Linux IO 多路复用 epoll 机制

本文摘自写给应用开发的 Android Framework 教程 ,完整教程请查阅 yuandaimaahao.github.io/AndroidFram... 更为详细的视频教程与答疑服务,请联系微信 zzh0838

什么是 IO 多路复用

在 Linux 中:

  • IO 就是对文件的读写操作
  • 多路是指同时读写多个文件
  • 复用是指使用一个程序处理多个文件的同时读写

问题来了,为什么需要多路复用,为了快,要给每一个 fd 通道最快的感受,要让每一个 fd 觉得,你只在给他一个人跑腿。

为了更快的处理多路 IO,大体有两种方案:

  • 一种方案是:一个 IO 请求(比如 write )对应一个线程来处理,但是线程数多了,性能反倒会差。
  • 另外一种方案是: IO 多路复用

接下来,我们就来看看 IO 多路复用:

我不用任何其他系统调用,能否实现 IO 多路复用?

可以的,写个 for 循环,每次都尝试 IO 一下,读/写到了就处理,读/写不到就 sleep 下。

cpp 复制代码
while(true) 
{
    foreach fd数组 
    {
        read/write(fd, /* 参数 */)
    }

    sleep(1s)
}

默认情况下,我们没有加任何参数 create 出的 fd 是阻塞类型的。我们读数据的时候,如果数据还没准备好,是会需要等待的,当我们写数据的时候,如果还没准备好,默认也会卡住等待。所以,在上面伪代码中的 read/write 是可能被直接卡死,而导致整个线程都得到不到运行。只需要把 fd 都设置成非阻塞模式。这样 read/write 的时候,如果数据没准备好,返回 EAGIN 的错误即可,不会卡住线程,从而整个系统就运转起来了。

这个实现只是为了帮助我们理解 IO 多路复用,实际上,上面的实现在性能上有很大的缺陷。for 循环每次要定期 sleep 1s,这个会导致吞吐能力极差,因为很可能在刚好要 sleep 的时候,所有的 fd 都准备好 IO 数据,而这个时候却要硬生生的等待 1s。

IO 多路复用就是 1 个线程处理 多个 fd 的模式。我们的要求是:这个 "1" 就要尽可能的快,避免一切无效工作,要把所有的时间都用在处理句柄的 IO 上,不能有任何空转,sleep 的时间浪费。

为了实现上诉的功能,内核提供了 3 种系统调用 select,poll,epoll。

这 3 种系统调用都能够管理 fd 的可读可写事件,在所有 fd 不可读不可写无所事事的时候,可以阻塞线程,切走 cpu 。fd 可读写的时候,对应线程会被唤醒。

三者的差异主要是在性能上,epoll 的性能是强于 select 和 poll 的,我们接下来就来看看 epoll 的具体使用。

epoll 的使用

使用 epoll 需要以下三个系统调用:

cpp 复制代码
//头文件
#include <sys/epoll.h>

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);
  • epollcreate 负责创建一个池子,一个监控和管理句柄 fd 的池子;
  • epollctl 负责管理这个池子里的 fd 增、删、改;
  • epollwait 就是负责打盹的,让出 CPU 调度,但是只要有"事",立马会从这里唤醒;

接下来我们看个示例程序:

使用 epoll_create 创建一个管理 fd 的池子

cpp 复制代码
epollfd = epoll_create(1024);
if (epollfd == -1) {
    perror("epoll_create");
    exit(EXIT_FAILURE);
}

这个池子对我们来说是黑盒,这个黑盒是用来装 fd 的,我们暂不纠结其中细节。我们拿到了一个 epollfd ,这个 epollfd 就能唯一代表这个 epoll 池。

然后,我们就要往这个 epoll 池里放 fd 了,这就要用到 epoll_ctl 了

cpp 复制代码
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
    perror("epoll_ctl: listen_sock");
    exit(EXIT_FAILURE);
}

我们就把 fd 放到这个池子里了,EPOLL_CTL_ADD 表明操作是增加 fd,最后一个参数是 epoll_event 结构体:

cpp 复制代码
struct epoll_event {
  __uint32_t events;  /* Epoll 事件 */
  epoll_data_t data;  /* 用户数据 */
};

epoll_event 的第一个成员 events 用于指定我们监听的 fd 事件类型,常见的值有:

  • EPOLLIN:可读事件
  • EPOLLOUT:可写事件

多个值可以通过或操作同时生效:

cpp 复制代码
epoll_event event;
// 同时监听可读可写事件
event.events = EPOLLIN | EPOLLOUT;

最后,我们需要调用 epoll_wait 进入休眠状态,可读或可写事件到来时,醒休眠中的程序从 epoll_wait 处被唤醒。

其使用方法,通常如下:

cpp 复制代码
while (true)
{   
    // epollfd 是 epoll_create 的返回值
    // events 是一个 epoll_event 的数组,用于存储收到的多个事件
    // EPOLL_SIZE 用于设定最多监听多少个事件
    // 最后一个参数 -1 用于指定阻塞时间上限,-1:表示调用将一直阻塞
    int count = epoll_wait(epollfd, events, EPOLL_SIZE, -1);
    if (count < 0)
    {
        perror("epoll failed");
        break;
    }
    for (int i=0;i < count;i++)
    {
        //处理可读或可写事件
    }
}

参考资料

相关推荐
怣疯knight1 小时前
Windows不安装 Android Studio如何打包安卓软件
android·windows·android studio
ke_csdn1 小时前
从Java演变到Kotlin下的jet pack
android
wenzhangli72 小时前
在低代码设计中践行 Harness Engineering
android·低代码·rxjava
xingpanvip3 小时前
星盘接口开发文档:组合三限盘接口指南
android·开发语言·前端·python·php·lua
TechMix3 小时前
【fkw学习笔记】Android 13 AOSP 源码添加系统预置应用实战指南
android·笔记·学习
云起SAAS3 小时前
私域直播系统UniApp源码 多商户商城+直播带货 微信小程序+H5+安卓iOS
android·微信小程序·uni-app·私域直播系统
空中海4 小时前
01. 安卓逆向基础、环境搭建与授权
android
星河耀银海4 小时前
JAVA 泛型与通配符:从原理到实战应用
android·java·服务器
Ada大侦探4 小时前
新手小白学习数据分析01----数据分析师???& 数据分析思维学习
android·学习·数据分析
空中海4 小时前
安卓逆向5. 安卓风险防护、加固复测与综合
android