• Linux IO模型


    简述

    IO操作不外乎读和写,但是不同场景对读写有不同的需求,例如网络中同时监控多个文件句柄,例如关键数据希望一路刷到存储设备而不是扔到cache就返回。

    怎么读,怎么写,等不等结果返回,是否等获取到数据才发返回,组成了不同的IO模型,分别适用于不同的场景。

    根据同步与异步,阻塞与非阻塞,可以把IO模型如下分类:

    同步/异步阻塞/非阻塞怎么理解呢?

    同步与异步,就是IO发起人是否等待数据操作结束。发起一次IO请求后,发起IO的进程死等操作结束才继续往下跑,就是同步。发起一次IO请求后,不等结束直接往下跑,就是异步。

    阻塞与非阻塞,就是没有数据时是否等到有数据才返回。如果没有数据,就让IO进程干等,直到有数据再返回,就是阻塞。如果看到没有数据,直接就返回了,就是非阻塞。

    什么情况下会没有数据呢?一个文件就这么大,除非读到文件末尾了,不然怎么会说没有数据呢?实际上,阻塞与非阻塞并不指磁盘上常规的文件读写,而是指socket或者pipe之类的特殊文件。这些特殊文件并没有明确意义上的大小,就是说,理论上他们的数据是无限大的,只要有人往里面写数据,你就能无限读出数据。这种特殊文件有数据的前提,就是有人往里面”灌“数据。如果你读的太快,别人写得太慢,就会出现池子里面没有数据的情况。这情况并不表示文件读到末端了,只表示暂时还没有数据。这时候你等呢?还是不等呢?这就是阻塞与非阻塞。

    如果是同步/异步是IO发起人是否主动想等待,阻塞/非阻塞就是没有数据时读是否被动等待。

    本文并不会严格按上图依次介绍6种IO模型,相信网上有一大堆的资料。本文尝试从常规读写、多路复用的2个常用场景介绍IO模型。

    上图中的信号IO,需要内核在某个时机向用户空间传递SIGIO信号,且用户空间在捕抓到此信号后进行IO处理。这做法并不常见,本文不会展开介绍。且由于是需要被动通知后才会执行IO操作,因此被我归类为异步IO。

    上图中的事件驱动依赖于某些特定的库。其原理类似于为某些事件注册钩子函数,由库函数实现事件监控。在事件触发时调用钩子函数解决。这些函数库屏蔽了平台之间的差异,例如Linux中通过epoll()来监控多个fd。不同库有不同的使用方法,本文也不会展开介绍。

    常规read()和write()

    读操作

    /* 同步阻塞 */
    fd = open("./test.txt", O_RDONLY); // 常规文件
    ret = read(fd, buf, len);
    
    /* 同步非阻塞 */
    int fds[2];
    ret = pipe2(fds, O_NONBLOCK); // 无名管道
    ret = read(fds[0], buf, len);
    
    /* 同步非阻塞 */
    fd = open("./fifo", O_RDONLY | O_NONBLOCK); // 有名管道
    ret = read(fd, buf, len);
    

    在大多数场景下,我们系统调用read()正确返回时就表示已经读到数据了,此次的IO操作已经结束了。毫无疑问,大多数情况下的读操作,都是同步的。

    是否要阻塞,取决于open()时是否有O_NONBLOCK参数。

    总的来说,我们系统调用read(),除非指定O_NONBLOCK,否则都是同步且阻塞的。

    写操作

    /* 异步 */
    fd = open("./test.txt", O_WRONLY);
    ret = write(fd, buf, len);
    
    /* 同步 */
    fd = open("./test.txt", O_WRONLY | O_SYNC);
    ret = write(fd, buf, len);
    

    阻塞与非阻塞主要针对读数据,写数据主要区分同步还是异步

    由于Linux的IO栈中,有专门为IO准备的Cache层。在正常情况下,写操作只是把数据直接扔到了Cache就返回了,此时数据并没回刷到磁盘。要不等到系统回刷线程主动回刷,要不应用主动调用fsync(),否则数据一直都在Cache层,此时掉电数据就丢了。

    写操作是同步还是异步,主要看open()时,是否带O_SYNC参数。带O_SYNC就是同步写,否则就是异步写。

    本文开头就提到,同步/异步是IO发起人是否主动想等待IO结束,这里的写IO结束,指的是数据完全写入到磁盘,而非write()返回。
    在没有O_SYNC情况下,数据只是写到了Cache,需要内核线程定期回刷,所以此时的write是并没有结束的,因此是异步的。相反,如果有O_SYNC,write()操作会一直等到数据完全写入到磁盘后再返回,所以是同步的。

    从写性能角度来说,异步写会优于同步写。由内核IO调度算法,对写请求进行合并与排序,再一次性写入,效率绝对高于东一块西一块的随机写。因此,除非是担心掉电丢失的关键数据,否则建议使用异步写

    多路复用

    多路复用常用于网络开发,例如每个客户端由一个socket与服务器进行远程通信,此时这个服务程序需要同时监控多个socket,为了避免资源损耗和提高响应速率,就会使用多路复用。

    多路复用是怎么一回事呢?我们假设一下有100个socket,在某一时间可能只有个别socket是有数据的,即客户端向服务端发送的请求数据。此时服务程序怎么监控这100个socket,找出有数据的socket,并做出响应?有一种做法,就是非阻塞读每个socket,没数据直接返回读下一个,有数据(请求)就响应,以此实现轮询。还有一种做法,创建100个进程/线程,每个线程,进程对应一个socket。

    对少量的文件还行,如果文件数量一多,数百个,上千个socket逐一轮询,或者创建上千个线程,这效率得多低啊。可不可以批量等待,当哪怕有一个socket有数据时,内核直接告诉应用那个socket来数据了?可以!这就是内核支持的多路复用的系统调用select()epoll()

    /* select 函数原型 */
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    
    /* epoll 函数原型 */
    int epoll_create(int size);
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    

    他们原理大抵相似:

    1. 创建一个文件句柄集合,把要监视的文件句柄按顺序整合到一起
    2. 在有数据时置位对应的标识后返回
    3. 应用通过检查标识就可以知道是哪个socket有数据了,此时读socket即可直接获取数据

    具体的使用方法不在这里详细介绍,网上有总多资料,可以参考《UNIX环境高级编程》。

    本文顺便记录下select()epoll()的优缺点对比:

    select()

    1. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
    2. 每次调用select,都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
    3. select支持的文件描述符数量太小了,默认是1024

    epoll()

    1. epoll不仅仅一个函数,而是切分为3个函数,使得监控新的fd时,不需要拷贝所有的fd集合,只需要拷贝新的fd到内核即可。
    2. epoll采取回调的形式,当某个fd就绪了,就会调用回调,而在回调中,把就绪的fd加入就绪链表
    3. epoll没有数量,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048

    可以发现,epoll()是对select()存在的问题进行针对性的解决。

  • 相关阅读:
    [转发]深入浅出EventSourcing和CQRS
    [转载]缓存与数据库双写一致性问题
    关于com.microsoft.sqlserver.jdbc.SQLServerException: 驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。错误:“java.lang.RuntimeException: Could not generate DH keypair”
    [转载]安装sql2016时提示Polybase 要求安装Oracle JRE 7更新51 (64位)或更高版本”规则失败
    windows安装配置压缩版mysql
    virtualbox虚拟机centeros7系统桥接网卡网络配置
    oracle导入dmp文件(恢复数据)
    解决PL/SQL DEVELOPER12查询中文乱码问题(本地没装Oracle)
    JAVA线程Thread
    深入构造器
  • 原文地址:https://www.cnblogs.com/gmpy/p/12652578.html
Copyright © 2020-2023  润新知