看完了apue关于系统I/O的几章,打算还是总结一下,不然在脑子总是不成条理.
文件描述符:对于内核而言,所有打开的文件都是通过文件描述符引用的.(所以系统调用的I/O函数参数基本都是文件描述符,而标准I/O库函数却不是这样).
- 文件描述符是一个非负整数,其中0,1,2已经与标准输入,标准输入,标准错误关联(默认情况下,实际上我们也可以修改,但是并不推荐).
- 当打开现有文件或者创建一个新文件的时候,内核向进程返回一个文件描述符.
函数open(),openat(),打开或创建一个文件
int open(const char *path, int oflag, mode_t mode)
int openat(int fd, const char *path, int oflag, mode_t mode)- path参数是要打开的或创建的文件的名字
- oflag用来说明多个选项,用多个选项’或’运算构成oflag.下面列出了部分oflag选项,具体可查看Unix程序员手册.
- O_RDONLY 只读打开
- O_WRONLY 只写打开
- O_RDWR 读,写打开
- O_APPEND 每次写都追加到文件尾端
- O_CREAT 若文件不存在就创建它.使用此选项时需要指定mode参数来确定创建文件的权限位(权限位还与umask值有关)
- O_TRUNC 如果此文件存在,且为只写或读-写成功打开,将其长度截断为0
- 若成功返回相应的文件描述符,否则返回-1
- fd参数区分开open和openat函数,有三种情况:
- path指定的是绝对路径,fd就被忽略,此时openat等同于open
- path指定相对路径名,fd参数指定相对路径名在文件系统中的开始位置.fd参数是通过打开相对路径名所在的目录来获取的.
- path指定相对路径名,fd参数为特殊值AF_FDCWD.此时,路径名在当前工作目录中火区,openat类似于open函数
creat(),close()函数
int creat(const char *path, mode_t mode)
int close(int fd)- creat函数等效于open(path,O_WRONLY | O_CREAT | O_TRUNC, mode).实际上更适合用open来替代
- close成功返回0,creat成功返回只写打开的文件描述符,错误的话都返回-1
- 关闭一个文件会释放该进程加在该文件上的所有记录锁,同时当一个进程终止时,内核自动关闭其打开的所有文件,所以close实际上可以不调用,但是建议显式调用,是个良好的变编程习惯(笑)
lseek()函数
off_t lseek(int fd, off_t offset, int whence)
- 若成功,返回新的文件偏移量,若错误就返回-1
- 通常,读写从当前文件偏移量开始,并使偏移量增加所读写的字节.默认情况下,若open没有指定O_APPEND选项,偏移量被设为0
- 对offset的解释和whence有关:
- 若whence是SEEK_SET,则将文件偏移量设为距文件开始处offset个字节处
- 若是SEEK_CUR,则设置为距当前文件偏移量offset个字节处
- 若是SEEK_END,则设置为文件长度加上offset,offset可正可负(注:在offset为正的情况下,下次写开始之处与之前文件结束之处就存在了空洞)
read()函数
ssize_t read(int fd, void *buf, size_t nbytes)
- 成功的话返回实际读到的字节数,如果到达文件尾就返回0,出错返回-1
- 读操作从当前文件偏移量开始,读至多nbytes的字节,如果提前遇到文件尾,返回实际读到的字节数,下次读遇到文件尾直接返回0.
write()函数
ssize_t write(int fd, const void *buf, size_t nbytes)
- 成功的话返回已写的字节数,出错返回-1,通常其返回值与nbytes相同,否则表示出错.
- 对于普通文件,写操作从当前偏移量开始. 如果打开该文件时,指定了O_APPEND选项,则在每次写操作前,将文件偏移量设置在文件的当前结尾处. 在一次成功写之后,文件偏移量增加实际写的字节数
文件共享
- 每个进程在进程表中有一个记录项,记录项中包含一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项.与每个文件描述符相关联的是
- 文件描述符标志(close_on_exec)
- 指向一个文件表项的指针
- 内核为所有打开文件维持一张文件表(每个文件可能不止一张),每个文件表项包含:
- 文件状态标志(读,写,添加,同步,非阻塞等)
- 当前文件偏移量
- 指向该文件v节点表项的指针
- 每个打开文件(或设备)都有一个v节点结构.v节点包含了文件类型和对此文件进行各种操作函数的指针,对大多数文件来说,v节点还包含了该文件的i节点(索引节点).这些信息实在打开文件时从磁盘读入内存的,所以文件的所有相关信息都是随时可用的.
- 每个进程在进程表中有一个记录项,记录项中包含一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项.与每个文件描述符相关联的是
dup()函数和dup2()函数
int dup(int fd)
int dup2(int fd, int fd2)- 如果成功返回新的文件描述符,错误返回-1
- 由dup返回的新文件描述符一定是当前可用描述符中的最小值.
- 对于dup2,可以由参数fd2指定新描述符的值.如果fd2已经打开,则先将其关闭.若fd等于fd2,则dup2返回fd2,而不关闭它.否则fd的FD_CLOEXEC文件描述符被清楚,这样fd2在进程调用exec(一系列的函数)时是打开状态.
- 这些函数返回的新文件描述符与参数fd共享一个文件表项,如下图所示.

fcntl()函数
int fcntl(int fd, int cmd, …
/* int arg */)- 若成功,返回值依赖于cmd,若失败返回-1
- 返回有有一些五种功能(具体用法略显复杂,可以参阅Unix程序员手册):
- 复制一个现有的描述符(cmd = F_DUPED或 F_DUPFD_CLOEXEC)
- 获取/设置文件描述符标志(cmd = F_GETED或F_SETFD)
- 获取/设置文件状态标志(cmd = F_GETFL或F_SETFL)
- 获取/设置异步I/O所有劝(cmd = F_GETOWN或F_SETOWN)
- 获取/设置记录锁(cmd = F_GETLK或F_SETLK或F_SETLKW)
- 注意:在修改文件描述符标志或文件状态标志时必须谨慎,要先获得现在的标志值,然后按照期望修改它,最后设置新标志值.不能只是执行F_SETFD或F_SETFL,这样会关闭之前设置的标志位
结语:算是把apue第三章的内容copy了一遍(笑),上面简单总结了我觉得比较重要的部分,太过繁琐的细节我就没有细扣了,想深入了解的可以看看unix程序员手册.上面也主要是系统I/O的部分,文件和标准库I/O的也还没总结,过几天应该会再写几篇博客总结一下.