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++)
    {
        //处理可读或可写事件
    }
}

参考资料

相关推荐
500了3 小时前
Kotlin基本知识
android·开发语言·kotlin
人工智能的苟富贵4 小时前
Android Debug Bridge(ADB)完全指南
android·adb
小雨cc5566ru9 小时前
uniapp+Android面向网络学习的时间管理工具软件 微信小程序
android·微信小程序·uni-app
bianshaopeng10 小时前
android 原生加载pdf
android·pdf
hhzz10 小时前
Linux Shell编程快速入门以及案例(Linux一键批量启动、停止、重启Jar包Shell脚本)
android·linux·jar
火红的小辣椒11 小时前
XSS基础
android·web安全
勿问东西13 小时前
【Android】设备操作
android
五味香13 小时前
C++学习,信号处理
android·c语言·开发语言·c++·学习·算法·信号处理
图王大胜15 小时前
Android Framework AMS(01)AMS启动及相关初始化1-4
android·framework·ams·systemserver
工程师老罗17 小时前
Android Button “No speakable text present” 问题解决
android