什么是多路复用IO

多路复用IO (IO multiplexing) 是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。在Linux系统中,常用的 多路复用IO 手段有 selectpoll 和 epoll

多路复用IO 主要用于处理网络请求,例如可以把多个请求句柄添加到 select 中进行监听,当有请求可进行IO的时候就会告知进程,并且把就绪的请求句柄保存下来,进程只需要对这些就绪的请求进行IO操作即可。下面通过一幅图来展示 select 的使用方式(图片来源于网络):

多路复用IO实现原理

为了更简明的解释 多路复用IO 的原理,这里使用 select 系统调用作为分析对象。因为 select 的实现比较简单,而现在流行的 epoll 由于处于性能考虑,实现则比较复杂,不便于理解 多路复用IO 的原理,当然当理解了 select 的实现原理后,对 epoll 的实现就能应刃而解了。

select系统调用的使用

要使用 select 来监听socket是否可以进行IO,首先需要把其添加到一个类型为 fd_set 的结构中,然后通过调用 select() 系统调用来进行监听,下面代码介绍了怎么使用 select 来对socket进行监听的:

int socket_can_read(int fd){int retval;fd_set set;struct timeval tv;FD_ZERO(&set);FD_SET(fd, &set);    tv.tv_sec = tv.tv_usec = 0;    retval = select(fd+1, &set, NULL, NULL, &tv);if (retval < 0) {return -1;    }return FD_ISSET(fd, &set) ? 1 : 0;}

通过上面的函数,可以监听一个socket句柄是否可读。

select系统调用的实现

接下来我们分析一下 select 系统调用的实现,用户程序通过调用 select 系统调用后会进入到内核态并且调用 sys_select() 函数,sys_select() 函数的实现如下:

asmlinkage longsys_select(int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp){    fd_set_bits fds;char *bits;long timeout;int ret, size;    timeout = MAX_SCHEDULE_TIMEOUT;if (tvp) {time_t sec, usec;        ...if ((unsigned long) sec < MAX_SELECT_SECONDS) {            timeout = ROUND_UP(usec, 1000000/HZ);            timeout += sec * (unsigned long) HZ;        }    }if (n > current->files->max_fdset)        n = current->files->max_fdset;    ret = -ENOMEM;    size = FDS_BYTES(n);    bits = select_bits_alloc(size);    fds.in = (unsigned long *)bits;    fds.out = (unsigned long *)(bits + size);    fds.ex = (unsigned long *)(bits + 2*size);    fds.res_in = (unsigned long *)(bits + 3*size);    fds.res_out = (unsigned long *)(bits + 4*size);    fds.res_ex = (unsigned long *)(bits + 5*size);if ((ret = get_fd_set(n, inp, fds.in)) ||        (ret = get_fd_set(n, outp, fds.out)) ||        (ret = get_fd_set(n, exp, fds.ex)))goto out;zero_fd_set(n, fds.res_in);zero_fd_set(n, fds.res_out);zero_fd_set(n, fds.res_ex);    ret = do_select(n, &fds, &timeout);    ...set_fd_set(n, inp, fds.res_in);set_fd_set(n, outp, fds.res_out);set_fd_set(n, exp, fds.res_ex);out:select_bits_free(bits, size);out_nofds:return ret;}

sys_select() 函数主要把用户态的参数复制到内核态,然后再通过调用 do_select() 函数进行监听操作, do_select()函数实现如下(由于实现有点复杂,所以我们分段来分析):

int do_select(int n, fd_set_bits *fds, long *timeout){    poll_table table, *wait;int retval, i, off;long __timeout = *timeout;    ...poll_initwait(&table);wait = &table;if (!__timeout)wait = NULL;    retval = 0;

上面这段代码主要通过调用 poll_initwait() 函数来初始化类型为 poll_table 结构的变量 table。要理解 poll_table结构的作用,我们先来看看下面的知识点:

因为每个socket都有个等待队列,当某个进程需要对socket进行读写的时候,如果发现此socket并不能读写, 那么就可以添加到此socket的等待队列中进行休眠,当此socket可以读写时再唤醒队列中的进程。

而 poll_table 结构就是为了把进程添加到socket的等待队列中而创造的,我们先跳过这部分,后面分析到socket相关的知识点再来说明。

我们接着分析 do_select() 函数的实现:

    for (;;) {set_current_state(TASK_INTERRUPTIBLE);for (i = 0 ; i < n; i++) {            ...            file = fget(i);            mask = POLLNVAL;if (file) {                mask = DEFAULT_POLLMASK;if (file->f_op && file->f_op->poll)                    mask = file->f_op->poll(file, wait);fput(file);            }

这段代码首先通过调用文件句柄的 poll() 接口来检查文件是否能够进行IO操作,对于socket来说,这个 poll() 接口就是 sock_poll(),所以我们来看看 sock_poll() 函数的实现:

static unsigned int sock_poll(struct file *file, poll_table * wait){struct socket *sock;    sock = socki_lookup(file->f_dentry->d_inode);return sock->ops->poll(file, sock, wait);}

sock_poll() 函数的实现很简单,首先通过 socki_lookup() 函数来把文件句柄转换成socket结构,接着调用socket结构的 poll() 接口,而对应 TCP 类型的socket,这个接口对应的是 tcp_poll() 函数,实现如下:

unsigned int tcp_poll(struct file * file, struct socket *sock, poll_table *wait){unsigned int mask;struct sock *sk = sock->sk;struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);poll_wait(file, sk->sleep, wait); // 把文件添加到sk->sleep队列中进行等待    ...return mask;}

tcp_poll() 函数通过调用 poll_wait() 函数把进程添加到socket的等待队列中。然后检测socket是否可读写,并通过mask返回可读写的状态。所以在 do_select() 函数中的 mask = file->f_op->poll(file, wait); 这行代码其实调用的是 tcp_poll() 函数。

接着分析 do_select() 函数:

            if ((mask & POLLIN_SET) && ISSET(bit, __IN(fds,off))) {SET(bit, __RES_IN(fds,off));                retval++;wait = NULL;            }if ((mask & POLLOUT_SET) && ISSET(bit, __OUT(fds,off))) {SET(bit, __RES_OUT(fds,off));                retval++;wait = NULL;            }if ((mask & POLLEX_SET) && ISSET(bit, __EX(fds,off))) {SET(bit, __RES_EX(fds,off));                retval++;wait = NULL;            }

因为 mask 变量保存了socket的可读写状态,所以上面这段代码主要通过判断socket的可读写状态来把socket放置到合适的返回集合中。如果socket可读,那么就把socket放置到可读集合中,如果socket可写,那么就放置到可写集合中。

        wait = NULL;if (retval || !__timeout || signal_pending(current))break;if(table.error) {            retval = table.error;break;        }        __timeout = schedule_timeout(__timeout);    }    current->state = TASK_RUNNING;poll_freewait(&table);    *timeout = __timeout;return retval;}

最后这段代码的作用是,如果监听的socket集合中有可读写的socket,那么就直接返回(retval不为0时)。另外,如果调用 select() 时超时了,或者进程接收到信号,也需要返回。

否则,通过调用 schedule_timeout() 来进行一次进程调度。因为前面把进程的运行状态设置成 TASK_INTERRUPTIBLE,所以进行进程调度时就会把当前进程从运行队列中移除,进程进入休眠状态。那么什么时候进程才会变回运行状态呢?

前面我们说过,每个socket都有个等待队列,所以当socket可读写时便会把队列中的进程唤醒。这里分析一下当socket变成可读时,怎么唤醒等待队列中的进程的。

网卡接收到数据时,会进行一系列的接收数据操作,对于TCP协议来说,接收数据的调用链是: tcp_v4_rcv() -> tcp_data() -> tcp_data_queue() -> sock_def_readable(),我们来看看 sock_def_readable() 函数的实现:

void sock_def_readable(struct sock *sk, int len){read_lock(&sk->callback_lock);if (sk->sleep && waitqueue_active(sk->sleep))wake_up_interruptible(sk->sleep);sk_wake_async(sk,1,POLL_IN);read_unlock(&sk->callback_lock);}

可以看出 sock_def_readable() 函数最终会调用 wake_up_interruptible() 函数来把等待队列中的进程唤醒,这时调用 select() 的进程从休眠状态变回运行状态。

-THE END-


推荐阅读

【1】SPI转can芯片CSM300详解、Linux驱动移植调试笔记必读【2】到底什么是Cortex、ARMv8、arm架构、ARM指令集、soc?一文帮你梳理基础概念【科普】 必读【3】Linux面试题100道,看看会多少?必读【4】Modbus协议概念最详细介绍必读【5】I2C基础知识入门 必读【6】两个线程,两个互斥锁,怎么形成一个死循环?

本公众号全部原创干货已整理成一个目录,点击「干货」或者回复 1024 即可获得。

想加入嵌入式技术交流群请加一口君微信

一口君有问必答

io多路复用的原理和实现_多路复用IO内幕相关推荐

  1. io多路复用的原理和实现_彻底理解 IO 多路复用实现机制

    本文作者:何建辉(公众号:org_yijiaoqian) 点赞再看,养成习惯,微信搜一搜[一角钱技术]关注更多原创技术文章.本文 GitHub org_hejianhui/JavaStudy已收录,有 ...

  2. io多路复用的原理和实现_IO多路复用机制详解

    select,poll,epoll机制区别总结: 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞I ...

  3. io多路复用的原理和实现_IO多路复用的三种机制:select 、poll 、epoll

    目录 概述 IO多路复用本质 IO多路复用的优势 IO多路复用Select机制 IO多路复用Poll机制 IO多路复用Epoll机制 select,poll,epoll机制区别总结 php7进阶到架构 ...

  4. IO多路复用底层原理及源码解析

    基本概念 1. 关于linux文件描述符 在Linux中,一切都是文件,除了文本文件.源文件.二进制文件等,一个硬件设备也可以被映射为一个虚拟的文件,称为设备文件.例如,stdin 称为标准输入文件, ...

  5. IO模型_阻塞_非阻塞_多路复用

    在了解IO模型前,先了解什么叫IO,IO得操作是怎么样的? IO既输入输出,指的是一切操作程序或设备与计算机之间发生的数据传输的过程.它分为IO设备和IO接口两个部分. IO设备:就是指可以与计算机进 ...

  6. java基础巩固-宇宙第一AiYWM:为了维持生计,四大基础之OS_Part_2整起~IO们那些事【包括五种IO模型:(BIO、NIO、IO多路复用、信号驱动、AIO);零拷贝、事件处理及并发等模型】

    PART0.前情提要: 通常用户进程的一个完整的IO分为两个阶段(IO有内存IO.网络IO和磁盘IO三种,通常我们说的IO指的是后两者!):[操作系统和驱动程序运行在内核空间,应用程序运行在用户空间, ...

  7. 详解磁盘IO、网络IO、零拷贝IO、BIO、NIO、AIO、IO多路复用(select、poll、epoll)

    文章很长,但是很用心! 文章目录 1. 什么是I/O 2. 磁盘IO 3. 网络IO 4. IO中断与DMA 5. 零拷贝IO 6. BIO 7. NIO 8. IO多路复用 8.1 select 8 ...

  8. 计算机IO系列(二)BIO/NIO/多路复用实现

    一.什么是IO? 我们都知道Liux世界里.一切皆文件.而文件是什么呢?文件就是一串二进制流而已.不管socket.还是FIFO.管道.终端.对我们来说.一切都是文件.一切都是流.在信息交换的过程中. ...

  9. 【笔记整理】通信原理第八章复习——多路复用和伪随机序列

    多路复用和伪随机序列 8.1 概述 多路复用 目的:在一条链路上传输多路独立信号 基本原理:正交划分方法 凡是理论上正交的多个信号,在同一条链路上传输到接收端后都可能利用其正交性完全区分开 多路复用基 ...

最新文章

  1. ISME:南农韦中等消减土壤青枯菌生物障碍新策略
  2. 【CSS3进阶】酷炫的3D旋转透视
  3. 精通python设计模式-精通Python设计模式
  4. WCF服务支持HTTP(get,post)方式请求例子
  5. PaddleOCR——C++服务端部署Visual Studio 2019 环境下CMake 编译错误【无法打开输入文件paddle_fluid.lib】解决方案
  6. java display属性_JavaScript中的style.display属性操作
  7. 文献学习(part19)--Graph Connectivity In Sparse Subspace Clustering
  8. 最大公约数的欧几里德[辗转相除]算法及其扩展
  9. ajax传图片的方法
  10. 美国ADP就业数据是什么?与非农有何关系
  11. 可方向导不一定连续的例子
  12. 使用Java对接永中格式转换
  13. emouse思·睿—评论与观点整理之三
  14. 【量化笔记】动量Momentum相关技术指标以其含义
  15. Graph U-Nets小结
  16. 月模拟题3 201609-3 炉石传说
  17. 佩尔(Pell)方程最小正整数解
  18. 基于kalman滤波的磨损预测算法matlab仿真
  19. JMeter基础使用教程及使用技巧(快速入门)
  20. Tomcat9的下载与安装

热门文章

  1. IDEA Maven 聚合项目(多模块)搭建--最精简
  2. 从“智能湖仓”升级看数据平台架构未来方向
  3. 下一代 Windows 将至,是全新的 Windows 11 还是 Windows 10 的延续?
  4. 特斯拉已在中国建立数据中心
  5. AI迎来重要发展契机,开发者的机会在哪里?
  6. 开发者的 Big Day!亚马逊 re:Invent 2020 参会学习攻略来啦~
  7. 开源项目征集 | CSDN “开源加速器计划”之【开源技术栈选型 Show】
  8. 基于选项模式实现.NET Core的配置热更新
  9. 全面开放运营3个月,百度揭秘Apollo最新技术创新
  10. 凿渠造舟:视频会议的昨天与明天