• APUE 文件IO


    文件 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。

    int fd;
    if ((fd = open("./foo", O_RDWR)) < 0)
            err_sys("open error for foo");
    lseek(fd, 2, SEEK_END);
    write(fd, "zxh", 3);
    
    $ od -c foo
    0000000   1   2   3       z   x   h
    0000010
    

    可以看到产生两个,产生了空洞文件。
    使用以下的方式得到当前的文件偏移量。

    off_t off;
    off = lseek(fd, 0, SEEK_END);  // off 为当前的文件偏移量,在上例中为 5
    

    原子操作
    操作是不可中断的,如 read write 系统调用,可能读取或者写入的数据少于我们要的数量,但是在函数调用这个事件上要么直接成功要么直接失败。
    新文件的读写可以使用 open 函数的 O_CREAT 标志来创建再读写,此为原子操作;
    还有一种方式是使用 creat 函数创建文件后再用 open 打开,这里有两个调用,当进行进程切换时候,其他进程对此文件进行处理,产生意向不到的错误。

    上面是文件的创建操作,还有文件描述符的复制操作, 也是如此,对于单进程的效果是一样的,但是在多进程的时候就

    dup2(fd, fd2);
      等效于
    close(fd2);
    fcntl(fd, F_DUPFD, fd2);
    

    函数 fcntl
    可以改变文件的属性,算的上是个杂货箱吧。

    函数原型
    #include <fcntl.h>
    int fcntl(int fd, int cmd, ... /* arg */);
    

    功能:

    • 复制一个已有的描述符(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打开的进程中关闭,可以达到进程间的文件隔离的效果。

    #ifdef _CLOEXEC
    	open("./foo", O_CLOEXEC | O_RDWR);
    #else
    	open("./file.hole", O_RDWR);
    #endif
    
    execl("./rdwr", "rdwr", "10000", NULL);
    
    

    执行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
    ", 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
    ");
     17     printf("F_DUPFD: %d
    ", 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
    ", val);
     27     if (val & FD_CLOEXEC)  // 这里 0,所以只会走 30 行的
     28         printf("getfd FD_CLOEXEC
    ");
     29     else
     30         printf("getfd not FD_CLOEXEC
    ");
     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
    ", 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
    ");
     56             break;
     57         case O_WRONLY:
     58             printf("write only
    ");
     59             break;
     60         case O_RDWR:
     61             printf("read write
    ");
     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 }
    
    
  • 相关阅读:
    Confluence 6 在升级过程中查看合并日志
    Confluence 6 从 WIKI 标记整合到基于 XHTML 的存储格式
    Confluence 6 升级完成后的检查
    Confluence 6 超过当前许可证期限进行升级
    HDU2571--命运---DP
    HDU 2084 DP经典例子---数塔问题
    紫书第二章
    求组合数
    欧拉函数
    二分幂/快速幂
  • 原文地址:https://www.cnblogs.com/shuqin/p/9883996.html
Copyright © 2020-2023  润新知