• BIO、NIO、IO多路复用(select/poll/epoll)、信号驱动IO、异步IO


    Linux用户空间和内核空间

    程序运行时,为了避免用户应用发生冲突,甚至发生系统冲突,用户应用和内核是分开的:
    进程的寻址空间会划分为两部分:内核空间、用户空间。
    当进程运行在用户空间时,我们称之为用户态,当进程运行在内核空间的时候,我们称之为内核态。

    • 用户空间只能执行受限的命令,而且不能调用系统资源,必须通过内核提供的接口来访问。
    • 内核空间可以执行特权命令,调用一切资源。

    Linux为了提高IO效率会在用户空间和内核空间都加入缓冲区:

    • 写数据时,要把用户缓冲数据拷贝到内核缓冲区,然后写去设备。
    • 读数据时,要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区。

    其中读数据的过程如下所示:
    在这里插入图片描述
    读数据主要分为两部分:

    1. 等待数据就绪:我们向内核空间发起请求,等待数据,内核空间将数据准备好之后,放到内核的缓冲区。
    2. 从内核缓冲区拷贝数据到用户缓冲区。然后用户程序就可以对用户缓冲区内的数据进行操作。

    阻塞IO(Blocking IO)

    阻塞IO指的是在 等待数据阶段由内核拷贝数据到用户空间阶段 都阻塞等待的IO。

    等待数据阶段: 用户进程尝试读取数据,此时数据尚未到达,内核需要等待数据,此时用户线程也处于阻塞状态。
    拷贝数据阶段: 数据到达并拷贝到内核缓冲区,代表已经就绪;将内核数据拷贝到用户缓冲区;拷贝过程中,用户进程依然阻塞等待;拷贝完成,用户进程阻塞解除,处理数据。
    在这里插入图片描述
    用户应用调用recvfrom读取数据,数据未准备好,用户应用阻塞等待,直到1,2都完成才能解除阻塞等待。

    非阻塞IO(Noblocking IO)

    非阻塞IOrecvfrom操作会立即返回结果而不是阻塞用户进程。

    等待数据阶段:
    用户进程尝试读取数据,此时数据尚未到达,内核需要等待数据,返回异常给用户进程,用户进程拿到error之后,再次读取失败,之后不停调用recvfrom,直到数据就绪。

    数据拷贝阶段:
    用户进程阻塞等待,直到拷贝完成,用户进程解除阻塞,处理数据。

    在这里插入图片描述
    虽然第二个阶段是非阻塞状态,但是性能并没有得到提高,而且忙等机制会导致CPU空转,CPU使用率暴增。

    IO多路复用(IO Multilpexing)

    文件描述符File DescriptorFD, 是一个从0开始的无符号整数,用来关联Linux中的一个文件。在Linux中一切皆文件,例如常规文件、视频、硬件设备等,也包括网络套接字。

    IO多路复用是利用单个线程来同时监听多个FD,并在FD可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。

    等待数据阶段:用户进程调用select,指定要监听的FD集合,内核监听FD对应的多个socket,任意一个或多个socket数据就绪就返回readable,该过程中用户线程阻塞。

    数据拷贝阶段:用户线程找到就绪的socket,依次调用recvfrom读取数据,内核将数据拷贝到用户空间,用户进程处理数据。
    在这里插入图片描述
    IO多路复用中监听FD的方式,有多种实现,常见的有:selectpollepoll
    其中:

    • selectpoll只会通知用户进程有FD就绪,但不确定是哪个FD,需要用户进程遍历FD来确认。
    • epoll则会在通知用户进程FD就绪的同时,把已经就绪的FD写入用户空间。

    select

    select的部分代码如下:

    // 定义类型别名 __fd_mask,本质是 long int
    typedef long int __fd_mask;
    /* fd_set 记录要监听的fd集合,及其对应状态 */
    typedef struct {
        // fds_bits是long类型数组,长度为 1024/32 = 32
        // 共1024个bit位,每个bit位代表一个fd,0代表未就绪,1代表就绪
        __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
        // ...
    } fd_set;
    // select函数,用于监听fd_set,也就是多个fd的集合
    int select(
        int nfds, // 要监视的fd_set的最大fd + 1
        fd_set *readfds, // 要监听读事件的fd集合
        fd_set *writefds,// 要监听写事件的fd集合
        fd_set *exceptfds, // // 要监听异常事件的fd集合
        // 超时时间,null-用不超时;0-不阻塞等待;大于0-固定等待时间
        struct timeval *timeout
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    其中,存储FD的数据结构为fd_set, fd_set中有一个数组fds_bits,其大小为32,fds_bits数组的类型为long int4字节,因此总共为32x4x8=1024位,每个bit位代表一个fd0代表未就绪,1代表就绪

    其过程如下:
    在这里插入图片描述
    在这里插入图片描述
    select模式存在的问题

    • 需要将整个fd_set从用户空间拷贝到内核空间,select结束还要再次拷贝回用户空间
    • select无法得知具体是哪个fd就绪,需要遍历整个fd_set
    • fd_set监听的fd数量不能超过1024

    poll

    poll的部分代码如下:

    // pollfd 中的事件类型
    #define POLLIN     //可读事件
    #define POLLOUT    //可写事件
    #define POLLERR    //错误事件
    #define POLLNVAL   //fd未打开
    
    // pollfd结构
    struct pollfd {
        int fd;     	  /* 要监听的fd  */
        short int events; /* 要监听的事件类型:读、写、异常 */
        short int revents;/* 实际发生的事件类型 */
    };
    // poll函数
    int poll(
        struct pollfd *fds, // pollfd数组,可以自定义大小
        nfds_t nfds, // 数组元素个数
        int timeout // 超时时间
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    IO流程:

    1. 创建pollfd数组,向其中添加关注的fd信息,数组大小自定义
    2. 调用poll函数,将pollfd数组拷贝到内核空间,转链表存储,无上限
    3. 内核遍历fd,判断是否就绪
    4. 数据就绪或超时后,拷贝pollfd数组到用户空间,返回就绪fd数量n
    5. 用户进程判断n是否大于0
    6. 大于0则遍历pollfd数组,找到就绪的fd

    select对比:

    • select模式中的fd_set大小固定为1024,而pollfd在内核中采用链表,理论上无上限
    • 虽然fd数量无限制,但是监听FD越多,每次遍历消耗时间也越久,性能反而会下降

    epoll

    epoll模式是对select和poll的改进,它提供了三个函数:

    struct eventpoll {
        //...
        struct rb_root  rbr; // 一颗红黑树,记录要监听的FD
        struct list_head rdlist;// 一个链表,记录就绪的FD
        //...
    };
    // 1.创建一个epoll实例,内部是event poll,返回对应的句柄epfd
    int epoll_create(int size);
    // 2.将一个FD添加到epoll的红黑树中,并设置ep_poll_callback
    // callback触发时,就把对应的FD加入到rdlist这个就绪列表中
    int epoll_ctl(
        int epfd,  // epoll实例的句柄
        int op,    // 要执行的操作,包括:ADD、MOD、DEL
        int fd,    // 要监听的FD
        struct epoll_event *event // 要监听的事件类型:读、写、异常等
    );
    // 3.检查rdlist列表是否为空,不为空则返回就绪的FD的数量
    int epoll_wait(
        int epfd,                   // epoll实例的句柄
        struct epoll_event *events, // 空event数组,用于接收就绪的FD
        int maxevents,              // events数组的最大长度
        int timeout   // 超时时间,-1用不超时;0不阻塞;大于0为阻塞时间
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述
    select模式存在的三个问题

    • 能监听的FD最大不超过1024
    • 每次select都需要把所有要监听的FD都拷贝到内核空间
    • 每次都要遍历所有FD来判断就绪状态

    `poll模式的问题

    • poll利用链表解决了select中监听FD上限的问题,但依然要遍历所有FD,如果监听较多,性能会下降

    epoll模式

    • 基于epoll实例中的红黑树保存要监听的FD,理论上无上限,而且增删改查效率都非常高
    • 每个FD只需要执行一次epoll_ctl添加到红黑树,以后每次epol_wait无需传递任何参数,无需重复拷贝FD到内核空间
    • 利用ep_poll_callback机制来监听FD状态,无需遍历所有FD,因此性能不会随监听的FD数量增多而下降

    信号驱动IO(Signal Driven IO)

    信号驱动IO是与内核建立SIGIO的信号关联并设置回调,当内核有FD就绪时,会发出SIGIO信号通知用户,期间用户应用可以执行其它业务,无需阻塞等待。

    等待数据阶段

    1. 用户进程调用sigaction,注册信号处理函数
    2. 内核返回成功,开始监听FD
    3. 用户进程不阻塞等待,可以执行其它业务
    4. 当内核数据就绪后,回调用户进程的SIGIO处理函数

    数据拷贝阶段

    1. 收到SIGIO回调信号
    2. 调用recvfrom,读取
    3. 内核将数据拷贝到用户空间
    4. 用户进程处理数据

    在这里插入图片描述
    当有大量IO操作时,信号较多,SIGIO处理函数不能及时处理可能导致信号队列溢出,而且内核空间与用户空间的频繁信号交互性能也较低。

    异步IO(Asynchronous IO)

    异步IO的整个过程都是非阻塞的,用户进程调用完异步API后就可以去做其它事情,内核等待数据就绪并拷贝到用户空间后才会递交信号,通知用户进程。

    等待数据阶段

    1. 用户进程调用aio_read,创建信号回调函数
    2. 内核等待数据就绪
    3. 用户进程无需阻塞,可以做任何事情

    数据拷贝阶段

    1. 内核数据就绪
    2. 内核数据拷贝到用户缓冲区
    3. 拷贝完成,内核递交信号触发aio_read中的回调函数
    4. 用户进程处理数据

    异步IO模型中,用户进程在两个阶段都是非阻塞状态。

    五种IO模型对比

    在这里插入图片描述

  • 相关阅读:
    SIMD指令集介绍
    【Flyweight模式】C++设计模式——享元模式
    【Web】NodeJs相关例题wp
    分布式锁-yudao之设计精妙之处
    Java异常、断言和日志①——Java基础
    iOS持续集成打包从Intel芯片迁移到Apple M2芯片解决方案
    虚拟机CentOS 8 重启后不能上网
    关卡三:面向对象编程
    Spark性能调优案例-多表join优化,减少shuffle
    4. 执行引擎
  • 原文地址:https://blog.csdn.net/qq_36944952/article/details/126681307