文件 IO
记录书中的重要知识和思考实践部分
Unix 每个文件都对应一个文件描述符(file descriptor),为一个非负整数,一个文件可以有多个fd, 后面所有与文件(设备,套接字等)有关操作都是围绕这个fd来的。
在shell中 < > 都为重定向符号,前者为重定向输入,后者为输出。
文件的打开
#include <fcntl.h> int open(const char *path, int flags, ... /* mode_t mode */); int openat(int fd, const char *path, int flags, ... /* mode_t mode */);O_RDONLY, O_WRONLY, O_RDWR, O_EXEC, O_SEARCH 这五个参数(flags)是必须的,另外可选的参数里面 O_CLOEXEC 与 FD_CLOEXEC 都是在 exec() 函数中关闭文件描述符的标志,这个后面会看到。
文件偏移量
#include <unistd.h> int lseek(fd, off_t off, int wheren);我们使用 lseek 函数的时候,比如lseek(fd, 10, SEEK_END); 这样会导致文件的偏移量增加而文件的大小仍然不变,
但是当再使用 write 函数向文件中写入数据时,直接给个例子更好理解, 文件 foo 中原有数据为123。
可以看到产生两个\0,产生了空洞文件。
使用以下的方式得到当前的文件偏移量。
原子操作
操作是不可中断的,如 read write 系统调用,可能读取或者写入的数据少于我们要的数量,但是在函数调用这个事件上要么直接成功要么直接失败。
新文件的读写可以使用 open 函数的 O_CREAT 标志来创建再读写,此为原子操作;
还有一种方式是使用 creat 函数创建文件后再用 open 打开,这里有两个调用,当进行进程切换时候,其他进程对此文件进行处理,产生意向不到的错误。
上面是文件的创建操作,还有文件描述符的复制操作, 也是如此,对于单进程的效果是一样的,但是在多进程的时候就
dup2(fd, fd2); 等效于 close(fd2); fcntl(fd, F_DUPFD, fd2);函数 fcntl
可以改变文件的属性,算的上是个杂货箱吧。
功能:
复制一个已有的描述符(cmd = F_DUPFD 或 F_DUPFD_CLOEXEC)
获取/设置文件描述符标志(cmd = F_GETFD 或 F_SETFD)
获取/设置文件状态标志(cmd = F_GETFL 或 F_SETFL)
F_GETFL 只能用屏蔽字O_ACCMODE取得存取方式位
F_SETFL 更改的标志只有 O_APPEND,O_NONBLOCK,O_SYNC 和 O_ASYNC
获取/设置异步IO所有权(cmd = F_GETOWN 或 F_SETOWN)
获取/设置记录锁(cmd = F_GETLK、F_SETLK 或 F_SETLKW)
函数的返回值依赖参数而定,所有失败都是返回 -1,除特定参数,如下:
F_DUPFD、F_DUPFD_CLOEXEC,返回新的文件描述符,FD_CLOEXEC标志被清除
F_GETFD, 返回文件描述符标志,当前只定义了一个 FD_CLOEXEC
F_GETFL,返回文件状态标志,O_RDONLY等
F_GETOWN,返回一个进程组ID
成功返回 0。
设置文件描述符标志(FD_CLOEXEC)和文件状态标志可用如下函数
int set_cloexec(int fd) { int val = fcntl(fd, F_GETFD, 0); val |= FD_CLOEXEC; return fcntl(fd, F_SETFD, val); } int set_fl(int fd, int flags) { int val = fcntl(fd, F_GETFL, 0); val |= flags; return fcntl(fd, F_SETFL, val); }文件描述符标志 FD_CLOEXEC
在前面提到,open 函数用使用 O_CLOEXEC 标志会是打开的文件描述符在exec打开的进程中关闭,可以达到进程间的文件隔离的效果。
执行execl后进程是 rdwr,在编译命令里面加入 -D_CLOEXEC 选项来看变化
可以发现没有占用foo,不加入-D_CLOEXEC
这样也存在一个问题,在另外一个进程里关闭了文件描述符,就须注意当前进程后面不能再对文件进行操作了。
上面是 open 函数,我们同样可以用fcntl来改变文件的描述符标志,直接调用上面的 set_cloexec(fd) 也可以达到这个效果;
5 #include <fcntl.h> 6 #include "apue.h" 7 8 // int fcntl(int fd, int cmd, ... /* int arg */); 9 10 // F_DUPFD F_DUPFD_CLOEXEC 11 // return new fd. 12 int dupfd(int fd) { 13 printf("fcntl_dup: %d\n", fd); 14 int new_fd = fcntl(fd, F_DUPFD, 4); // new_fd 应该是 fd + 1 15 if (new_fd < 0) 16 err_sys("fcntl F_DUPFD error\n"); 17 printf("F_DUPFD: %d\n", new_fd); 18 return new_fd; 19 } 20 21 // F_GETFD 22 int getfd(int fd) { 23 int val = fcntl(fd, F_GETFD); 24 if (val < 0) 25 err_sys("fcntl F_GETFD error"); 26 printf("getfd %d\n", val); 27 if (val & FD_CLOEXEC) // 这里 0,所以只会走 30 行的 28 printf("getfd FD_CLOEXEC\n"); 29 else 30 printf("getfd not FD_CLOEXEC\n"); 31 32 close(val); 33 return val; 34 } 35 36 // F_SETFD 37 int setfd(int fd) { 38 int val = set_cloexec(fd); 39 printf("setfd %d\n", val); 40 // execl("./rdwr", "rdwr", "123", NULL); 41 return val; 42 } 43 44 void setfl(int fd, int flags) { 45 set_fl(fd, flags); 46 } 47 48 int getfl(int fd) { 49 int val; 50 if ((val = fcntl(fd, F_GETFL)) < 0) 51 err_sys("fcntl F_GETFL error"); 52 53 switch (val & O_ACCMODE) { 54 case O_RDONLY: 55 printf("read only\n"); 56 break; 57 case O_WRONLY: 58 printf("write only\n"); 59 break; 60 case O_RDWR: 61 printf("read write\n"); 62 break; 63 default: 64 err_dump("unkown access mode"); 65 } 66 if (val & O_APPEND) 67 printf(", append"); 68 if (val & O_NONBLOCK) 69 printf(", nonblocking"); 70 if (val & O_SYNC) 71 printf(", synchronous writes"); 72 return val; 73 }; 74 75 int main(int argc, char *argv[]) { 76 int fd; 77 78 if ((fd = open("./foo", O_RDWR | O_CREAT)) < 0) 79 err_sys("open error"); 80 dupfd(fd); 81 getfd(fd); 82 setfd(fd); 83 getfl(fd); 84 // setfl(fd, O_APPEND); // 从文件为开始操作 85 if (write(fd, "jinpi", 5) < 0) 86 err_sys("write error"); 87 }