图解Linux poll机制,终于集齐IO复用三剑客(精华篇)

前言:

前面几篇文章已经详细讲解了Linux select和epoll机制,select和epoll两种IO复用方式用的人比较多,就像一家人一样,epoll相当于大哥,select相当于弟弟,而poll相当于二哥,家里的老二通常是很容易被忽视的对象,poll这种IO复用方式也很容易被忽视。

epoll机制效率高,适用于高并发场景,所以epoll机制广泛用于各种开源项目。

select机制编程简单,如果对高并发没有需求,那么很多人会选择select机制做多路IO请求处理。

poll机制既没有高并发能力,编程也并不简单,所以经常吃灰。

这篇文章通过图文详解Linux poll实现原理,目的是让大家能够从本质了解poll机制,以及对三种IO多路复用方式有一个全面的了解,最后根据自己的业务需求,选择合适的IO复用机制。

1.poll简介

poll是一种I/O多路复用机制,它可以同时监视多个文件描述符,当其中任意一个文件描述符就绪时,就会通知程序进行相应的读写操作。

poll机制与select机制类似,但是poll没有最大文件描述符数量的限制,因此在文件描述符数量较大时,poll的效率会更高。

poll机制的使用需要调用系统调用poll()函数,该函数会阻塞进程直到有文件描述符就绪或者超时。

poll()函数的参数是一个pollfd结构体数组,每个结构体中包含了一个文件描述符和该文件描述符所关注的事件类型。

2.poll实现原理

图片

poll实现原理重要活动如上图,内容有点多,但是很重要,请仔细分析确保真正理解:

  • 用户将想要监听的socket文件绑定struct pollfd对象,并注册监听事件至struct pollfd对象events成员,监听多个socket文件使用struct pollfd数组。

  • 用户通过struct pollfd数组注册poll事件至poll_list链表,poll_list链表单个元素可以存储固定数量的struct pollfd对象。

  • poll系统调用采用轮询方式获取socket事件信息,一次poll调用需完成整个poll_list链表轮询工作,轮询socket的过程中会创建socket等待队列项,并加入socket等待队列(用于socket唤醒进程)。如果检测到socket处于就绪状态,将socket事件保存在struct pollfd对象的revents成员。

  • poll系统调用完成一次轮询后,如果检测到有socket处于就绪状态,则将poll_list链表所有的struct pollfd通过copy_to_user拷贝至用户struct pollfd数组。如果未检测到有socket处于就绪状态,根据超时时间确定是否返回或者阻塞进程。

  • socket检测到读,写,异常事件后,会通过注册到socket等待队列的回调函数poll_wake将进程唤醒,唤醒的进程将再次轮询poll_list链表。

3.poll编程

3.1 poll函数原型

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

功能:poll函数是Linux系统中的一种I/O多路复用机制,它可以同时监视多个文件描述符。

参数:

fds:监听事件结构体数组。

nfds:监听事件结构体数组长度。

timeout:

等于-1:一直阻塞。

等于0:立即返回。

大于0:等待超时时间,单位毫秒。

返回值:

成功:返回检测到的文件描述符数量。

失败:返回-1,设置errno。

超时:返回0。

3.2 poll事件定义

#define POLLIN      0x0001

#define POLLPRI     0x0002

#define POLLOUT     0x0004

#define POLLERR     0x0008

#define POLLHUP     0x0010

#define POLLNVAL    0x0020

#define POLLRDNORM  0x0040

#define POLLRDBAND  0x0080

#define POLLWRNORM  0x0100

#define POLLWRBAND  0x0200

#define POLLMSG     0x0400

#define POLLREMOVE  0x1000

#define POLLRDHUP   0x2000

#define POLLFREE    0x4000

#define POLL_BUSY_LOOP 0x8000

3.3 struct pollfd 结构体

struct pollfd {    

    int fd;     

    short events;    

    short revents;

};

fd: 监听文件描述符。

events:监听事件集合,用于注册监听事件。

revents:返回事件集合,用于存储返回事件。

3.4 events和revents对poll事件支持情况

图片

3.5 poll编程模型

图片

4.poll常见问题?

问题1:poll的优缺点?

优点:

  • poll没有1024最大文件描述符限制。

  • poll监视事件(events)和返回事件(revents)分离,简化编程。

缺点:

  • 采用轮询方式获取就绪文件描述符,效率低,和select一样。

  • 每次调用poll都需要把所有文件描述符从内核空间复制到用户空间。

  • 虽然poll没有1024最大文件描述符限制,但是注册的文件描述符越多,poll效率越低。

问题2:poll和select的区别?

poll和select底层实现非常相似,分析poll和select内核源码会发现二者之间很多地方都复用了相同的代码。

poll可以说是select的加强版,poll优化了select一些设计缺陷:

  • poll不受1024最大文件描述符限制,poll采用poll_list链表方式存储输入和输出事件,理论上可以不受最大文件描述符限制。

  • poll传入的是struct pollfd数组,并指定了数组长度,可以减少无效的轮询,提高轮询效率。

  • poll监视事件(events)和返回事件(revents)分离,每次调用poll不需要重新设置struct pollfd对象。

  • poll返回时不会返回剩余超时时间,用户不需要当心超时出现异常

不过很可惜,即使poll对select做了很多优化,依然没有改变轮询方式,也没有改变selec执行效率低的本质问题。

5.select,poll,epoll对比

图片

Linux中的poll机制是一种IO多路复用机制,它可以同时监视多个文件描述符的可读、可写和异常等事件。当有文件描述符就绪时,poll会通知应用程序进行相应的操作。 poll机制的工作原理如下: 1. 应用程序调用poll函数,并传入一个pollfd结构体数组,每个结构体包含一个文件描述符和要监视的事件。 2. poll函数将这个数组传递给内核,并阻塞等待事件发生。 3. 当有文件描述符就绪时,内核会修改pollfd结构体中的revents字段,表示该文件描述符的就绪事件。 4. poll函数返回,应用程序可以通过遍历pollfd数组来找到就绪的文件描述符,并进行相应的操作。 与select机制相比,poll机制的改进之处在于: 1. poll没有最大文件描述符数量的限制,可以处理更多的文件描述符。 2. poll使用pollfd结构体数组来传递文件描述符和事件,更加灵活。 示例代码如下: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <poll.h> int main() { struct pollfd fds[1]; int ret; // 打开一个文件描述符 int fd = open("test.txt", O_RDONLY); // 设置要监视的文件描述符和事件 fds[0].fd = fd; fds[0].events = POLLIN; // 调用poll函数等待事件发生 ret = poll(fds, 1, -1); if (ret == -1) { perror("poll"); exit(EXIT_FAILURE); } // 检查文件描述符的就绪事件 if (fds[0].revents & POLLIN) { printf("File descriptor is ready for reading.\n"); } // 关闭文件描述符 close(fd); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值