今天看啥  ›  专栏  ›  西门早柿

Linux I/O

西门早柿  · 简书  ·  · 2021-02-09 21:38

文章预览

文件 I/O

  • 大多数只需要用到五个函数:open, read, write, lseek, close
    • lseek 也可以用来确定涉及的文件是否可以设置偏移量,管道、FIFO、socket 在调用 lseek 时会返回 -1,并将 errno 置为 ESPIPE。这也体现了块 I/O 和字符 I/O 的区别。是否允许随机读写。
    • open 函数在内存中创建两个结构,一个是 fd 相关, 一个是 inode 相关。一个文件对应一个 inode,但可以对应多个 fd(一一对应多个进程)。文件被删除,但对应的文件还被进程使用,inode 没有被释放,则该文件占用的空间还被计算到硬盘使用空间里。
  • 不带缓冲,直接调用系统调用,与库函数中的 I/O 函数不同
    • 操作系统的页缓存一般是默认使用的,可以指定对应的参数,来决定是否直接读取磁盘内容。
    • 但硬盘读取的最小单位是块,读取一个字节,内核也会读一块到内存里,一般块的大小最小为 512B.
  • 多进程间如何共享文件
    • 进程表项、文件表项、i 节点,可以会有多个文件描述符项指向同一文件表项,dup 函数和 fork 函数都属于此类
    • 追加文件属于原子操作,但 lseek 不是,有的系统包含原子性的 lseek I/O, pread 和 pwrite。检查文件是否存在和创建文件这两个操作是作为一个原子操作来进行的,redis 中的 SETNX 调用也是类似的原子操作。
  • dup, fcntl, sync, fsync, iotcl 函数
    • int dup(int fd);返回 fd 的复制,当前可用的最小 fd.
    • int dup2(int fd, int fd2);返回 fd 的复制 fd2, 若 fd2 已打开,则先关闭。
    • 可以使用 fcntl 来复制文件描述符,但是不是一个原子操作。
    • sync 将所有修改过的块排入写队列,然后返回,不等待实际写磁盘结束;fsync 只对由文件描述符指定的一个文件起作用,并且等待写磁盘操作结束后才返回。
    • fcntl,使用 fcntl,只需要知道打开文件的描述符,就可以修改描述符的属性。
  • 文件描述符,与进程相关,0, 1, 2 为固定
  • unix 系统的文件类型
    • 普通文件
    • 目录文件
    • 块特殊文件,提供对设备(如磁盘)的带缓冲的访问,每次访问以固定长度进行
    • 字符特殊文件,系统中的所有设备要么是字符特殊文件,要么是块特殊文件
    • FIFO,命名管道
    • 套接字
    • 符号链接

标准 I/O

  • 标准 I/O 与文件 I/O 的区别
    • 标准 I/O 是建立在文件 I/O 之上的带缓冲的 I/O,操作对象是 FILE 文件指针,文件 I/O 操作的对象是文件描述符,而且不带用户空间的缓冲
    • 标准 I/O 带有用户空间的缓存,其主要目的是提高效率,减少系统调用的次数
    • 全缓冲、行缓冲和无缓冲
    • 当以读和写类型打开一个文件时,具有以下限制:(由于缓冲区的存在,你并不知道现在的读写位置在哪里)
      • 如果中间没有 fflush、fseek、fsetpos 或者 rewind,则在输出的后面不能直接跟随输入
      • 如果中间没有 fseek、fsetpos 或者 rewind,或者一个输入操作没有到达文件尾端,则在输入操作之后不能直接跟随输出
    • 标准 I/O 的不足:fio sfio ASI

高级 I/O

  • I/O 的几个阶段:数据到达 -> 准备好读取(读入内核缓存) -> 应用程序读取,对于写 I/O 也是同样的过程
    • 我们通常说的 I/O 指的是应用程序读取内核准备好的数据这一过程,同步/异步,阻塞/非阻塞指的也是这个阶段,这是两个不同的概念
    • 阻塞与非阻塞的主要区别在于是否等待数据准备好,应用程序在读/写 fd 时,如果对应的 fd 并没有准备好,等待准备好再进行读/写,这就是阻塞 I/O,如果不等待,直接返回错误,这就是非阻塞 I/O
    • 数据到达 -> 准备好读写,这个过程是由内核和驱动程序完成的,是一个异步的过程
    • 异步 I/O 和同步 I/O 指的是应用程序在读取准备好的数据这个过程是同步还是异步的,我们通常使用的都是同步 I/O,异步 I/O 很少使用。同步 I/O 指的是应用程序在读取数据期间一直是阻塞的,至少读取数据的这个线程是阻塞的,而异步 I/O 中,读取数据这个过程是由内核异步完成的,在完成 I/O 之后通知对应的应用程序。
    • 关于同步和异步的另外一种是数据准备好这个条件的判断是同步还是异步的
    • 经常使用的 select/poll/epoll 等使用的都是同步非阻塞 I/O
  • 记录锁
    • 可以对一个文件中的全部或者部分加锁,记录锁除了数据库系统中,平常的工作用的比较少
  • select/poll/epoll
    • I/O 多路转接的套路
      • 告诉内核
        • 我们所关心的描述符
        • 对于每个描述符我们关心的条件
        • 愿意等待多长时间
      • 内核返回
        • 已准备好的描述符的总数量
        • 对于读写或异常这三个条件中的每一个,哪些描述符已准备好
    • 不论是 select/poll/epoll,所做的事情都是一样的,只不过是实现方式有些差异
  • 存储映射 I/O
    • 以页为单位将文件映射到内存中,可以在不使用 read 和 write 的情况下执行 I/O
    • 映射区一般位于堆和栈之间

网络设备为什么没有抽象成文件,而是 socket?

大部分的设备都被抽象成设备文件,主要分为两类。

  • 字符设备,按字/字节读取,顺序读取,一般不支持随机读取,例如调制解调器。
  • 块设备,数据的读写只能按块(通常是 512B)的倍数进行。一般与操作系统的文件缓存配合使用,应用程序不会直接操作块设备。
    但是网卡属于特殊情况,无法利用设备文件来访问,原因在于网络通信期间,数据打包到了各种协议层中,用户直接读取数据包是没有意义的,不可能由用户来解析网络协议。内核必须要处理数据包的解包和封包。
    为支持通过文件接口处理网络连接,linux 使用了源于 BSD 的套接字抽象。套接字可以看作是应用程序、文件接口、内核的网络实现之间的代理。

epoll 为什么不能监听普通的文件描述符?

linux 中针对文件描述符的操作有一个大的操作集,但并非所有类型的文件描述符都实现了整个操作集,各个类型的文件描述符操作只实现了其中的一部分。其中有一个操作是 poll 函数,poll 用来表示这个文件描述符是否可读或者可写。只有实现了这个函数的文件描述符才可以利用 epoll 来监听,普通文件是没有实现这个接口的。
这个设计也有它的道理,普通文件与管道、socket 不同,在任何情况下都应该是可读可写的。因此实现 poll 是没有意义的。

………………………………

原文地址:访问原文地址
快照地址: 访问文章快照
总结与预览地址:访问总结与预览