文件 IO

记录书中的重要知识和思考实践部分

Unix 每个文件都对应一个文件描述符(file descriptor),为一个非负整数,一个文件可以有多个fd, 后面所有与文件(设备,套接字等)有关操作都是围绕这个fd来的。

在shell中 < > 都为重定向符号,前者为重定向输入,后者为输出。

文件的打开

  1. #include <fcntl.h>
  2. int open(const char *path, int flags, ... /* mode_t mode */);
  3. 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() 函数中关闭文件描述符的标志,这个后面会看到。

文件偏移量

  1. #include <unistd.h>
  2. int lseek(fd, off_t off, int wheren);

我们使用 lseek 函数的时候,比如lseek(fd, 10, SEEK_END); 这样会导致文件的偏移量增加而文件的大小仍然不变,

但是当再使用 write 函数向文件中写入数据时,直接给个例子更好理解, 文件 foo 中原有数据为123。

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

可以看到产生两个\0,产生了空洞文件。

使用以下的方式得到当前的文件偏移量。

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

原子操作

操作是不可中断的,如 read write 系统调用,可能读取或者写入的数据少于我们要的数量,但是在函数调用这个事件上要么直接成功要么直接失败。

新文件的读写可以使用 open 函数的 O_CREAT 标志来创建再读写,此为原子操作;

还有一种方式是使用 creat 函数创建文件后再用 open 打开,这里有两个调用,当进行进程切换时候,其他进程对此文件进行处理,产生意向不到的错误。

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

  1. dup2(fd, fd2);
  2. 等效于
  3. close(fd2);
  4. fcntl(fd, F_DUPFD, fd2);

函数 fcntl

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

  1. 函数原型
  2. #include <fcntl.h>
  3. 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)和文件状态标志可用如下函数

  1. int set_cloexec(int fd)
  2. {
  3. int val = fcntl(fd, F_GETFD, 0);
  4. val |= FD_CLOEXEC;
  5. return fcntl(fd, F_SETFD, val);
  6. }
  7. int set_fl(int fd, int flags)
  8. {
  9. int val = fcntl(fd, F_GETFL, 0);
  10. val |= flags;
  11. return fcntl(fd, F_SETFL, val);
  12. }

文件描述符标志 FD_CLOEXEC

在前面提到,open 函数用使用 O_CLOEXEC 标志会是打开的文件描述符在exec打开的进程中关闭,可以达到进程间的文件隔离的效果。

  1. #ifdef _CLOEXEC
  2. open("./foo", O_CLOEXEC | O_RDWR);
  3. #else
  4. open("./file.hole", O_RDWR);
  5. #endif
  6. execl("./rdwr", "rdwr", "10000", NULL);

执行execl后进程是 rdwr,在编译命令里面加入 -D_CLOEXEC 选项来看变化



可以发现没有占用foo,不加入-D_CLOEXEC

这样也存在一个问题,在另外一个进程里关闭了文件描述符,就须注意当前进程后面不能再对文件进行操作了。

上面是 open 函数,我们同样可以用fcntl来改变文件的描述符标志,直接调用上面的 set_cloexec(fd) 也可以达到这个效果;

  1. 5 #include <fcntl.h>
  2. 6 #include "apue.h"
  3. 7
  4. 8 // int fcntl(int fd, int cmd, ... /* int arg */);
  5. 9
  6. 10 // F_DUPFD F_DUPFD_CLOEXEC
  7. 11 // return new fd.
  8. 12 int dupfd(int fd) {
  9. 13 printf("fcntl_dup: %d\n", fd);
  10. 14 int new_fd = fcntl(fd, F_DUPFD, 4); // new_fd 应该是 fd + 1
  11. 15 if (new_fd < 0)
  12. 16 err_sys("fcntl F_DUPFD error\n");
  13. 17 printf("F_DUPFD: %d\n", new_fd);
  14. 18 return new_fd;
  15. 19 }
  16. 20
  17. 21 // F_GETFD
  18. 22 int getfd(int fd) {
  19. 23 int val = fcntl(fd, F_GETFD);
  20. 24 if (val < 0)
  21. 25 err_sys("fcntl F_GETFD error");
  22. 26 printf("getfd %d\n", val);
  23. 27 if (val & FD_CLOEXEC) // 这里 0,所以只会走 30 行的
  24. 28 printf("getfd FD_CLOEXEC\n");
  25. 29 else
  26. 30 printf("getfd not FD_CLOEXEC\n");
  27. 31
  28. 32 close(val);
  29. 33 return val;
  30. 34 }
  31. 35
  32. 36 // F_SETFD
  33. 37 int setfd(int fd) {
  34. 38 int val = set_cloexec(fd);
  35. 39 printf("setfd %d\n", val);
  36. 40 // execl("./rdwr", "rdwr", "123", NULL);
  37. 41 return val;
  38. 42 }
  39. 43
  40. 44 void setfl(int fd, int flags) {
  41. 45 set_fl(fd, flags);
  42. 46 }
  43. 47
  44. 48 int getfl(int fd) {
  45. 49 int val;
  46. 50 if ((val = fcntl(fd, F_GETFL)) < 0)
  47. 51 err_sys("fcntl F_GETFL error");
  48. 52
  49. 53 switch (val & O_ACCMODE) {
  50. 54 case O_RDONLY:
  51. 55 printf("read only\n");
  52. 56 break;
  53. 57 case O_WRONLY:
  54. 58 printf("write only\n");
  55. 59 break;
  56. 60 case O_RDWR:
  57. 61 printf("read write\n");
  58. 62 break;
  59. 63 default:
  60. 64 err_dump("unkown access mode");
  61. 65 }
  62. 66 if (val & O_APPEND)
  63. 67 printf(", append");
  64. 68 if (val & O_NONBLOCK)
  65. 69 printf(", nonblocking");
  66. 70 if (val & O_SYNC)
  67. 71 printf(", synchronous writes");
  68. 72 return val;
  69. 73 };
  70. 74
  71. 75 int main(int argc, char *argv[]) {
  72. 76 int fd;
  73. 77
  74. 78 if ((fd = open("./foo", O_RDWR | O_CREAT)) < 0)
  75. 79 err_sys("open error");
  76. 80 dupfd(fd);
  77. 81 getfd(fd);
  78. 82 setfd(fd);
  79. 83 getfl(fd);
  80. 84 // setfl(fd, O_APPEND); // 从文件为开始操作
  81. 85 if (write(fd, "jinpi", 5) < 0)
  82. 86 err_sys("write error");
  83. 87 }

APUE 文件IO的更多相关文章

  1. (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  2. APUE学习笔记3_文件IO

    APUE学习笔记3_文件IO Unix中的文件IO函数主要包括以下几个:open().read().write().lseek().close()等.这类I/O函数也被称为不带缓冲的I/O,标准I/O ...

  3. C3 文件IO:APUE 笔记

    C3:文件IO 1 引言 本章描述的函数被成为不带缓冲的IO,涉及5个函数:open.read.write.lseek.close. 文件控制:dup.sync.fsync.fdatasync.fcn ...

  4. 《嵌入式linux应用程序开发标准教程》笔记——6.文件IO编程

    前段时间看APUE,确实比较详细,不过过于详细了,当成工具书倒是比较合适,还是读一读这种培训机构的书籍,进度会比较快,遇到问题时再回去翻翻APUE,这样的效率可能更高一些. <嵌入式linux应 ...

  5. 无缓冲文件IO和目录操作

    引言 在后台开发中,对于文件I/O我们通常不使用C语言封装的fopen.fread.fwrite标准I/O,而是直接使用Linux提供的系统调用函数.因为这些系统调用没有使用用户缓冲区,我们直接与内核 ...

  6. 标准io与文件io

    A: 代码重复: 语句块1: while(判断) { 语句块2: 语句块1: } 上面可以改写为: while(1) { 语句块1: if(判断) break: 语句块2: } B: 标准IO和文件I ...

  7. 文件IO函数和标准IO库的区别

    摘自 http://blog.chinaunix.net/uid-26565142-id-3051729.html 1,文件IO函数,在Unix中,有如下5个:open,read,write,lsee ...

  8. 转 漫谈linux文件IO

    在Linux 开发中,有几个关系到性能的东西,技术人员非常关注:进程,CPU,MEM,网络IO,磁盘IO.本篇文件打算详细全面,深入浅出.剖析文件IO的细节.从多个角度探索如何提高IO性能.本文尽量用 ...

  9. Java文件IO操作应该抛弃File拥抱Paths和Files

    Java7中文件IO发生了很大的变化,专门引入了很多新的类: import java.nio.file.DirectoryStream;import java.nio.file.FileSystem; ...

随机推荐

  1. Bing必应地图中国API-放大与平移

    Bing必应地图中国API-放大与平移 2011-05-24 14:26:32|  分类: Bing&Google|字号 订阅     有些时候我们不希望通过默认的控制栏来控制地图,而是希望能 ...

  2. SepicalJudge

    原文:http://www.cnblogs.com/chouti/p/5752819.html Special Judge:当正确的输出结果不唯一的时候需要的自定义校验器 首先有个框架 #includ ...

  3. python time 时间模块

    time():获取当前系统的时间戳ctime():以人类可读的方式打印当前系统时间sleep():接受一个参数,表示休眠时间 #!/usr/bin/env python #coding:utf8 im ...

  4. GitHub上README.md教程(copy)

    [说明:转载于http://blog.csdn.net/kaitiren/article/details/38513715] 最近对它的README.md文件颇为感兴趣.便写下这贴,帮助更多的还不会编 ...

  5. matlab进入指定目录

    cd C:\Users\hui\Desktop\minepy\1\minepy-1.2.0\minepy-1.2.0\matlab

  6. css实现左边div固定宽度,右边div自适应撑满剩下的宽度

    (1)使用float <div class="use-float"> <div></div> <div></div> & ...

  7. Git简介(转载)

    转自:http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/00137396284551 ...

  8. git 删除本地仓库

    更新: 2017/06/27 修改格式,备注mac下的命令没测试过   windows: rm .git/ mac: sudo rm -rf .git/ 没验证

  9. 源码阅读之HashMap(JDK8)

    概述 HashMap根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的. HashMap最多只允许一条记录的键为null,允许多条记录 ...

  10. [C陷阱和缺陷] 第3章 语义“陷阱”

    第3章 语义"陷阱"     一个句子哪怕其中的每个单词都拼写正确,而且语法也无懈可击,仍然可能有歧义或者并非书写者希望表达的意思.程序也有可能表面上是一个意思,而实际上的意思却相 ...