APUE读书笔记(3.0) ——第三章 文件I/O

第三章 文件I/O
     这一章讲的是 Unix 的基本 I/O 函数:open, write, read, close, lseek、dup、fcntl 等。它们又被称为不带缓冲的 I/O,这是因为这些函数直接执行系统调用,而不在进程地址空间中另外开辟缓冲区。

一、文件描述符file descriptor
     1、 文件描述符是对文件的引用,本身是个 int 类型的数值,而且时非负的。它的取值在进程内是唯一且循环使用的。

     2、文件描述符 0、1、2 (通常使用<unistd.h>中定义的 STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO) 则用作进程的标准输入文件、标准输出文件和标准出错文件。
     标准输入、标准输出和标准出错对应的设备文件注册在目录/dev 中,文件名分别为 stdin, stdout, stderr。使用命令"ls -­l"可以发现,它们实际上分别是指向/dev/self/fd/0, /dev/self/fd/1, /dev/self/fd/2 的软链接。
     • 虚拟目录/proc/self/fd 中记录了当前进程所打开的文件描述符。通过命令"ls ­-l"可以看到这些文件描述符分别引用了系统中哪些文件(软链接的目标)。可以看到文件描述符 0、1、2 会指向 tty 或者 pipe 之类的设备,这说明这些进程是和这些设备进行数据读/写的。还可以看到对于守护进程,0、1、2 都是链接到/dev/null 的,这说明守护进程不会跟任何的接口进行交互。



二、打开文件:open 函数
     #include <fcntl.h>
     int open(const char *filename, int oflag);
     int open(const char *filename, int oflag, mode_t mode);
     1、
该函数以 oflag 指定的方式打开字符串 filename 指定的文件,成功后返回 filename 对应的文件描述符,失败时返回­1,并设置 errno 指代失败原因(例如:EACCES——Permission denied)。
          1.1 oflag 包括了 O_RDONLY(以只读方式打开)、 O_WRONLY(以只写方式打开)、 O_RDWR(以读写方式打开),
               这三个标志只能使用一个。否则使用例如 O_RDONLY |  O_WRONLY |  O_RDWR 这样的方式打开文件,在编译时可以通过甚至不会发出警告(我在 gcc 4.2,使用­Wall 选               项时看到也不会有警告),但此时读写方式是不可预料的;
          1.2 除了读写方式标志外,oflag 还可以通过按位或运算方式同时加入其它标志。包括 O_APPEND(写时追加到尾端)、 O_CREAT(文件不存在的话则创建,否则忽略此标志)、 O_EXCL(只用于与 O_CREAT结合,此时文件若已存在 open 调用将失败)、 O_TRUNC(用写标志打开且文件存在时将文件长度截为0)、 O_NONBLOCK(以非阻塞方式打开文件,如果要求的读写操作不能马上执行的话立即返回失败,常用于管道、字符终端等特殊文件);
          1.3还包括三个 POSIX 可选的同步标志: O_DSYNC、O_RSYNC、O_SYNC
               • O_DSYNC与O_SYNC的区别:
                    仅当文件属性需要更新以后反应数据变化(例如,更新文件大小以反应文件中包含了更多的数据)时,O_DSYNC标志才影响文件属性,而O_SYNC则保证数据和                    属性总是同步更新。例如,当文件用O_DSYNC打开时,重写文件后时间属性不会同步更新,而O_SYNC无论有没有操作都更新。         
               对于 Linux,三个标志的含义都与 O_SYNC 这个标志相同,使用此标志时,write(2)操作将阻塞到内核将内容真正同步到设备,文件在这之前将一直保持打开。

     2、open返回的文件描述符一定是最小的未用描述符数值
          有的程序可以先关闭标准输出(描述符为1),然后打开另一个文件,这个时候新的文件描述符一定为1
     3、文件名 超出系统的长度限制后,有些系统会 报错误,有的会 截断名字(这会引发致命错误,不仅影响到新文件的名字,还有可能重名)

     4、mode 为文件创建权限,与进程 EUID 的 umask 进行“或”操作成为文件的权限位。熟悉 chmod(1)命令则自然知道其具体用法。



三、创建新文件:creat 函数
     #include <fcntl.h>
     int creat(const char *filename, mode_t mode);
     据记载这个creat的名字确实是当年实现时的拼写错误,而一直被后世沿袭。它以只写方式创建并打开一个新文件,如果文件已存在,则文件被截短为0。相当于执行了open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);不过以前不支持这种按位且的方式,所以才有creat



四、关闭文件:close 函数
     #include <unistd.h>
     int close(int filedes);
     1、 关闭指定的文件描述符。同时,如果进程在此文件上加有记录锁,将释放。
     2、 在进程终止时,内核将自动关闭进程打开的文件。



五、定位文件:lseek 函数
     #include <unistd.h>
     off_t lseek(int filedes, off_t offset, int whence);
     1、lseek 执行时,将文件 filedes 的当前读写位置更改到相对 whence 指定的位置 offset 处的地方。

     2、whence 包括了 SEEK_SET(文件开始)、SEEK_END(文件末尾)、SEEK_CUR(当前),后两者的offset 可以是负数。offset 的类型 off_t 通常定义为一个机器字的长度(一般 typedef 自 long 类型,因为根据ISO C,long 在任何机器上都是和机器字的长度相同的。对于 32 位平台则为 4 个字节)。lseek 成功时将返回相对于文件开始处的偏移量(可能是负数),失败返回­1 并设置 errno。在文件是FIFO、管道或者套接字时,lseek 将失败并设置 errno 为 ESPIPE(Illegal seek)。lseek 只更改进程打开文件的状态,并不会引起 I/O 操作。

     3、程序清单3-1(书P51),演示了不同输入的不同结果
     ./a.out < /etc/motd
          # 返回seek OK
          # 原因:通过 I/O 重定向,将文件作为 a.out 的标准输入,STDIN_FILENO 对应的是一个文件,可以 seek
     cat < /etc/motd | ./a.out                     
          # 返回cannot seek
          # 原因:通过 I/O 重定向,将文件作为 cat 的标准输入,由 cat 输出,再通过管道作为 a.out 的输入,因为a.out 的输入是管道,所以没法 seek
     4、有的lseek返回负数,因此应该通过测试是否返回-1,而不是<0来判断函数是否成功;
     5、偏移量大于文件长度时,会产生空洞(全为零),空洞不占用磁盘



六、读文件数据到缓冲区(输入操作):read 函数
     #include <unistd.h>
     ssize_t read(int filedes, void *buf, size_t nbytes);
     read 按指定的字节数 nbytes 从文件 filedes 的当前位置处读取数据,输入到缓冲区 buf 中。其返回值:
          为正数时:为实际读取的字节数,注意读取到的字节数不一定为要求的数字
          为 0 时:已经读到 EOF;
          为­1 时:调用失败,同时 errno 被设置。
     如果文件打开时未指定 O_NONBLOCK 标志,对其的 read 调用可能发生阻塞等待可读。阻塞时如果进行了信号捕捉,read 将直接失败。
     注意参数 nbytes 的类型为无符号的 size_t(即必须为正整数),而返回值是有符号的 ssize_t。



七、从缓冲区写数据到文件(输出操作):write 函数
     #include <unistd.h>
     ssize_t write(int filedes, const void *buf, size_t nbytes);
     write 按指定的字节数 nbytes 从 buf 处取数据,输出到文件 filedes 的当前位置处,如果已经到文件末尾,将增加文件长度并在最后添加 EOF 标志。其返回值:
          为正数时:为实际写入的字节数,
          为­1 时:函数出错,同时 errno 被设置。
     read 和 write 操作一次写入数据的大小将会影响其 I/O 效率,通常按文件系统的块大小(文件 stat 结构的 st_blksize)设置



八、UNIX文件共享机制
     1、内核使用三种数据结构表示打开的文件,分别是:
          a、每个进程在进程表中记录打开文件的描述符表,这种安排使每个进程都有各自对该文件的当前偏移量
               每项的内容:(fd, file pointer)
          b、内核为所有打开文件维持一张文件表
               每项的内容:(文件状态标志(读、写、添加、同步、非阻塞等等), 当前文件偏移量, v-node pointer)
          c、每个打开文件都有一个v-node结构
               每项的内容:(v-node信息, i-node信息, 当前文件长度)
         

九、定位同时读写文件的原子操作:pread 和 pwrite 函数
     #include <unistd.h>
     ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);
     ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset);
     这两个函数先将文件位置定位到距开始 offset 处,然后对其按给定参数进行读/写。这两个步骤是原子操作,这意味这要不这些步骤一次性全部执行,要不就不执行。如果不是原子操作,则可能会由于内核调度或者信号处理等原因,使其他进程插到几个步骤之间更改所操作对象的状态,而引起后续操作发生意外。



十、复制文件描述符:dup 和 dup2 函数
     #include <unistd.h>
     int dup(int filedes);
     int dup2(int filedes, int filedes2);
     1、dup 函数使用一个当前进程中可用的最小文件描述符引用 filedes 所引用的文件,这个新的文件描述符的状态(打开标志、模式、当前位置等)和 filedes 相同。
          若成功,返回这个新的文件描述符。
          失败时返回­1(例如 filedes 不存在)并设置 errno。

     2、dup2 可以直接指定 dup 中新的文件描述符的值为 filedes2,如果 filedes2 已经打开,则先原子的关闭之。如果 filedes==filedes2 且存在时则直接返回文件描述符而不执行关闭。
          失败时也返回­1 并设置errno。
          dup2 常用于输入/输出重定向以实现管道操作。例如
               dup2(filedes, STDOUT_FILENO);
               则重定向标准输出到 filedes,相当于在 shell 中使用重定向操作符执行了"> file"。
          dup2 的功能也可以用 fcntl(2)实现,但后者不能实现为原子操作且某些 errno 不同。



十一、更新到实际文件:sync、fsync 和 fdatasync 函数
     通常内核为了考虑吞吐效率等情况,write 调用成功后并不马上将数据写到磁盘,而是放在磁盘的缓存区中并通过缓存区交换算法(例如最近最少使用)不定期的同步数据到磁盘,或者通过守护进程定时进行数据同步,也可以通过调用 sync 使其马上进行数据同步。
     #include <unistd.h>
     int fsync(int filedes);     //立即将内核缓冲的数据送到磁盘中的写队列,等待到磁盘写结束时才返回。。
     int fdatasync(int filedes);     //类似 fync,但除了同步数据外还同步文件的属性(例如 stat 结构的 st_ctime 等)。
     void sync(void);     //立即将内核缓冲的数据送到磁盘中的写队列,并直接返回。



十二、已打开文件状态的更改:fcntl 函数
     #include <fcntl.h>
     int fcntl(int filedes, int cmd, /* int arg*/);
     cmd 包括了以下取值:
          F_DUPFD: 复制文件描述符并返回新的文件描述符(同 dup)
          F_GETFD/F_SETFD 获取/重设文件描述符的标志(FD_CLOEXEC,用于指出执行 exec(3)调用时是否关闭此文件)
          F_GETFL/F_SETFL 获取/重设文件描述符的打开状态标志(O_RDWR, O_NONBLOCK 等)
          F_GETOWN/F_SETOWN 获取/重设捕捉信号 SIGIO(异步 I/O 时,若 I/O 已经可用则产生此信号)和信号 SIGURG(收到带外数据时产生此信号)的 PID;
          F_GETLK/F_SETLK获取/设置记录锁,在并发场合用于同步文件的操作时序。arg 为根据 cmd 进行不同取值的相关参数,略。



十三、ioctl 函数
     对设备进行指定的操作,适用于 read、write、lseek、fcntl 等函数不能完成的其它功能。对于Linux,手册中称其用于操作 STREAMS 设备,但 Linux 下几乎用不上 STREAMS 设备。其它平台对 ioctl函数的用途不尽相同,略。


十四、/dev/fd
     /dev/fd/0相当于fd为0的文件,它们共用一份文件表,这样做的目的是为了在终端中可以以路径的形式操作文件描述符
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值