【TLPI读书笔记】 四、文件I/O:通用的I/O模型
概述
- 所有执行I/O操作的系统调用都以文件描述符,一个非负整数的,来指代打开的文件。
- 文件描述符用以表示所有类型的已打开文件,包括管道、FIFO、socket、终端、设备和普通文件。
- 针对每个进程,文件描述符都自成一套。
- 文件描述符
0
,1
,2
表示标准输入,标准输出,标准错误,这三个文件描述符在进程中始终是打开的 下面介绍执行文件I/O操作的4个主要系统调用:
fd = open(pathname, flags, mode)
函数打开pathname
所标识的文件,并返回文件描述符,用以在后续函数调用中指代打开的文件numread = read(fd, buffer, count)
调用从fd
所指代的打开文件中读取至多count
字节的数据,并存储到buffer
中numwritten = write(fd, buffer, count)
调用从buffer
中读取多达count
字节的数据写入由fd所指代的已打开文件中status = close(fd)
在所有输入/输出操作完成后,调用close()
,释放文件描述符fd
以及与之相关的内核资源
通用I/O
UNIX I/O模型的显著特点之一是其输入/输出的通用性概念。这意味着使用上述4个同样的系统调用可以对所有类型的文件执行I/O操作。
一旦应用程序需要访问文件系统或设备的专有功能时,可以选择瑞士军刀般的ioctl()
系统调用。
打开一个文件:open()
open()
调用既能打开一个已存在的文件,也能创建并打开一个新文件。
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, .../* mode_t mode */);
// Returns file descriptor on success, or -1 on error
参数
pathname
:所要打开的文件路径,如果是符号链接会对其进行解引用flags
:位掩码,用于指定文件的访问模式,可能的值如下:
访问模式 | 描述 |
---|---|
O_RDONLY | 以只读方式打开文件 |
O_WRONLY | 以只写方式打开文件 |
O_RDWR | 以读写方式打开文件 |
O_CLOEXEC | 设置close-on-exec 标志 |
O_CREAT | 若文件不存在则创建之 |
O_DIRECT | 无缓冲的输入/输出 |
O_DIRECTORY | 若pathname 不是目录,则失败 |
O_EXCL | 结合O_CREAT 参数使用,专门用于创建文件 |
O_LARGEFILE | 在32位系统中使用此标志打开大文件 |
O_NOATIME | 调用read() 时不更新文件最近访问时间 |
O_NOCTTY | 不要让pathname (所指向的是终端设备)成为控制终端 |
O_NOFOLLOW | 对符号链接不予解引用 |
O_TRUNC | 截断已有文件,使其长度为0 |
O_APPEND | 总在文件尾部追加数据 |
O_ASYNC | 当I/O操作可行时,产生信号通知进程 |
O_DSYNC | 提供同步的I/O数据完整性 |
O_NONBLOCK | 以非阻塞方式打开 |
O_SYNC | 以同步方式打开 |
使用的时候多个值作|
(按位或)运算即可
mode
:文件权限,只有当flags
指定为O_CREAT
(创建)访问模式时,才有必要设置这个参数,换句话说,这个参数是为了新建文件时指定其权限用的,其可能的值如下:
权限标识 | 描述 |
---|---|
S_IRUSR | 所属主可读 |
S_IWUSR | 所属主可写 |
S_IXUSR | 所属主可执行 |
S_IRGRP | 所属组可读 |
S_IWGRP | 所属组可写 |
S_IXGRP | 所属组可执行 |
S_IROTH | 其他人可读 |
S_IWOTH | 其他人可写 |
S_IXOTH | 其他人可执行 |
使用的时候多个值作|
(按位或)运算即可
返回值
- 成功:返回文件描述符。SUSv3规定,必须保证其返回值为进程未用文件描述符中数值最小者
- 失败:返回
-1
,并将errno
置为相应的错误标志
读取文件内容:read()
read()
系统调用从文件描述符fd所指代的打开文件中读取数据。
#include <unistd.h>
ssize_t read(int fd, void *buffer, size_t count);
// Returns number of bytes read, 0 on EOF, or -1 on error
参数
fd
:文件描述符buffer
:提供用来存放输入数据的内存缓冲区地址,缓冲区至少应有count
个字节count
:指定最多能读取的字节数
返回值
- 成功:返回实际读取的字节数;该返回值可能小于
count
参数值,如遇到文件结束(EOF
) - 失败:返回
-1
,并将errno
置为相应的错误标志;如遇到文件结束(EOF
)则返回0
数据写入文件:write()
write()
系统调用从文件描述符fd
所指代的打开文件中读取数据。
#include <unistd.h>
ssize_t write(int fd, void *buffer, size_t count);
// Returns number of bytes written, or -1 on error
参数
fd
:文件描述符buffer
:提供要写入文件中数据的内存地址count
:指定最多能写入的字节数
返回值
- 成功:返回实际写入的字节数;该返回值可能小于
count
参数值,这被称为“部分写” - 失败:返回
-1
,并将errno
置为相应的错误标志
关闭文件:close()
close()
系统调用关闭一个打开的文件描述符,并将其释放回调用进程,供该进程继续使用。
#include <unistd.h>
int close(int fd);
// Returns 0 on success, or -1 on error
显式关闭文件往往是良好的编程习惯
参数
fd
:文件描述符
返回值
- 成功:返回
0
- 失败:返回
-1
,并将errno
置为相应的错误标志
改变文件偏移量:lseek()
- 对于每个打开的文件,系统内核会记录其文件偏移量,有时也将文件偏移量称为读写偏移量或指针。文件偏移量是指执行下一个
read()
或write()
操作的文件起始位置,会以相对于文件头部起始点的文件当前位置来表示。文件第一个字节的偏移量为0。 - 文件打开时,会将文件偏移量设置为开始,以后每次执行
read()
或write()
将自动对其调整,以指向已读或已写数据后的下一节。
lseek()
系统调用依照offset
和whence
参数值调整该文件的偏移量
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
// Returns new file offset if successful, or -1 on error
参数
fd
:文件描述符offset
:指定了一个以字节为单位的数值whence
:表明应参照哪个基点来解释offset
参数,应为下列其中之一:SEEK_SET
:将文件偏移量设置为从文件头部起始点开始的offset
个字节SEEK_CUR
:相对于当前文件偏移量,将文件偏移量调整offset
个字节SEEK_END
:将文件偏移量设置为起始于文件尾部的offset
个字节
返回值
- 成功:返回实际写入的字节数;该返回值可能小于
count
参数值,这被称为“部分写” - 失败:返回
-1
,并将errno
置为相应的错误标志
文件空洞
如果程序的文件偏移量跨越了文件结尾,然后执行I/O操作,read()
调用将返回0
,表示文件结尾;但write()
调用可以在文件结尾后任意位置写入数据。
从文件结尾后到新写入数据间的这段空间被称为文件空洞。文件空洞存在字节(空字节,读取将返回0)但不占用任何磁盘空间。
通用IO模型以外的操作:ioctl()
ioctl()
系统调用为执行文件和设备操作提供了一种多用途机制。
#include <sys/ioctl.h>
int ioctl(int fd, int request, ... /* argp */);
// Value returned on success depends on request, or -1 on error
参数
fd
:文件描述符request
:指定了将在fd
上执行的控制操作。具体设备的头文件定义了可传递给request
参数的常量...
:可以是任意数据类型,根据request
的参数值来确定argp
所期望的类型。通常情况下是指向整数或结构的指针
返回值
- 成功:返回
0
- 失败:返回
-1
,并将errno
置为相应的错误标志
总结
本章介绍了通用I/O模型,这是UNIX环境下非常重要非常基础的一个概念