可用的文件I/O函数——打开文件、读文件、写文件等,UNIX系统中的大多数文件I/O只需要用到5个函数:
open
read
write
lseek
close
这些函数经常被称为不带缓冲
的I/O(unbuffered I/O),术语不带缓冲指的是每个read和write都调用内核中的一个系统调用。
STDIN_FILENO
、STDOUT_FILENO
和STDERR_FILENO
以提高可读性;调用open
或openat
函数可以打开或创建一个文件:
#include
int open(const char *path, int oflag, .../* mode_t mode */);
int openat(int fd, const char *path, int oflag, .../* mode_t mode */);
// 两函数的返回值:若成功,返回文件描述符;若出错,返回-1
或
”运算构成:oflag参数选项 | ||
---|---|---|
选项 | 含义 | 备注 |
O_RDONLY | 只读打开 | 必选,且只能指定一个 |
O_WRONLY | 只写打开 | |
O_RDWR | 读、写打开 | |
O_EXEC | 只执行打开 | |
O_SEARCH | 只搜索打开(应用于目录) | |
O_APPEND | 每次写时都追加到文件的尾端 | 可选 |
O_CLOEXEC | 将FD_CLOEXEC设置为文件描述符的标志,在执行exec时关闭该文件描述符 | |
O_CREAT | 若此文件不存在则创建它 | |
O_EXCL | 如果同时指定了O_CREAT,而文件已经存在,则报错; 用此可以测试一个文件是否存在,如果不存在,则创建此文件,这使测试和创建两者成为一个原子操作 |
|
O_TRUNC | 如果此文件存在,而且为只写或读-写成功打开,则将其长度截断为0 | |
... | ... |
可调用creat
函数创建一个新文件:
#include
int creat(const char *path, mode_t mode);
// 返回值:若成功,返回只写打开的文件描述符;若出错,返回-1
此函数等效于:
open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
可调用close
函数关闭一个打开文件:
#include
int close(int fd);
// 返回值:若成功,返回0;若出错,返回-1
当前文件偏移量
”,它是一个非负整数,用以度量从文件开始处计算的字节数;可调用lseek
函数为一个打开文件设置偏移量:
#include
off_t lseek(int fd, off_t offset, int whence);
// 返回值:若成功,返回新的文件偏移量;若出错,返回-1
对参数offset的解释与参数whence的值有关:
调用read
函数从打开文件中读数据:
#include
ssize_t read(int fd, void *buf, size_t nbytes);
// 返回值:读到的字节数,若已到文件尾,返回0;若出错,返回-1
调用write
函数向打开文件写数据:
#include
ssize_t write(int fd, const void *buf, size_t nbytes);
// 返回值:若成功,返回已写的字节数;若出错,返回-1
大多数文件系统为改善性能都采用某种预读
技术,当检测到正进行顺序读取时,系统就试图读入比应用所要求的更多数据,并假想应用很快就会读这些数据。
UNIX系统支持在不同进程间共享打开文件,内核使用3种数据结构表示打开文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
进程表
中都有一个记录项,记录项中包含一张打开文件描述符表
,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:文件表
。每个文件表项包含:v节点
(v-node)结构。v节点包含了文件类型和对此文件进行各种操作函数的指针。对于大多数文件,v节点还包含了该文件的i节点
(i-node,索引节点),i节点包含了文件的所有者、文件长度、指向文件实际数据块在磁盘上所在位置的指针等。如果两个独立进程各自打开了同一文件,则有下图中的关系:
可能有多个文件描述符项指向同一文件表项,比如:
dup
函数复制文件描述符;fork
调用后,父进程、子进程各自的每一个打开文件描述符共享同一个文件表项。文件描述符标志
和文件状态标志
在作用范围方面的区别:
原子操作:由多步组成一个操作,要么执行完所有步骤,要么一步也不执行。
pread
和pwrite
允许原子性地定位并执行I/O:
#include
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
// 返回值:读到的字节数,若已到文件尾,返回0;若出错,返回-1
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
// 返回值:若成功,返回已写的字节数;若出错,返回-1
下面两个函数都可用来复制一个现有的文件描述符:
#include
int dup(int fd);
int dup2(int fd, int fd2);
// 两函数的返回值:若成功,返回新的文件描述符;若出错,返回-1
延迟写
:向文件写入数据时,内核先将数据复制到缓冲区中,然后排入队列,晚些时候再写入磁盘;#include
int fsync(int fd);
int fdatasync(int fd);
// 返回值:若成功,返回0;若出错,返回-1
void sync(void);
(1)sync
只是将所有修改过的块缓冲区排入写队列,然后就返回,并不等待实际写磁盘操作结束,通常update守护进程周期性地调用sync函数来冲洗内核的块缓冲区;
(2)fsync
函数只对由文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束才返回,可用于数据库这样的应用程序,确保修改过的块立即写到磁盘上;
(3)fsyncdata
函数类似于fsync
,但它只影响文件的数据部分,而fsync还会同步更新文件的属性。
fcntl
函数可以改变已经打开文件的属性:
#include
int fcntl(int fd, int cmd, .../* int arg */);
// 返回值:若成功,则依赖于cmd;若出错,返回-1
fcntl
函数有以下5种功能:
文件描述符标志 | 含义 |
---|---|
F_DUPFD | 复制文件描述符fd,返回尚未打开的各描述符中大于或等于第3个参数值的最小值; 新描述符与fd共享同一文件表项; 新描述符有自己的一套文件描述符标志,其FD_CLOEXEC标志被清除 |
F_DUPFD_CLOEXEC | 复制文件描述符,设置与新描述符关联的FD_CLOEXEC标志的值,返回新文件描述符 |
F_GETFD | 返回fd的文件描述符标志 |
F_SETFD | 设置fd的文件描述符标志,新标志按照第3个参数(取值为整型值)设置 |
F_GETFL | 返回fd的文件状态标志 |
F_SETFL | 将文件状态标志设置为第3个参数的值(取值为整型值) |
F_GETOWN | 获取当前接收SIGIO和SIGURG信号的进程ID或进程组ID |
F_SETOWN | 设置接收SIGIO和SIGURG信号的进程ID或进程组ID; 正的arg指定一个进程ID,负的arg表示等于arg绝对值的一个进程组ID |
对于fcntl的文件状态标志 | |
---|---|
文件状态标志 | 说明 |
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读、写打开 |
O_EXEC | 只执行打开 |
O_SEARCH | 只搜索打开目录 |
O_APPEND | 追加写 |
O_NONBLOCK | 非阻塞模式 |
O_SYNC | 等待写完成(数据和属性) |
O_DSYNC | 等待写完成(仅数据) |
O_RSYNC | 同步读和写 |
O_FSYNC | 等待写完成(仅FreeBSD和Mac OS X) |
O_ASYNC | 异步I/O(仅FreeBSD和Mac OS X) |
ioctl
函数一直是I/O操作的杂物箱,不能用其他函数表示的I/O操作通常都能用ioctl表示,终端I/O是使用ioctl最多的地方:
#include
#include
int ioctl(int fd, int request, ...);
// 返回值:若出错,返回-1;若成功,返回其他值
/dev/fd
的目录,其目录项是名为0、1、2等的文件;-
”特指标准输入和标准输出。chapter3