系统调用I/O
1.1 文件描述符
对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数,当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。在符合POSIX.1的应用程序中,幻数0,1,2虽然已经被标准化,但应当把它们替换成符号常量STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO以提高可读性。这些常量都在头文件<unistd.h>中。1.2 打开或创建一个文件
#include <fcntl.h> int open(const char *path, int oflag,…/* mode_t mode */); int openat(int fd, const char *path, int oflag,…/* mode_t mode */); 返回值:成功返回文件描述符,失败返回-1 |
仅当创建新文件时才使用最后一个参数。
path参数是要打开或创建文件的路径名。
oflag参数用来说明函数的多个选项,用下列一个或多个常量进行或运算构成oflag参数。这些常量在<fcntl.h>中定义:
oflag | 说明 | 备注 |
O_RDONLY | 只读打开 |
|
O_WRONLY | 只写打开 |
|
O_RDWR | 读写打开 |
|
O_EXEC | 只执行打开 |
|
O_SEARCH | 只搜索打开(用于目录) | 以上有且仅有一个存在,O_SEARCH常量的母的在于在目录了打开时验证它的搜索权限。 |
O_APPEND | 将文件偏移量设置到文件末尾处 |
|
O_CLOEXEC | 把FD_CLOEXEC常量设置为文件描述符标志 |
|
O_CREAT | 文件不存在则创建它。使用这个选项需要设置最后的mode参数 |
|
O_DIRECTORY | Path不是目录则出错 |
|
O_EXCL | 如果同时指定了O_CREAT,而文件已经存在则出错,不存在则创建它。 |
|
O_NOCTTY | 如果path引用的是终端设备,则不将该设备分配作为此进程的控制终端。 |
|
O_NOFOLLOW | 如果path引用的是一个符号链接则出错 |
|
O_NONBLOCK | 如果path引用的是一个FIFO、一块特殊文件或一个字符特殊文件,则此选项为文本的本次打开操作和后续I/O操作设置非阻塞方式。 |
|
O_SYNC | 使每次write等待物理I/O操作完成,包括由该write操作引起的文件属性更新所需的I/O |
|
O_DSYNC | 使每次write要等待物理I/O操作完成,但是如果该操作并不影响读取刚写入的数据,则不需要等待文件属性被更新。 | 当文件用O_DSYN标志打开,在重写其现有的部分内容时,文件时间属性不会同步更新。于此相反,如果文件使用O_SYNC标志打开,那么对该文件的每一次write都将在write返回前更新文件时间,这与是否改写现有字节或追加文件无关。 |
O_TRUNC | 如果文件存在,而且为只写或读写成功打开,则将其长度截断为0 |
|
O_TTY_INIT | 如果打开一个还未打开的终端设备,设置非标准termios参数值。 |
|
1.2.1 open与openat的区别
fd参数把open和openat函数区分开,有2种可能性。(1) path参数指定的是绝对路径名,在这种情况下,fd参数被忽略,两个函数没有区别。
(2) path参数指定的是相对路径,fd参数指出了相对路径名在文件系统中的开始地址,fd参数是通过打开相对路径名所在的路径来获取。
(3) path参数之指定了相对路径名,fd参数具有特殊值AT_FDCWD。这种情况下,路径名在当前目录中获取,openat函数在操作上与open函数类似。
1.3 改变文件偏移量
每个打开文件都对应有文件表项,纪录了当前文件偏移量,通常是非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。 #include <unistd.h> off_t lseek(int fd, off_t offset, int whence); 返回值:成功返回新的文件偏移量,错误返回-1 |
选项 | 说明 |
SEEK_SET | 文件偏移量设置为文件开始出offset个字节 |
SEEK_CUR | 文件偏移量设置为文件当前值加offset个字节,offset可正可负 |
SEEK_END | 文件偏移量设置为文件长度加offset个字节,offset可正可负 |
文件中的空洞并不要求在磁盘上占用存储区,具体处理方式与文件系统的实现有关,当定位到超出文件尾端之后写时,对于新写的数据需要分配磁盘块,但对于原文件尾端和新开始写位置之前的部分则不需要分配磁盘块。
1.4 读取文件数据
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t nbyts); 返回值:成功则返回已写的字节数,出错返回-1 |
(1) 读普通文件,在读到要求字节数之前已到达文件尾端。
(2) 读终端设备时,通常一次最多读一行。
(3) 读网络时,网络中的缓冲机制可能造成返回值小于要求读的字节数。
(4) 读管道或FIFO时,若管道包含的字节少于所需的数量,那么read将只返回可用的字节数。
(5) 当信号中断,而已经读了部分数据量。
1.5 向文件写数据
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t nbyts); 返回值:成功则返回已写的字节数,出错返回-1 |
返回值通常与参数nbytes的值相同,否则表示出错。磁盘已满或超过一个给定进程的文件长度限制都可能导致出错。在一次成功写之后,文件偏移量增加实际写的字节数。
1.6 文件共享
内核使用了3种数据结构来表示打开文件。分别是打开文件描述符表、文件表和v节点(i节点)。(1) 每个进程在进程表中都有一个记录项,记录项中包含一张打开文件描述符表,每个描述符占用一项,与每个文件描述符相关联的是:
a. 文件描述符标志(close_on_exec)
b. 指向一个文件表项的指针
(2) 内核为所有打开文件维持一张文件表,每个文件表项包含:
a. 文件状态标志(读、写、添写、同步和非阻塞等)
b. 当前文件偏移量
c. 指向该文件v节点的指针。
(3) 每个打开文件(或设备)都有一个v节点结构,v节点包含了文件类型和对此文件进行各种操作函数的指针。对于大多数文件,v节点还包含了该文件的i节点。Linux中没有v节点,而是使用了通用的i节点结构。
1.7 原子操作
原子操作指的是由多不组成的一个操作,如果该操作原子执行,则要么执行完所有步骤,要么都不执行。Single UNIX Specification包括了XSi扩展,该扩展允许原子性地定位并执行I/O。pread和pwrite就是这种扩展。
1.7.1 pread和pwrite函数
#include <unistd.h> ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset); 返回值:读到的字节数,若到文件尾则返回0,出错返回-1 ssize_t pwite(int fd, const void *buf, size_t nbytes, off_t offset); 返回值:返回已写的字节数,错误返回-1 |
与read和write不同的是,无法终端其定位和读(写)操作。
1.7.2 创建一个文件
对于open函数,同时指定O_CREAT和O_EXCL选项时,而该文件已经存在则open失败,否则会原子操作地创建并打开文件。1.8 复制现有的文件描述符
#include <unistd.h> int dup(int fd); int dup2(int fd, int fd2); 返回值:成功则返回新的文件描述符;错误返回-1。 |
dup函数返回的新文件描述符一定是当前可用文件描述符中最小的整数。dup2函数使用fd2参数指定新描述符的值。如果fd2已经打开,则会先将其关闭(不报错)。如果fd2等于fd,返回fd2,而不关闭它。这些函数返回的新文件描述符与参数fd共享同一个文件表项:
1.9 函数fcntl
fcntl函数可以改变已经打开文件的属性。 #include <fcntl.h> int fcntl(int fd, int cmd,… /* int arg */); 返回值:成功,则依赖于cmd,出错则返回-1 |
fcntl函数有一下5个作用:
(1) 复制一个已有的文件描述符 (cmd=F_DUPFD 或 F_DUPFD_CLOEXEC )。
(2) 获取/设置文件描述符标志(cmd=F_GETFD 或 F_SETFD)。
(3) 获取/设置文件状态标志(cmd=F_GETFL 或 F_SETFL)。
(4) 获取/设置异步I/O所有权(cmd=F_GETOWN 或 F_SETOWN)。
(5) 获取/设置记录锁(cmd=F_GETFK、F_SETLK或F_SETLKW)。
Cmd | 说明 |
F_DUPFD | 复制文件描述符fd。新文件描述符作为函数值返回。它是尚未打开的各描述符中大于或等于第3个参数值中的最小值。 |
F_DUPFD_CLOEXEC | 复制文件描述符,设置与新描述符关联的FD_CLOEXEC文件描述符标志的值,返回新文件描述符。 |
F_GETFD | 对应于fd的 文件描述符标志作为函数的返回值,当前只有FD_CLOEXEC这个标志。 |
F_SETFD | 对应fd设置文件描述符的标志。,新的值按第三个参数设置。 |
F_GETFL | 对应fd的文件状态标志作为函数值返回。遗憾的是O_RDONLY,O_WRONLY,O_RDWR,O_EXEC,O_SEARCH这五个标志并不是各占一位,因此首先使用屏蔽字O_ACCMODE取得访问方式位,然后将结果与5个值做相等比较,其他的标志与函数返回值做与运算。 |
F_SETFL | 将文件状态标志设置为第三个参数的值,可以更改的标志是:O_APPEND,O_NONBLOCK,O_SYNC,O_DSYNC,O_RSYNC,O_FSYNC和O_ASYNC。 |
F_GETOWN | 获取当前接收SIGIO和SIGURG信号的进程ID或进程组ID。 |
F_SETOWN | 设置接收SIGIO和SIGURG信号的进程ID或进程组ID,正的arg表示一个进程ID,负的 表示等于绝对值的一个进程组ID。 |
1.10 sync、fsync和fdatasync函数
内核中设有缓冲区高速缓存或页高速缓存,大多数磁盘I/O都通过缓冲区进行。当我们向文件写入数据时,内核通常先将数据复制到缓冲区,然后排入队列,晚些再写入磁盘。这种方式称为延迟写。通常,当内核需要重用缓冲区来存放其他磁盘块数据时,会把所有延迟写数据写入磁盘。为了保证磁盘上实际文件系统与缓冲区中内容的一致性,Unix提供了这三个函数。
#include <unistd.h> int fsync(int fd); int fdatasync(int fd); 返回值:成功返回0,出错返回-1 void sync(void); |
sync只是将所有修改过的块缓冲区排入写队列,然后就返回,并不等待实际写磁盘操作结束。通常称为update的系统守护进程周期性的调用sync函数定期冲洗内核的块缓冲区。
fsync函数只对由文件描述符fd指定的一个文件起作用,并且等待写磁盘结束才返回。
fdatasync函数类似于fsync,但他只影响文件的数据部分,而除了数据外,fsync还会同步更新文件的属性。