Linux 基础IO
一. 系统调用接口
- fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
- open close read write lseek 都属于系统提供的接口,称之为系统调用接口。
根据这张图, 系统调用接口和库函数的关系, 一目了然。
二. 文件描述符
- 文件描述符fd就是一个整数。
- Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2。
- 0,1,2对应的物理设备一般是:键盘,显示器,显示器。
文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files,
指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针。所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
三. Linux中的IO接口
3.1 open函数
int open(const char *pathname, int flags);
-
功能:打开或创建文件。
-
参数:
pathname:要打开或创建的文件名。
flags:指定文件如何被打开(读取、写入、追加等),可以使用OR运算符组合多个标志。 -
返回值:成功时返回非负整数作为文件描述符,失败则返回-1,并设置errno。
3.2 close函数
int close(int fd);
- 功能:关闭一个打开的文件描述符。
- 参数:
fd:要关闭的文件描述符。 - 返回值:成功时返回0,失败则返回-1,并设置errno。
3.3 read函数
ssize_t read(int fd, void *buf, size_t count);
- 功能:从文件描述符读取数据。
- 参数:
fd:要读取的文件描述符。
buf:指向存储读取数据的缓冲区的指针。
count:尝试读取的最大字节数。 - 返回值:成功时返回实际读取的字节数,如果达到文件末尾则返回0。失败则返回-1,并设置errno。
3.4 write函数
ssize_t write(int fd, const void *buf, size_t count);
- 功能:向文件描述符写入数据。
- 参数:
fd:要写入的文件描述符。
buf:指向要写入的数据的指针。
count:要写入的字节数。 - 返回值:成功时返回实际写入的字节数,可能小于count(例如,当写入非阻塞文件描述符时)。如果返回值为0,表示没有数据被写入。失败则返回-1,并设置errno。
3.5 dup2函数
int dup2(int oldfd, int newfd);
- 功能:复制一个文件描述符,使其具有新的文件描述符号。
- 参数:
oldfd:现有的文件描述符。
newfd:新文件描述符的号码。如果newfd已经打开,它将被关闭,然后设置为oldfd的副本。 - 返回值:成功时返回新的文件描述符,通常是newfd。失败则返回-1,并设置errno。
3.6 lseek函数
off_t lseek(int fd, off_t offset, int whence);
- 功能:改变文件描述符的读/写位置。
- 参数:
fd:要改变位置的文件描述符。
offset:相对于whence的位置偏移量。
whence:定义了offset的参考点,可以是SEEK_SET(文件开头)、SEEK_CUR(当前位置)、SEEK_END(文件结尾)。 - 返回值:成功时返回新的文件偏移量,失败则返回-1,并设置errno。
四. Linux文件系统
4.1 ex2文件系统
磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可更改。例如mke2fs的-b选项可以设定block大小为1024、2048或4096字节。启动块(Boot Block)的大小是确定的。以下该图为磁盘文件系统图:
- 超级块(Superblock):
超级块位于文件系统的开头,通常在第一个块组中,但为了容错,每个块组都会复制一份超级块它包含了整个文件系统的关键信息,如文件系统的大小、状态、空闲块的数量等。如果某个超级块损坏了,可以使用其他块组中的备份超级块来恢复文件系统。 - 组描述符表(Group Descriptor Table):
每个块组都有一个组描述符,这个表记录了所有块组的信息。组描述符中包含了指向该组内位图和inode表的指针,以及关于该组的一些统计信息,比如有多少个空闲块或inode。 - 块位图(Block Bitmap):
块位图用于追踪块组内的哪些块已经被分配出去,哪些块还处于未分配状态。每一位对应一个块,如果位为1,则表示对应的块已被分配;如果位为0,则表示该块尚未被分配。 - inode位图(Inode Bitmap):
inode位图与块位图类似,但它用于追踪inode的状态。每一位代表一个inode,位为1表示inode已分配,位为0表示inode未分配。 - inode表(Inode Table):
每个块组都有一个inode表,用于存储该组内的所有inode。inode是文件系统中用来存储文件元数据(例如文件类型、权限、时间戳等)的数据结构。每个文件或目录在文件系统中都有一个唯一的inode号。 - 数据块(Data Blocks):
数据块是用来存储文件实际内容的空间。文件系统中的每个文件都可以占用一个或多个数据块。数据块的分配是通过inode中的指针来实现的,这些指针指向实际存储文件数据的数据块。
4.2 inode索引节点
每个文件在文件系统中都有一个唯一的inode编号,inode包含了文件的各种属性和信息,但不包括文件名和文件的实际数据。
inode的主要内容:
文件类型:指示文件是普通文件、目录、符号链接还是其他类型的特殊文件(如设备文件)。
文件权限:定义了文件的所有者、所属组和其他用户的读、写、执行权限。
文件所有权:包括文件的所有者(用户ID)和所属组(组ID)。
文件大小:以字节为单位的文件大小。
时间戳:包括文件的最后修改时间(mtime)、最后访问时间(atime)和inode最后更改时间(ctime)。
链接数:指向该inode的硬链接数量。
指向数据块的指针:这些指针指向存储文件实际内容的数据块。对于大文件,inode可能包含直接指针、间接指针、双重间接指针甚至三重间接指针。
特殊标志:例如,是否设置了追加只写标志(append-only)、不可删除标志(immutable)等。
4.3 文件是如何创建的
- 存储属性
内核先找到一个空闲的i节点(这里是263466)。内核把文件信息记录到其中。 - 存储数据
该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推。 - 记录分配情况
文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。 - 添加文件名到目录
新的文件名abc。linux如何在当前的目录中记录这个文件?内核将入口(263466,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来。
4.4 硬链接和软链接
4.4.1 硬链接
- 创建方法:
ln source target
- 定义: 硬链接是指向同一个inode的不同文件名。也就是说,多个文件名可以指向同一个文件数据。
- 特点
- 共享inode:硬链接文件和原始文件共享同一个inode,因此它们实际上是同一个文件的不同名字。
- 数据一致性:对任何一个硬链接文件进行修改,都会影响到其他所有硬链接文件,因为它们共享相同的数据块。
删除影响:删除一个硬链接文件不会删除实际的数据,只有当最后一个硬链接被删除后,inode才会被释放,数据块也会被回收。 - 限制:硬链接不能跨文件系统创建,只能在同一文件系统内部创建。
4.4.2 软链接
-
创建方法:
ln -s target symbolic_link
-
定义: 软链接(符号链接)是一个特殊的文件,它包含了指向另一个文件或目录的路径名。它就像Windows中的快捷方式。
-
特点
- 独立inode:软链接有自己的inode,它存储的是目标文件的路径名。
- 路径依赖:软链接依赖于目标文件的路径,如果目标文件被移动或删除,软链接会失效(变成“断链”)。
- 跨文件系统:软链接可以跨文件系统创建。
- 支持目录:软链接可以指向文件或目录。
- 灵活性:软链接可以创建更复杂的文件系统结构,例如跨目录的快捷方式。
————————————————————
感谢大家观看,不妨点赞支持一下吧喵~
如有错误,随时纠正喵~