Linux 基础 I/O
概念
在 Linux 操作系统中,实现了两类对文件 I/O 的管理,一类遵循 POSIX 标准,Linux 操作系统自身提供的 IO 系统调用函数,直接进行 IO 系统调用,我们在这里对这些函数进行简单的介绍。
特性
- 系统调用的 I/O 函数是不带缓冲的 I/O。每个 read 和 write 都调用内核中的一个系统调用。
- 所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核返回一个唯一的文件描述符。
- 系统把文件描述符 0 与进程的标准输入相关联,文件描述符 1 与标准输出相关联,文件描述符 2 与标准错误相关联。
函数
打开/创建文件
open 函数
int open(const char* filename, // 想要打开/创建的文件的路径
int flag, // 打开文件的方式
/*
O_RDONLY 只读方式打开
O_WRONLY 只写方式打开
O_RDWR 可读可写方式打开
以上三种打开方式同时只能存在一种
O_CREAT 若要打开的文件不存在则自动创建
O_EXCL 如果打开的文件存在/为符号链接则报错
O_EXCL 通常与 O_CREAT 一起搭配使用
O_TRUNC 文件截断
其他的参数可以自己查找 man 手册
*/
...) // 创建时需要的第三个参数,用来约定文件权限可以采用直接设置八进制的方式 如 :0644
在这里先只验证 O_CREAT 与 O_EXCL 和 O_TRUNC
O_CREAT
O_EXCL
由于之前的 test.txt 文件已经创建,此时应该报错。
O_TRUNC
我们先随便往之前的 test.txt 文件中写一些字符,随后调用 O_TRUNC。
调用前
调用后
可以发现文件已经被截断。
关闭文件
colse 函数
int close (int fd)
此函数的参数为调用 open 函数打开文件时返回的文件描述符。与 open 搭配使用。
读取文件
read 函数
ssize_t read(int fd, void * buf, size_t count)
在读取数据过程中,文件的读位置会随读取到的字节移动,成功调用返回读取的字节数。
当出现 : 文件中剩余字数小于 count 时
read 请求已被某个信号中断
文件是管道文件时
返回值可能小于欲读取的数据量。
我们先向 test.txt 中加入一些数据。
可以看出 test.txt 文件里有 14 个字符。
我们给出的 buf 是20个字节的,但是文件中只有 14 个字符,所以 read 函数的返回值应该是等于14 而不是 20的。
写入文件
write 函数
ssize_t write(int fd, void *buf, size_t count)
以 buf 为起始地址的缓冲区前 count 个字节写入与打开文件描述符 fd 关联的文件内。
调用成功返回真正写入数据的大小。
因为之前的 test.txt 文件的内容是 abcdefghijklmn 我们从 d 开始插入。要想从 d 的位置开始插入,我们需要将写的位置后移三个字符,在这里我们需要用到 lseek 函数。
off_t lseek(int fd, // open 函数所返回的文件描述符
off_t offset, // 相对于第三个参数的偏移量
int whence) // SEEK_SET 0 文件头部
// SEEK_CUR 1 当前位置
// SEEK_END 2 文件末尾
可以发现,在添加之后,并不会覆盖后面的内容。
首先我们要清楚 Linux 中的文件结构。
Linux 为管理每个进程打开的文件,在进程的私有结构体 task_struct 中。对任何进程,都将为其专门分配管理打开文件信息的表项,用以指示当前进程打开的文件结构体 struct file,在 struct file 中,将最终指向对应的文件。
struct file{
struct list_head f_list; // 文件对象链表
struct dentry *f_dentry; // 文件的 dentry 结构
struct vfsmount *f_vfsmnt; // 文件系统挂载信息(所挂载的根目录)
struct file_openerations *f_op; // 文件操作 open read write等
atomic_t f_count; // 文件的引用计数(有多少个进程打开该文件)
unsigned int f_flags; // 文件标志也就是 open 函数返回的 fd
mode_t f_mode; // 文件的打开模式(权限)
int f_error;
loff_t f_pos; // 文件操作指针的当前位置
unsigned int f_uid, f_gid; // 文件的 uid gid
...
};
文件描述符 fd :
任何进程在运行时默认打开三个流对象,stdin, stdout, stderr,其文件描述符分别为 0 1 2,以后打开的文件描述符的值一般选用未使用的最小值。
对于用户来说,任何打开的文件都将被分配一个唯一的非负整数,这个整数就是所谓的文件描述符 fd。
在这里我们需要先画出 Linux 文件表图
一个进程拥有一个 task_struct 结构体来标识基本信息,其成员 file 指向打开的文件列表,在文件列表中,列出当前进程打开的所有文件,由其成员指向打开的文件信息位置 file 结构体,在 file 结构体中,通过 v 节点最终查找到文件的磁盘位置。
我们可以看到在 file 结构体中有一个 f_pos 的成员,当我们对文件进行读写操作的时候,该成员会随着函数的调用而改变,从而记录文件操作的当前位置,应为每个进程都有会其唯一的 task_struct 结构体来标识信息,所以当多个进程同时进程读操作是不会互相影响的,但是当多个进程同时进行写操作则会出现一些错误现象。