LV06-07-IO模型-05-多路复用IO
来详细了解一下多路复用IO?若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 | Windows | Windows11 |
Ubuntu | Ubuntu20.04.2的64位版本 | |
VMware® Workstation 17 Pro | 17.6.0 build-24238078 | |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) | |
Win32DiskImager | Win32DiskImager v1.0 | |
Linux开发板环境 | Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04 | |
linux内核 | linux-4.19.71(NXP官方提供) |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.nxp.com.cn/ | NXP官方网站 | |
https://www.nxpic.org.cn/ | NXP 官方社区 | |
https://u-boot.readthedocs.io/en/latest/ | u-boot官网 | |
https://www.kernel.org/ | linux内核官网 |
点击查看相关文件下载
分类 | 网址 | 说明 |
NXP | https://github.com/nxp-imx | NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库 |
nxp-imx/linux-imx/releases/tag/v4.19.71 | NXP linux内核仓库tags中的v4.19.71 | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 | NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0 | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) | |
Source Code | https://elixir.bootlin.com/linux/latest/source | linux kernel源码 |
kernel/git/stable/linux.git - Linux kernel stable tree | linux kernel源码(官网,tag 4.19.71) | |
https://elixir.bootlin.com/u-boot/latest/source | uboot源码 |
一、多路复用IO
1. 什么是多路复用IO
多路复用 IO 是一种同步的 IO 模型。 多路复用 IO 可以实现一个进程监视多个文件描述符。一旦某个文件描述符准备就绪, 就通知应用程序进行相应的读写操作。 没有文件描述符就绪时就会阻塞应用程序, 从而释放出 CPU 资源。
举个例子,小李同时放置了十个鱼竿, 并把十个鱼竿连在了一个铃铛上。 这样小李就不必在岸边等待。 当铃铛响了就表示有鱼上钩, 再回来挨个检查到底是哪个鱼竿有鱼上钩即可。
2. 应用层的多路复用IO模型
在应用层 Linux 提供了三种实现 IO 多路复用的模型, 分别是 select、 poll 和 epoll。epoll 更多的是用在大规模的并发服务器上,因为在这种场合下 select 和 poll 并不适合。当设计到的文件描述符(fd)比较少的时候就适合用 selcet 和 poll。
2.1 select()
2.1.1 函数说明
我们使用man select 来看一下该函数的帮助手册:
1 | NAME |
函数参数 :
- nfds: 所要监视的这三类文件描述集合中, 最大文件描述符加 1。
- readfds、 writefds 和 exceptfds:这三个指针指向描述符集合。
这三个参数指明了关心哪些描述符、需要满足哪些条件等等,这三个参数都是 fd_set 类型的, fd_set 类型变量的每一个位都代表了一个文件描述符。 readfds 用于监视指定描述符集的读变化,也就是监视这些文件是否可以读取,只要这些集合里面有一个文件可以读取那么 seclect 就会返回一个大于 0 的值表示文件可以读取。如果没有文件可以读取,那么就会根据 timeout 参数来判断是否超时。可以将 readfs设置为 NULL,表示不关心任何文件的读变化。 writefds 和 readfs 类似,只是 writefs 用于监视这些文件是否可以进行写操作。 exceptfds 用于监视这些文件的异常。
比如我们现在要从一个设备文件中读取数据,那么就可以定义一个 fd_set 变量,这个变量要传递给参数 readfds。当我们定义好一个 fd_set 变量以后可以使用如下所示几个宏进行操作:
1 | void FD_ZERO(fd_set *set) |
FD_ZERO 用于将 fd_set 变量的所有位都清零, FD_SET 用于将 fd_set 变量的某个位置 1,也就是向 fd_set 添加一个文件描述符,参数 fd 就是要加入的文件描述符。 FD_CLR 用于将 fd_set 变量的某个位清零,也就是将一个文件描述符从 fd_set 中删除,参数 fd 就是要删除的文件描述符。 FD_ISSET 用于测试一个文件是否属于某个集合,参数 fd 就是要判断的文件描述符。
- timeout:超时时间,当我们调用 select 函数等待某些文件描述符可以设置超时时间,当 timeout 为 NULL 的时候就表示无限期的等待。
超时时间使用结构体 timeval 表示,该结构体定义如下:
1 | struct timeval { |
返回值: 0,表示的话就表示超时发生,但是没有任何文件描述符可以进行操作; -1,发生错误;其他值,可以进行操作的文件描述符个数。
2.1.2 使用示例
使用 select 函数对某个设备驱动文件进行读非阻塞访问的操作示例如下:
1 | int main(int argc, const char *argv[]) |
2.2 poll()
2.2.1 函数说明
我们可以使用 man poll 看一下函数的帮助手册:
1 | NAME |
在单个线程中, select 函数能够监视的文件描述符数量有最大的限制,一般为 1024,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!这个时候就可以使用 poll 函数, poll 函数本质上和 select 没有太大的差别,但是 poll 函数没有最大文件描述符限制。
函数参数 :
- fds: 要监视的文件描述符集合以及要监视的事件,为一个数组,数组元素都是结构体 pollfd类型的。
pollfd 结构体如下所示 :
1 | struct pollfd { |
fd 是要监视的文件描述符,如果 fd 无效的话那么 events 监视事件也就无效,并且 revents返回 0。 events 是要监视的事件,可监视的事件类型如下所示:
1 | POLLIN 有数据可以读取。 |
revents 是返回参数,也就是返回的事件, 由 Linux 内核设置具体的返回事件。
- nfds: poll 函数要监视的文件描述符数量。
- timeout: 超时时间,单位为 ms。
返回值:返回 revents 域中不为 0 的 pollfd 结构体个数,也就是发生事件或错误的文件描述符数量; 0,超时; -1,发生错误,并且设置 errno 为错误类型。
2.2.2 使用示例
使用 poll 函数对某个设备驱动文件进行读非阻塞访问的示例如下:
1 | int main(int argc, const char *argv[]) |
2.3 epoll()
传统的 selcet 和 poll 函数都会随着所监听的 fd 数量的增加,出现效率低下的问题,而且poll 函数每次必须遍历所有的描述符来检查就绪的描述符,这个过程很浪费时间。为此, epoll应运而生, epoll 就是为处理大并发而准备的,一般常常在网络编程中使用 epoll 函数。
2.3.1 epoll_create()
应用程序需要先使用 epoll_create 函数创建一个 epoll 句柄,我们可以用man epoll_create命令查看这个函数的帮助手册:
1 | NAME |
函数参数 :
- size: 从 Linux2.6.8 开始此参数已经没有意义了,随便填写一个大于 0 的值就可以。
返回值: epoll 句柄,如果为-1 的话表示创建失败。
2.3.2 epoll_ctl()
我们可以使用 man epoll_ctl 来查看函数帮助手册
1 | NAME |
函数参数 :
- epfd: 要操作的 epoll 句柄,也就是使用 epoll_create 函数创建的 epoll 句柄。
- op: 表示要对 epfd(epoll 句柄)进行的操作,可以设置为:
1 | EPOLL_CTL_ADD 向 epfd 添加文件参数 fd 表示的描述符。 |
- fd:要监视的文件描述符。
- event: 要监视的事件类型,为 epoll_event 结构体类型指针, epoll_event 结构体类型如下所示:
1 | struct epoll_event { |
结构体 epoll_event 的 events 成员变量表示要监视的事件,可选的事件如下所示:
1 | EPOLLIN 有数据可以读取。 |
上面这些事件可以进行“或”操作,也就是说可以设置监视多个事件。
返回值: 0,成功; -1,失败,并且设置 errno 的值为相应的错误码。
2.3.3 epoll_wait()
我们可以使用 man epoll_wait 来查看该函数的帮助手册:
1 | NAME |
函数参数 :
- epfd: 要操作的 epoll 句柄,也就是使用 epoll_create 函数创建的 epoll 句柄。
- events: 指向 epoll_event 结构体的数组,当有事件发生的时候 Linux 内核会填写 events,调用者可以根据 events 判断发生了哪些事件。
- maxevents: events 数组大小,必须大于 0。
- timeout: 超时时间,单位为 ms。
返回值: 0,超时; -1,错误;其他值,准备就绪的文件描述符数量。
2.4 三者有什么区别?
select、 poll 和 epoll 函数有什么区别呢?
poll 函数和 seslect 函数都可以监听多个文件描述符, 通过轮询来获取已经准备好的文件描述符。 但是 epoll 函数将主动轮询变成了被动通知, 当事件发生时被动接收通知。
举个例子。 假如 poll 和 select是公司的前台, 某天一位客户来公司找硬件工程师-小李, 请求前台帮忙找人。 于是 poll 和 select前台带着这位客户挨个屋子寻找小李, 直到找到小李为止。 假如 epoll 是公司的前台, 他提前统计了公司每个员工的工位。 当客户来找小李的时候, 不必像 poll select 一样, 可以直接带着客户到硬件部门去找小李。 从上面的例子, 明显 epoll 的效率更高。 假如公司园区很大, 那么 poll 和 select 需要花费很长时间寻找小李, 而 epoll 已经提前知道小李坐在哪个工位了,直接带客户去找小李即可。
3. 驱动中的poll
3.1 file_operations.poll
当应用程序使用 select 或者 poll 函数对驱动程序进行非阻塞访问时, 驱动程序中 file_operations 操作集的file_operations.poll 函数会执行。
1 | __poll_t (*poll) (struct file *, struct poll_table_struct *); |
这个函数要进行下面两项工作。 首先, 对可能引起设备文件状态变化的等待队列调用poll_wait(),将对应的等待队列头添加到 poll_table.然后返回表示是否能对设备进行无阻塞读写访问的掩码。
函数参数:
- pFile:struct file 类型指针变量,指向要打开的文件描述符。
- pWait: struct poll_table_struct 类型指针, 此参数是由应用程序中传递的。 一般此参数要传递给 poll_wait 函数。
返回值:向应用程序返回设备或者资源状态,可以返回的资源状态如下:
1 | POLLIN 有数据可以读取。 |
3.2 poll_wait()
驱动程序的 file_operations.poll 函数中需要调用 poll_wait() 函数:
1 | static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p) |
poll_wait() 函数不会引起阻塞,只是将应用程序添加到 poll_table 中。其中参数 wait_address 是要添加到 poll_table 中的等待队列头, 参数 p 是 poll_table类型, 也就是file_operations 中 poll 函数的 pWait参数。
二、多路复用IO demo
1. demo源码
08_IO_model/04_io_poll · 苏木/imx6ull-driver-demo - 码云 - 开源中国
2. 开发板验证
我们执行以下命令:来验证效果:
1 | ./app_demo.out /dev/sdevchr 1 0 & |
- select

- poll
