一个通用的 IO 模型通常包括打开文件、读写文件、关闭文件这些基本操作, 主要涉及到 4 个函数: open()、 read()、 write()以及 close()。
一、文件描述符
调用 open 函数会有一个返回值,返回int 类型的数据。在 open函数执行成功的情况下, 会返回一个非负整数, 该返回值为一个文件描述符(file descriptor) , 这说明文件描述符是一个非负整数; 对于 Linux 内核而言,所有打开的文件都会通过文件描述符进行索引。
当调用 open 函数打开一个现有文件或创建一个新文件时,内核会向进程返回一个文件描述符, 用于指代被打开的文件,所有执行 IO 操作的系统调用都是通过文件描述符来索引到对应的文件。
对于一个进程来说,文件描述符是一种有限资源, 文件描述符是从 0 开始分配的,譬如说进程中第一个被打开的文件对应的文件描述符是 0、第二个文件是 1、第三个文件是 2、第 4 个文件是 3……以此类推,文件描述符数字最大值为 1023(0~1023)。每一个被打开的文件在同一个进程中都有一个唯一的文件描述符,不会重复,如果文件被关闭后,它对应的文件描述符将会被释放,那么这个文件描述符将可以再次分配给其它打开的文件、与对应的文件绑定起来。
但是,当我们在程序中,调用 open 函数打开文件的时候,分配的文件描述符一般都是从 3 开始,因为0、 1、 2 这三个文件描述符已经默认被系统占用,分别分配给系统标准输入(0)、 标准输出(1)以及标准错误(2)。
注:Linux 系统下,一切皆文件,也包括各种硬件设备,使用 open 函数打开任何文件成功情况下便会返回对应的文件描述符 fd。每一个硬件设备都会对应于 Linux 系统下的某一个文件,把这类文件称为设备文件。所以设备文件对应的是某一硬件设备,应用程序通过对设备文件进行读写等操作、来使用、操控硬件设备,如 LCD 显示器、串口、音频。
标准输入是键盘,0 :打开键盘对应的设备文件时所得到的文件描述符;标准输出是 LCD 显器,1 :打开 LCD 设备对应的设备文件时所得到的文件描述符;而标准错误: LCD 显示器。
二、open()
在 Linux 系统中要操作一个文件,需要先打开该文件,得到文件描述符,然后再对文件进行相应的读写操作,最后在关闭该文件; open 函数用于打开文件,并且也可以创建一个新的文件。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname:用于标识需要打开或创建的文件,可以包含路径信息。
flags:调用 open 函数时需要提供的标志, 包括文件访问模式标志以及其它文件相关标志mode:只有使用 O_CREAT 或 O_TMPFILE 标志时才有效,用于指定新文件的访问权限
标志 | 用途 | 说明 |
O_RDONLY | 以只读方式打开文件 | |
O_WRONLY | 以只写方式打开文件 | |
O_RDWR | 以可读可写的方式打开文件 | 前三个是文件访问权限标志, 必须包含其中一种,并且只能是一种 |
O_CREAT | 文件不存在,则创建此文件 | 需要传入第3个参数mode,用于指定新文件的访问权限 |
O_DIRECTORY | pathname不为目录,则调用失败 | |
O_EXCL | 用于创建文件 | |
O_NOFOLLOW | pathname指向符号连接,将不对其解引用,返回错误 |
注:除了前三种标志,可以通过位或运算(|)将多个标志进行组合。
mode 参数的类型是 mode_t,为u32 无符号整形数据,权限表示方法如下所示:
O---其他用户的权限
G---同组用户(group)的权限,即与文件所有者有相同组 ID 的所有用户
U---文件所属用户的权限,即文件或目录的所属者
S---文件的特殊权限,文件特殊权限一般用的比较少
3 个 bit 位中,按照 rwx 顺序来分配权限位(特殊权限除外) 。最高位(权值为 4)表示读权限,为 1 时表示具有读权限,为 0 时没有读权限;中间位(权值为 2)表示写权限,为 1 时表示具有
写权限,为 0 时没有写权限;最低位(权值为 1)表示执行权限,为 1 时表示具有可执行权限,为 0 时没有执行权限。
111000000:表示文件所属者具有读、写、执行权限,同组用户和其他用户不具有任何权限
100100100:表示文件所属者、同组用户以及其他用户都具有读权限,但没有写、执行限
文件权限宏定义:
三、write()
调用 write 函数可向打开的文件写入数据
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd:将进行写操作的文件所对应的文件描述符传递给 write 函数
buf: 指定写入数据对应的缓冲区
count: 指定写入的字节数
返回值: 如果成功将返回写入的字节数(0 表示未写入任何字节)
注意:读写操作都是从文件的当前位置偏移量处开始。默认情况下当前位置偏移量一般是 0,指向了文件起始位置,当调用 read、 write 函数读写操作完成之后, 当前位置偏移量也会向后移动对应字节数。
四、read()
调用 read 函数可从打开的文件中读取数据
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
fd: 文件描述符
buf: 指定用于存储读取数据的缓冲区count: 指定需要读取的字节数
返回值: 如果读取成功将返回读取到的字节数
五、close()
调用 close 函数关闭一个已经打开的文件
#include <unistd.h>
int close(int fd);
fd: 文件描述符,需要关闭的文件所对应的文件描述符
返回值: 如果成功返回 0,如果失败则返回-1
六、lseek()
对于每个打开的文件, 系统都会记录它的读写位置偏移量,把读写位置偏移量称为读写偏移量,记录了文件当前的读写位置,当调用 read()或 write()函数对文件进行读写操作时,就会从当前读写位置偏移量开始进行数据读写。
读写偏移量用于指示 read()或 write()函数操作时文件的起始位置,会以相对于文件头部的位置偏移量来表示,文件第一个字节数据的位置偏移量为 0。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
fd: 文件描述符。
offset: 偏移量,以字节为单位。
whence: 用于定义参数 offset 偏移量对应的参考值,如下:1.SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头部开始算)
2.SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处,
3.SEEK_END:读写偏移量将指向 文件末尾 + offset 字节位置处返回值: 成功将返回从文件头部开始算起的位置偏移量(字节为单位), 当前的读写位置