Redian新闻
>
Linux五大网络IO模型图解

Linux五大网络IO模型图解

公众号新闻

用户空间与内核空间

  对于一个应用程序即一个操作系统进程来说,它既有内核空间(与其他进程共享),也有用户空间(进程私有),它们都是处于虚拟地址空间中。用户进程是无法访问内核空间的,它只能访问用户空间,通过用户空间去内核空间复制数据,然后进行处理。为了避免用户应用导致冲突甚至内核崩溃,所以进程的寻址空间就划分为两部分:内核空间、用户空间。

  用户空间:只能执行受限的命令(Ring3),而且不能直接调用系统资源,必须通过内核提供的接口来访问
  内核空间:可以执行特权命令(Ring0),调用一切系统资源
  写数据时,要把用户缓冲数据拷贝到内核缓冲区,然后写入设备; 读数据时,要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区

  

阻塞io(同步io)

  发起请求就一直等待,直到数据返回。全程阻塞在第一阶段 用户进程尝试读取数据时,此时数据尚未到达,内核需要等待数据,此时用户进程处于阻塞状态;第二阶段 数据拷贝到内核缓冲区后 此时已就绪,然后将数据拷贝到用户进程缓冲区,拷贝的过程中用户进程也是阻塞状态。(好比你去商场试衣间,里面有人,那你就一直在门外等着)

非阻塞io(同步io)

  不管有没有数据都返回,没有就隔一段时间再来请求,如此循环。复制数据时阻塞在第一阶段 用户进程尝试读取数据时,此时数据尚未到达,就返回异常给用户进程,用户进程拿到error后,就再次重复尝试,直到数据就绪;第二阶段 数据拷贝到内核缓冲区后 此时已就绪,然后将数据拷贝到用户进程缓冲区,拷贝的过程中用户进程也是阻塞状态。(好比你要喝水,水还没烧开,你就隔段时间去看一下饮水机,直到水烧开为止,水烧开了就等在那里接水喝)

  可以看到,非阻塞IO模型中,用户进程在第一个阶段是非阻塞,第二个阶段是阻塞状态。虽然是非阻塞,但性能并没有得到提高。而且忙等机制会导致CPU空转,CPU使用率暴增。无论是阻塞IO还是非阻塞IO,用户应用在一阶段都需要调用recvfrom来获取数据,差别在于无数据时的处理方案: 

  如果调用recvfrom时,恰好没有数据,阻塞IO会使CPU阻塞,非阻塞IO使CPU空转,都不能充分发挥CPU的作用。
  如果调用recvfrom时,恰好有数据,则用户进程可以直接进入第二阶段,读取并处理数据

  

io多路复用(同步io)

  文件描述符(File Descriptor):简称FD,是一个从0 开始的无符号整数,用来关联Linux中的一个文件。在Linux中,一切皆文件,例如常规文件、视频、硬件设备等,当然也包括网络套接字(Socket)。
  IO多路复用:是利用单个线程来同时监听多个FD,并在某个FD可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。检查FD就绪时阻塞复制数据时阻塞。它的实现有三种方式   select   poll   epoll。

   

select

// 定义类型别名 __fd_mask,本质是 long inttypedef long int __fd_mask;
/* fd_set 记录要监听的fd集合,及其对应状态 */typedef struct { // fds_bits是long类型数组,长度为 1024/32 = 32 // 共1024个bit位,每个bit位代表一个fd,0代表未就绪,1代表就绪 __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS]; // ...} fd_set;
// select函数,用于监听fd_set,也就是多个fd的集合int select( int nfds, // 要监视的fd_set的最大fd + 1 fd_set *readfds, // 要监听读事件的fd集合 fd_set *writefds,// 要监听写事件的fd集合 fd_set *exceptfds, // // 要监听异常事件的fd集合 // 超时时间,null-用不超时;0-不阻塞等待;大于0-固定等待时间 struct timeval *timeout);

  可以看到 select 函数 里面有5个属性。ndfs 用来遍历fd集合的,当遍历到最大值就说明集合遍历完了;timeout 超时时间;fd_set 读/写/异常 三种集合,可以看到他是一个长度为32的数组,每个元素是32bit,每个数组1024bit 也就是每种fd的监听上限个数是1024个。

  1 建立连接时创建 fd_set ,然后用户进程调用 select 函数。2 内核进程在 timeout 前遍历 fd_set 等待数据就绪或超时,数据准备好后 就从内核空间 缓冲区 拷贝到 用户空间 缓冲区,并返回就绪的df个数。3 用户进程遍历整个 fd_set 找到就绪的 fd 。

  

poll

/
/ pollfd 中的事件类型#define POLLIN     //可读事件#define POLLOUT    //可写事件#define POLLERR    //错误事件#define POLLNVAL   //fd未打开
// pollfd结构struct pollfd { int fd; /* 要监听的fd */ short int events; /* 要监听的事件类型:读、写、异常 */ short int revents;/* 实际发生的事件类型 */};
// poll函数int poll( struct pollfd *fds, // pollfd数组,可以自定义大小 nfds_t nfds, // 数组元素个数 int timeout // 超时时间);


  poll 相比于 select 做了几点优化。首先 poll 函数的 fds 数组大小可以自定义,理论上无限大。(由于需要遍历整个数组找到就绪的 fd ,所以他不可能特别的大);其次所有的 fd 事件都封装在 pollfd 类型里面,包括 fd句柄 监听的事件 实际发生的事件。

epoll


struct eventpoll {    //...    struct rb_root  rbr; // 一颗红黑树,记录要监听的FD    struct list_head rdlist;// 一个链表,记录就绪的FD    //...};
// 1.创建一个epoll实例,内部是event poll,返回对应的句柄epfdint epoll_create(int size);
// 2.将一个FD添加到epoll的红黑树中,并设置ep_poll_callback// callback触发时,就把对应的FD加入到rdlist这个就绪列表中int epoll_ctl( int epfd, // epoll实例的句柄 int op, // 要执行的操作,包括:ADD、MOD、DEL int fd, // 要监听的FD struct epoll_event *event // 要监听的事件类型:读、写、异常等);
// 3.检查rdlist列表是否为空,不为空则返回就绪的FD的数量int epoll_wait( int epfd, // epoll实例的句柄 struct epoll_event *events, // 空event数组,用于接收就绪的FD int maxevents, // events数组的最大长度 int timeout // 超时时间,-1用不超时;0不阻塞;大于0为阻塞时间);


  1 建立连接时创建 fd_set ,然后用户进程调用 epoll_create 函数,创建 eventpoll 对象,里面通过 红黑树 记录要监听的 fd,还有一个 链表 记录就绪的 fd。2 调用 epoll_ctl 函数往红黑树中添加要监听的 fd ,并且为每一个 fd 函数添加 callback 函数,当 callback 触发时,就把相应的 fd 添加到 就绪链表中(返回具体的 fd 和 fd 数量,而不是像 select/poll 那样只返回个数量)。3 调用 epoll_wait 等待并检查链表是否为空,然后对具体的 fd 进行操作,同时将 fd 从链表中摘除。(多个进程监听同一个 eventpoll 时,调用epoll_wait 监听链表,当任意一个fd就绪时,所有的进程都会被通知到,这种就是惊群现象。)

  1 基于epoll实例中的红黑树保存要监听的FD,理论上无上限,而且增删改查效率都非常高
  2 每个FD只需要执行一次epoll_ctl添加到红黑树,以后每次epol_wait无需传递任何参数,无需重复拷贝FD到内核空间
  3 利用ep_poll_callback机制来监听FD状态,无需遍历所有FD,因此性能不会随监听的FD数量增多而下降

  

事件通知机制

  当FD有数据可读时,我们调用epoll_wait(或者select、poll)可以得到通知。但是事件通知的模式有两种:LevelTriggered:简称LT,也叫做水平触发,默认是这种。只要某个FD中有数据可读,每次调用epoll_wait都会得到通知。EdgeTriggered:简称ET,也叫做边沿触发。只有在某个FD有状态变化时,调用epoll_wait才会被通知。


LT 举个栗子:  1. 假设一个客户端socket对应的FD已经注册到了epoll实例中  2. 客户端socket发送了2kb的数据  3. 服务端调用epoll_wait,得到通知说FD就绪  4. 服务端从FD读取了1kb数据   5. 回到步骤3(再次调用epoll_wait,形成循环)

如果是 LT 模式,则事件通知频率较高,需要重复进行通知 完成对整个数据流操作,影响性能。

==============
ET 流程的话,它仅通知一次,效率高,我们只需要对FD操作一次,在第四步 读取1kb之后就不管了。所以我们需要手动修改策略,要么每次操作fd后 如果有剩余数据,则把fd放入链表,循环操作;要么一次性读取整个fd。


信号驱动io(同步io)

  事先发出一个请求,当有数据后会返回一个标识回调,这时你可以去请求数据。复制数据时阻塞。信号驱动IO是与内核建立SIGIO的信号关联并设置回调,当内核有FD就绪时,会发出SIGIO信号通知用户,期间用户应用可以执行其它业务,无需阻塞等待。用户进程收到SIGIO信号就调用recvfrom读取内核空间缓存区数据。(好比银行排号,当叫到你的时候,你就可以去处理业务了)。

  

异步io

  发出请求就返回,剩下的事情会异步自动完成,不需要做任何处理。(好比有事秘书干,自己啥也不用管)  

  用户进程调用aio_read,创建信号回调函数,然后内核等待数据就绪,用户进程无需阻塞,可以做任何事情。当内核数据就绪,就把数据从内核数据拷贝到用户空间。拷贝完成,内核递交信号触发aio_read中的回调函数,用户进程处理数据

  

总结

  

链接:https://www.cnblogs.com/wlwl/p/10291397.html

(版权归原作者所有,侵删)


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
在Linux中,如何在Linux中使用Ansible进行自动化部署?《我的完美日常》(Perfect Days)以茶和器宴春,五大名窑+五大名茶揽入怀!美丽的博纳维尔盐滩(Bonneville salt flats )Linux 网络参数和 ifconfig2009 年首度出现,至今感染 40 万台 Linux 主机,研究人员警告古老僵尸网络病毒 Ebury 卷土重来Linux网络参数和ifconfig雪之随想用基础模型指导特征传播,首个泛化型图像匹配器OmniGlue搞定未见过域Linux之父 Linus Torvalds 编译 arm64 Linux 内核又有 “ 新欢 ”:Ampere AArch64太丰富!加拿大网红小哥被中国Tims餐厅折服,中国特色的贝果很惊艳!还有这些神奇的搭配...什么是系统调用机制?结合Linux0.12源码图解linux网络运维命令--route[热议]"抢工作, 推高房价"!加拿大网络掀起仇恨言论! 留学生不敢发声!“活不起”!加拿大网红夫妇带着3个娃逃离,永久移居亚洲...Lindroid 开源应用:在安卓手机 / 平板上安装 GNU / Linux 发行版熵泱——第三十四章《夢想集中營》Linux 有多重要?这么说吧,只要是干 IT 相关的,学 Linux 是绕不过去的 “ 坎儿 ”Linux 之父讽刺AI炒作:“很搞笑,大概我也会被大模型取代”再见了,三大网购平台!Linux之父讽刺AI炒作:很搞笑,大概我也会被大模型取代小摩面试官让我当场估值建模,还必须用LBO模型!我慌了…Red Hat Linux 企业版云部署定价模型调整Linux之父 Linus Torvalds 在技术上可谓是从不私藏,也绝不闭门造车一顿饭的事儿,搞懂 Linux 5 种 IO 模型这里简直人间天堂!加拿大网红夫妇带娃移居亚洲,生活幸福感爆棚!加拿大网友被物价整疯!$164在Costco买了个寂寞!单人早餐40刀GPT-4o模型发布 免费;曝小米15系列原价位段难守;Vision Pro通过3C认证毫无幸福感!加拿大网红夫妇带3娃永久移居亚洲!Linux 一社区封杀大模型代码!“shit”7次出现在小作文,网友:此举非常明智!铁厂千万粉丝大网红,可以随叫随到?Linux常用的网络命令"抢工作, 推高房价"!加拿大网络掀起仇恨言论! 留学生不敢发声!OpenAI一夜干翻语音助手!GPT-4o模型强到恐怖,ChatGPT学会看屏幕,现实版Her来了
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。