1. 引言

  • Unix系统的大多数文件I/O只需用到5个函数:open、read、write、lseek以及close
  • 本章描述的函数经常被称为不带缓冲的I/O。术语不带缓冲指的是在用户的进程中对其不会自动缓冲,每个read和write都调用内核中的一个系统调用。但是,所有磁盘I/O都要经过内核的块缓存区(也称为内核的缓冲区高速缓存)。唯一例外的是对原始磁盘设备的I/O。

2. 文件描述符

  • 对于内核而言,所有打开的文件都通过文件描述符引用。文件描述符是一个非负整数,其变化范围是0~OPEN_MAX-1
  • 惯例:幻数0、1、2分别为符号常量STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO

3. 函数open、openat:创建或打开一个文件

  1. #include <fcntl.h>
  2. int open(const char *path, int oflag, ... /* mode_t mode */ );
  3. int openat(int fd, const char *path, int oflag, ... /* mode_t mode */ );
  4. Both return: file descriptor if OK, − on error
  • oflag参数

    1. 以下5个常量必须指定一个且只能指定一个
      O_RDONLY、O_WRONLY、O_RDWR、O_EXEC(只执行打开)、O_SEARCH(只搜索打开,应用于目录,尚未支持)
    2. 下列常量则可选
      O_APPEND、O_CLOEXEC、O_CREAT(需要指定第3个参数mode)、O_EXCL、O_DIRECTORY、O_NOFOLLOW、O_NONBLOCK、O_SYNC、O_TRUNC
  • open和openat函数返回的文件描述符一定是最小的未用描述符数值。这一点被某些应用程序用来在标准输入、标准输出或标准错误上打开新的文件。

  • fd参数把open和openat函数区分开,共有3种可能性

    1. path参数指定的是绝对路径名,则fd参数被忽略,openat函数相当于open函数
    2. path参数指定的是相对路径名,则fd参数指出了相对路径名在文件系统中的开始地址。fd参数是通过打开相对路径名所在的目录来获取
    3. path参数指定了相对路径名,而fd参数具有特殊值AT_FDCWD,则路径名在当前工作目录中获取
  • openat函数是POSIX.1最新版本中新增的一类函数之一,希望解决两个问题:

    1. 让线程可以使用相对路径名打开目录中的文件,而不再只能打开当前工作目录
    2. 可以避免time-of-check-to-time-of-use(TOCTTOU)错误
  • TOCTTOU错误的基本思想是:

    指计算机系统的资料与权限等状态的检查与使用之间,因为特定状态在这段时间已发生改变所产生的软件漏洞

  • 文件名和路径名截断

4. 函数creat:创建一个新文件

  1. #include <fcntl.h>
  2. int creat(const char *path, mode_t mode);
  3. Returns: file descriptor opened for write-only if OK, − on error
  • 等效于 open( path, O_WRONLY | O_CREAT | O_TRUNC, mode );
  • creat的一个不足之处是它以只写方式打开所创建的文件

5. 函数close

  1. #include <unistd.h>
  2. int close(int fd);
  3. Returns: if OK, − on error
  • 关闭一个文件时会释放该进程加在该文件的所有记录锁
  • 当一个进程终止时,内核自动关闭它所有的打开文件

6. 函数lseek:为一个打开文件设置偏移值

  1. #include <unistd.h>
  2. off_t lseek(int fd, off_t offset, int whence);
  3. Returns: new file offset if OK, − on error
  • 每个打开文件(通过open)都有一个与其关联的“当前文件偏移量”。它通常是一个非负整数(有可能为负),用于度量从文件开始处计算的字节数。
  • offse参数的解释与whence参数的值有关:SEEK_SET、SEEK_CUR、SEEK_END
  • 若lseek执行成功,返回新的文件偏移量,故可通过 lseek( fd, 0, SEEK_CUR ); 确定当前偏移量
  • 可用来确定所涉及的文件是否可以设置偏移量,管道、FIFO或网络套接字不可以,lseek返回-1,errno被设置为ESPIPE
  • 名字中的l是在引入off_t类型之前,offset参数和返回值都是long

7. 函数read

  1. #include <unistd.h>
  2. ssize_t read(int fd, void *buf, size_t nbytes);
  3. Returns: number of bytes read, if end of file, − on error
  • 读操作从文件的当前偏移处开始,在成功返回之前,该偏移量将增加实际读到的字节数
  • 多种情况下使得读到的字节数少于要求读的字节数
    1. 读普通文件时,到达文件尾端
    2. 从终端设备读时,通常一次最多读一行
    3. 从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数
    4. 从管道或FIFO读时,如若管道包含的字节少于所需的数量,那么read将只返回实际可用的字节数
    5. 从某些面向记录的设备(如磁带)读时,一次最多返回一个记录
    6. 当一信号造成中断,而已经读了部分数据量时

8. 函数write

  1. #include <unistd.h>
  2. ssize_t write(int fd, const void *buf, size_t nbytes);
  3. Returns: number of bytes written if OK, − on error
  • 其返回值通常与参数nbytes的值相同,否则表示错误。
  • 对于普通文件,写操作从文件的当前偏移处开始。如果在打开文件时,指定了O_APPEND选项,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处

9. I/O的效率

  • 大多数文件系统为改善性能都采用某种预读技术

10. 文件共享

  • 内核使用3种数据结构表示打开文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响

  • 左边:进程级的文件描述符表

    1. 文件描述符标志,目前只有一个FD_CLOEXEC
    2. 指向一个文件表项的指针
  • 中间:系统级的打开文件表:每次调用open打开一个文件新增一个文件表项(不同进程可打开同一个文件,导致多个文件表项)

    1. 文件状态标志(读、写、添写、同步、非阻塞等,受open时指定的oflag参数影响,也可通过fcntl函数指定FD_SETFL改变)
    2. 当前文件偏移量
    3. 指向该文件v节点表项的指针
  • 右边:文件系统级的i-node表

    1. i节点包含文件的相关信息,如文件的所有者、文件长度、指向文件实际数据块在磁盘上所在位置的指针等。
    2. Linux没有v节点,而是采用了一个通用i节点。无论是v节点还是通用i节点,它们都是指向一个与文件系统相关的i节点
  • 其他

    1. 完成write之后,文件表项的当前文件偏移量增加所写入的字节数。如果这导致当前文件偏移量超出了文件长度,则将i节点表项中的当前文件长度设置为当前文件偏移量
    2. 如果通过O_APPEND标志打开一个文件,则相应标志会设置到文件表项的文件状态标志中。每次执行写操作之前,当前文件偏移量会首先被设置为i节点表项中的文件长度
    3. lseek函数只修改文件表项中的当前文件偏移量,不进行任何I/O操作。
    4. 注意:文件描述符标志只作用于一个进程的一个文件描述符,而文件状态标志则作用于指向该文件表项的任何进程中的所有描述符

11. 原子操作

  • 先lseek再write,不等价于,指定了O_APPEND的write
  • 对open函数指定O_CREAT和O_EXCL

  • 函数pread、pwrite

  1. #include <unistd.h>
  2. ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
  3. Returns: number of bytes read, if end of file, − on error
  4. ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
  5. Returns: number of bytes written if OK, − on error
  • 调用pread相当于调用lseek后调用read,但又有区别:

    1. 调用pread时,无法中断其定位和读操作
    2. 不更新当前文件偏移量
  • 调用pwrite相当于调用lseek后调用write,也有类似的区别

12. 函数dup、dup2

  1. #include <unistd.h>
  2. int dup(int fd);
  3. int dup2(int fd, int fd2);
  4. Both return: new file descriptor if OK, − on error
  • dup返回的新文件描述符一定是当前可用文件描述符的最小数值
  • dup2可以用fd2参数指定新描述符的值

    1. 如果fd2已经打开,则先将其关闭
    2. 如果fd等于fd2,则dup2返回fd2,而不关闭它
    3. 否则,fd2的FD_CLOEXEC文件描述符标志就被清除,这样fd2在进程调用exec时是打开状态
  • dup(fd); 等价于 fcntl(fd, F_DUPFD, 0);

  • dup2(fd, fd2); 不完全等价于 close(fd2); fcntl(fd, F_DUPFD, fd2); 因为其非原子操作,且dup2和fcntl有一些不同的errno

13. sync、fsync、fdatasync

  • 传统的Unix系统实现在内核中设有缓冲区高速缓存或页高速缓存,大多数磁盘I/O都通过缓冲区进行。当我们向文件写入数据时,内核通常先将数据复制到缓冲区中,然后排入队列,晚些时候再写入磁盘。这种方式被称为延迟写
  1. #include <unistd.h>
  2. int fsync(int fd); // 只对fd指定的一个文件其作用,并且等待写磁盘操作结束后才返回
  3. int fdatasync(int fd); // 类似于fsync,但只影响文件的数据部分。而除数据之外,fsync还会同步更新文件的的属性
  4. Returns: if OK, − on error
  5. void sync(void); // 只是将所有修改过的块缓冲区排入写队列,然后就返回,不等待实际写磁盘操作结束

14. 函数fcntl:改变已打开文件的属性

  1. #include <fcntl.h>
  2. int fcntl(int fd, int cmd, ... /* int arg */ );
  3. Returns: depends on cmd if OK (see following), − on error
  • fcntl函数有以下5种功能

    1. 复制一个已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)
    2. 获取/设置文件描述符标志(cmd=F_GETFD或F_SETFD),某个进程的文件描述符
    3. 获取/设置文件状态标志(cmd=F_GETFL或F_SETFL),系统打开文件表中某个文件表项的文件状态标志(参见open函数的oflag参数)
    4. 获取/设置异步I/O所有权标志(cmd=F_GETOWN或F_SETOWN)
    5. 获取/设置记录锁(cmd=F_GETLK或F_SETLK)
  • 先通过GET获取得到val,再 val |= flags; 或 val &= ~flags; 最后通过SET将val设置到fd

15. 函数ioctl

  1. #include <unistd.h> /* System V */
  2. #include <sys/ioctl.h> /* BSD and Linux */
  3. int ioctl(int fd, int request, ...);
  4. Returns: − on error, something else if OK
  • 通常有另外一个参数,它常常是指向一个变量或结构的指针
  • 上面原型中只include了ioctl函数本身需要的头文件。通常,需要另外的设备专用头文件,如:终端I/O的ioctl命令都需要头文件termios.h

16. /dev/fd

  • 打开文件/dev/fd/n等效于复制描述符n(假设描述符n是打开的)
  • Linux实现中的/dev/fd是个例外,它把文件描述符映射成指向底层物理文件的符号链接,例如,当打开/dev/fd/0时,事实上正在打开与标准输入关联的文件

[wulin@localhost ~]$ ls /dev/fd/ -l
lrwx------. 1 wulin wulin 64 7月 29 10:28 0 -> /dev/pts/0
lrwx------. 1 wulin wulin 64 7月 29 10:28 1 -> /dev/pts/0
lrwx------. 1 wulin wulin 64 7月 29 10:28 2 -> /dev/pts/0
lr-x------. 1 wulin wulin 64 7月 29 10:28 3 -> /proc/5341/fd

[wulin@localhost ~]$ ls /dev/std* -l
lrwxrwxrwx. 1 root root 15 7月 29 2014 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx. 1 root root 15 7月 29 2014 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx. 1 root root 15 7月 29 2014 /dev/stdout -> /proc/self/fd/1

  • /dev/fd文件主要由shell使用,它允许使用路径名作为调用参数的程序,能用处理其他路径名的相同方式处理标准输入和输出。
  • filter file2 | cat file1 - file3 | lpr 首先cat读file1,然后读其标准输入(filter file2命令的输出),然后读file3
  • 如果支持/dev/fd,则可以删除cat对“-”的特殊处理,于是可以: filter file2 | cat file1 /dev/fd/0 file3 | lpr
  • 作为命令后参数的“-”特指标准输入或标准输出,这已被很多程序采用。但是这会带来一些问题,例如,如果用“-”指定第一个文件,那么看来就像指定了命令行的一个选项

原创文章,转载请注明出处:http://www.cnblogs.com/DayByDay/p/3890670.html

《Unix环境高级编程》读书笔记 第3章-文件I/O的更多相关文章

  1. [置顶] 文件和目录(二)--unix环境高级编程读书笔记

    在linux中,文件的相关信息都记录在stat这个结构体中,文件长度是记录在stat的st_size成员中.对于普通文件,其长度可以为0,目录的长度一般为1024的倍数,这与linux文件系统中blo ...

  2. unix环境高级编程-读书笔记与习题解答-第一篇

    从这周开始逐渐的进入学习状态,每天晚上都会坚持写c程序,并且伴随对这本书的深入,希望能写出更高质量的读书笔记和程序. 本书的第一章,介绍了一些关于unix的基础知识,在这里我不想去讨论linux到底是 ...

  3. unix 环境高级编程-读书笔记与习题解答-第二篇

    第四节 输入与输出 上次的笔记中写到的 open, read, write, lseek 以及close ,都是不带缓存的IO函数,这些函数都使用文件描述符进行工作. 上一篇笔记用到的 read(ST ...

  4. [置顶] 文件io(一)--unix环境高级编程读书笔记

    unix-like(后面以linux为例)系统中的文件操作只需要五个函数就足够了,open.close.read.write以及lseek.这些操作被称为不带缓存的io,这里有必要说一下带缓存和不带缓 ...

  5. unix 环境高级编程 读书笔记与习题解答第四篇

    第一章 第六节 第一小节 这一章没有程序设计和API方面的深入学习,而是注重介绍了unix操作系统中的原始数据类型和系统原型函数,错误处理方面的知识. ____unistd.h____ 该文件包含了u ...

  6. unix进程的环境--unix环境高级编程读书笔记

    http://blog.csdn.net/xiaocainiaoshangxiao/article/category/1800937

  7. unix环境高级编程 读书笔记

    1.上班业余时间把书下载下来,第一章读完了,但是程序只能回家运行啦!Fighting!

  8. Unix环境高级编程学习笔记——fcntl

    写这篇文正主要是为了介绍下fcntl,并将我自己在学习过程中的一些理解写下来,不一定那么官方,也有错误,希望指正,共同进步- fcntl: 一个修改一打开文件的性质的函数.基本的格式是 int fcn ...

  9. 《UNIX环境高级编程》笔记--UNIX标准化及实现

    1.UNIX标准化 1.1.ISO C 1989 年后期,C程序设计语言的ANSI(American National Standards Institute) 标准X3. 15 9-1989得到批准 ...

  10. 《UNIX环境高级编程》笔记——2.标准和实现

    随着UNIX各种衍生版本不断发展壮大,标准化工作就十分必要.其实干啥事都是这样,玩的人多了,必须进行标准化. 一.UNIX标准 1.1 ISO C(ANSI C) ANSI:Amerocan Nato ...

随机推荐

  1. How an Event Enters a Cocoa Application

    How an Event Enters a Cocoa Application An event is a low-level record of a user action that is usua ...

  2. 由一道面试题简单扩展 — setTimeout、闭包

    在一个前端公众号,看到这么一个号称简单的面试题: 1.以下程序输出什么? <script type="text/javascript"> function init() ...

  3. poj 2954 Triangle 三角形内的整点数

    poj 2954 Triangle 题意 给出一个三角形的三个点,问三角形内部有多少个整点. 解法 pick's law 一个多边形如果每个顶点都由整点构成,该多边形的面积为\(S\),该多边形边上的 ...

  4. KVM虚拟机相关步骤

    KVM是Kernel-based Virtual Machine的简称,是一个开源的虚拟化模块,该文档是基于CentOS 7.4环境操作的 一.操作系统安装 本文采用的是CentOS 7.4 1.查看 ...

  5. SUSE Linux Enterprise 11 SP4系统安装过程 字符界面

    首先开启虚拟机之后显示这个界面: (1) 进入之后显示下面界面,点击Installation安装. (2)进入Welcome界面,选择语言,默认Einglish(US)不需要改动:点击I Agree ...

  6. IDEA使用操作说明(自己总结)

    1.idea导入一个项目后,如何再导入另一个项目 首先,点击File-->new-->Module from Existing Sources...-->找到该项目所在位置,选中该项 ...

  7. 搭建ELK日志分析平台(上)—— ELK介绍及搭建 Elasticsearch 分布式集群

    笔记内容:搭建ELK日志分析平台(上)-- ELK介绍及搭建 Elasticsearch 分布式集群笔记日期:2018-03-02 27.1 ELK介绍 27.2 ELK安装准备工作 27.3 安装e ...

  8. POI 导入excel数据自己主动封装成model对象--代码分析

    上完代码后,对代码进行基本的分析: 1.主要使用反射api将数数据注入javabean对象 2.代码中的日志信息级别为debug级别 3.获取ExcelImport对象后须要调用init()方法初始化 ...

  9. Aizu - 2305 Beautiful Currency (二分 + DFS遍历)

    F. Beautiful Currency Time Limit: 5000ms Case Time Limit: 5000ms Memory Limit: 65536KB 64-bit intege ...

  10. [jQuery] 选择器和事件

    jQuery选择器 属性选择器 <p>p1</p> <span style="font-size:24px;"></span>< ...