美文网首页
【IO】 I/O 多路复用解析

【IO】 I/O 多路复用解析

作者: Bogon | 来源:发表于2023-06-20 00:01 被阅读0次
image.png

关于fd你可以简单理解为指向socket资源的一个指针,因为内核只返回给我们socket的fd,所以我们对socket的操作都是通过这个fd。

image.png image.png image.png image.png

用户空间为什么要拷贝fd到内核空间呢?
因为用户态不能执行内核操作。

网络数据包传输过来,解析端口号可以找到对应的进程,但是是怎么找到对应的socket的呢?
根据来源ip和端口号,目标ip 和端口号,就可以定位到。

image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png

讨论了 select/poll 几个缺点,针对这几个缺点,就需要解决以下几件事:

  • 如何突破文件描述符数量的限制
  • 如何避免用户态和内核态对文件描述符集合的拷贝
  • socket 就绪后,如何避免线性遍历文件描述符集合

如何突破文件描述符数量的限制?
其实 poll 已经解决了,poll 使用的是链表的方式管理 socket 描述符,但问题是不够高效,如果有百万级别的连接需要管理,如何快速的插入和删除就变得很重要,于是 epoll 采用了红黑树的方式进行管理,这样能保证在添加 socket 和删除 socket 时,有 O(log(n)) 的复杂度。

如何避免用户态和内核态对文件描述符集合的拷贝?
其实对于 select 来说,由于这个集合是保存在用户态的,所以当调用 select 时需要屡次的把这个描述符集合拷贝到内核空间。所以如果要解决这个问题,可以直接把这个集合放在内核空间进行管理。没错,epoll 就是这样做的,epoll 在内核空间创建了一颗红黑树,应用程序直接把需要监控的 socket 对象添加到这棵树上,直接从用户态到内核态了,而且后续也不需要再次拷贝了。

socket就绪后,如何避免内核线性遍历文件描述符集合?
这个问题就会比较复杂,要完整理解就得涉及到内核收包到应用层的整个过程。
这里先简单讲一下,与 select 不同,epoll 使用了一个双向链表来保存就绪的 socket,这样当活跃连接数不多的情况下,应用程序只需要遍历这个就绪链表就行了,而 select 没有这样一个用来存储就绪 socket 的东西,导致每次需要线性遍历所有socket,以确定是哪个或者哪几个 socket 就绪了。这里需要注意的是,这个就绪链表保存活跃链接,数量是较少的,也需要从内核空间拷贝到用户空间。

从上面 3 点可以看到 epoll 的几个特点:

  • 程序在内核空间开辟一块缓存,用来管理 epoll 红黑树,高效添加和删除
  • 红黑树位于内核空间,用来直接管理 socket,减少和用户态的交互
  • 使用双向链表缓存就绪的 socket,数量较少
  • 只需要拷贝这个双向链表到用户空间,再遍历就行,注意这里也需要拷贝,没有共享内存

当一个包从网卡进来之后,是如何走到应用程序的呢?中间经过了哪些步骤呢?

  1. 进程用户态创建Socket.
  2. 调用read后,如果当前Socket数据接受队列上没有数据时则会将当前进程阻塞掉,修改当前进程状态,并让出CPU使用权,一直等待到有数据包的到来,这里当前进程已经让出CPU使用权,CPU已经不在对当前进程进行调度.
  3. 数据包到达网卡后,通过DMA技术将本次网络包复制到RingBuffer中.
  4. 网卡通过给CPU特定针脚发送电压变化来通知CPU有网络包到来,也就是网络包到来硬中断.
  5. CPU简单处理后发送软中断,此时内核线程ksoftirqd来进行处理.
  6. 内核线程根据特定软中断信号执行网络包接受处理函数,其会将网络包从RingBuffer中取出来放入到指定Socket的数据就绪队列中.
  7. 唤醒用户进程,CPU重新调度,调度到之后,进入内核态读取Socket数据就绪队列中的数据包到用户态.

简单总结一下收包以及触发的过程:

  1. 包从网卡进来
  2. 一路经过各个子系统到达内核协议栈(传输层)
  3. 内核根据包的 {src_ip:src_port, dst_ip:dst_port} 找到 socket 对象(内核维护了一份四元组和 socket 对象的一一映射表)
  4. 数据包被放到 socket 对象的接收缓冲区
  5. 内核唤醒 socket 对象上的等待队列中的进程,通知 socket 事件
  6. 进程唤醒,处理 socket 事件(read/write)

参考

I/O 多路复用解析
https://www.bilibili.com/video/BV1r54y1f7bU

相关文章

网友评论

      本文标题:【IO】 I/O 多路复用解析

      本文链接:https://www.haomeiwen.com/subject/rmaeydtx.html