此为第三章的笔记,由于其内容大部分类似于API手册,所以就不直接搬运到笔记上了。
简介
UNIX系统中的大多数文件I/O只需要5个函数:open 、 write 、 lseek 和 close 。
不带缓冲的I/O:每个 write 和 read 都调用内核中的一个系统调用。
文件描述符
非负整数。
对于内核而言,所有打开的文件都通过 文件描述符引用 。打开或创建一个新文件时,内核向进程返回一个文件描述符。
通过 open 或 create 返回文件描述符表示该文件,并将其作为参数传给 read 或 write。
范围在 0 ~ OPEN_MAX-1 之间。
默认文件描述符
0(STDIN_FILENO)标准输入
1(STDOUT_FILENO)标准输出
2(STDERR_FILENO)标准错误
括号中的符号常量定义在 <unistd.h> 头文件中。
TOCTTOU 错误
time-of-check-to-time-of-use:如果有两个基于文件的函数调用,其中第二个依赖于第一个调用的结果,那么该程序是脆弱的。两个系统并不是原子操作,两个函数调用之间文件可能改变了。
文件打开方式
在 <fcntl.h> 头文件中定义。这里只列出部分重要的,其他见PDFp70
| 代号 |
意义 |
| O_RDONLY |
只读打开(0) |
| O_WRONLY |
只写打开(1) |
| O_RDWR |
读、写打开(2) |
| O_EXEC |
只执行打开 |
| - |
以上只能指定一个 |
| O_APPEND |
追加至文件末尾 |
| O_CREAT |
不存在则创建(配合O_RDWR使用) |
| O_SYNC |
每次write等物理I/O完成(数据和属性) |
| O_DSYNC |
同上(仅数据) |
| O_TRUNC |
存在,且以只写或读写打开,则将其长度截0 |
文件描述符操作
在 <fcntl.h> 头文件中定义。作为 fcntl 函数的第二个参数,这里只列出部分重要的,其他见PDFp85
| 代号 |
意义 |
| F_DUPFD |
复制文件描述符fd。新文件描述符作为函数值返回 |
| F_GETFD |
获取fd |
| F_SETFD |
设置fd,新值为 fcntl 第三个参数 |
| F_GETFL |
获取fd的文件状态标志作为函数值返回 |
| F_SETFL |
设置文件状态标志,新值为 fcntl 第三个参数 |
lseek
无法设置偏移的情况
测试标准输入是否可以设置偏移量。管道、FIFO或网络套接字返回为-1,并将errno设置为 ESPIPE
1 2 3 4 5 6 7 8 9 10 11 12
| #include <stdlib.h> #include <unistd.h> #include <stdio.h>
int main(void) { if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1) printf("cannot seek\n"); else printf("seek ok\n"); exit(0); }
|
1 2 3 4 5
| > ./a.out < /etc/passwd seek ok
> cat /etc/passwd | ./a.out cannot seek
|
创建具有空洞的文件
文件的偏移量可以大于文件的当前长度。位于文件中但没有写过的字节都读为0。
文件中的空洞并不要求在磁盘上占用存储区。从 原文件尾部 和 新开始写位置 之间的部分不需要分配磁盘块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <apue.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h>
char buf1[] = "abcdefghij"; char buf2[] = "ABCDEFGHIJ";
int main(void) { int fd;
if ((fd = creat("hole.txt", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) < 0) err_sys("creat error");
if (write(fd, buf1, 10) != 10) err_sys("buf1 write error");
if (lseek(fd, 16384, SEEK_SET) == -1) err_sys("lseek error");
if (write(fd, buf2, 10) != 10) err_sys("buf2 write error");
exit(0); }
|
使用 od 查看文件中实际的内容
输出如下
1 2 3 4 5
| 0000000 a b c d e f g h i j \0 \0 \0 \0 \0 \0 0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 * 0040000 A B C D E F G H I J 0040012
|
fcntl
根据指令执行文件控制操作
下列程序指定文件描述符,并根据该描述符打印其所选择的文件标志说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include <apue.h> #include <fcntl.h>
int main(int argc, char *argv[]) { int val;
if (argc != 2) err_quit("usage: a.out <descriptor#>");
if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0) err_sys("fcntl error for fd %d", atoi(argv[1]));
switch (val & O_ACCMODE) { case O_RDONLY: printf("read only"); break;
case O_WRONLY: printf("write only"); break;
case O_RDWR: printf("read write"); break;
default: err_dump("unknown access mode"); }
if (val & O_APPEND) printf(", append"); if (val & O_NONBLOCK) printf(", nonblocking"); if (val & O_SYNC) printf(", synchronous writes");
putchar('\n'); exit(0); }
|
输出如下
1 2 3 4 5 6 7 8 9 10 11 12
| > ./a.out 0 < /dev/tty read only
> ./a.out 1 > temp.foo > cat temp.foo write only
> ./a.out 2 2>>temp.foo write only, append
> ./a.out 5 5<>temp.foo read write
|
增删文件描述符
新增
逻辑或
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <apue.h> #include <fcntl.h>
void set_fl(int fd, int flags) { int val; if ((val = fcntl(fd, F_GETFL, 0) < 0) err_sys("fcntl F_GETFL error");
val |= flags;
if (fcntl(fd, F_SETFL, val) < 0) err_sys("fcntl F_SETFL error"); }
|
删除
反码的逻辑与
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <apue.h> #include <fcntl.h>
void set_fl(int fd, int flags) { int val; if ((val = fcntl(fd, F_GETFL, 0) < 0) err_sys("fcntl F_GETFL error");
val &= ~flags;
if (fcntl(fd, F_SETFL, val) < 0) err_sys("fcntl F_SETFL error"); }
|
/dev/fd
Linux下,/dev/fd 被链接至 /proc/self/fd
本章涉及到的所有系统调用
| 功能 |
名称 |
PDF页面 |
| 打开文件 |
open、openat、creat |
70 |
| 关闭文件 |
close |
73 |
| 偏移内容 |
lseek |
73 |
| 读数据 |
read |
77 |
| 写数据 |
write |
77 |
| 复制文件描述符 |
dup、dup2 |
83 |
| 改变已打开文件属性 |
fcntl |
85 |
文件共享
打开文件的内核数据结构

多进程打开同一文件
