转载|网络编程中阻塞式函数的底层逻辑

逛知乎看到的,觉得写的挺透彻的,转载一下,原文链接:Unix网络编程里的阻塞是在操作系统的内核态创建一个线程来死循环吗?
原文以阻塞式的recv函数作为讲解,但是所有阻塞式的api底层逻辑基本相通。
下面是正文:

作者:张彦飞
链接:https://www.zhihu.com/question/492983429/answer/2236327954
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

大家天天都在说阻塞,实际上95%的程序员并没有真正理解阻塞是啥。这里并没有循环的事情,我们来从内核视角详细剖析一下阻塞到底是啥,它是如何工作的。把问题再具体一下,recv 接收数据阻塞的原理是啥? 理解了这个就能真正理解所有的阻塞了。用一段大家都熟悉的代码来举例!

int main()
{int sk = socket(AF_INET, SOCK_STREAM, 0);connect(sk, ...)recv(sk, ...)
}

在上面的 demo 中虽然只是简单的两三行代码,但实际上用户进程和内核配合做了非常多的工作。大致的工作流程如下:
看到这里,你可能还没看着阻塞的原理。别着急,往下看。我们来看 recv 函数依赖的底层实现。首先通过 strace 命令跟踪,可以看到 clib 库函数 recv 会执行到 recvfrom 系统调用。进入系统调用后,用户进程就进入到了内核态,通过执行一系列的内核协议层函数,然后到 socket 对象的接收队列中查看是否有数据,没有的话就把自己添加到 socket 对应的等待队列里。最后让出CPU,操作系统会选择下一个就绪状态的进程来执行。
整个流程图如下:

以上这个流程图是我根据 Linux 内核源码的执行过程总结后画出来的。注意上面的第四步和第五步。第四步中是在访问 sock 对象下面的接收队列,如果接收队列中还没有数据到达,那么就会进入第五步,把当前进程阻塞掉。但是在把自己阻塞掉之前,进程干了一件事, 给 socket 上留了个标记。告诉内核,如果这个 socket 上数据好了,记得叫我起来哈!就是源码 prepare_to_wait 函数中的 __add_wait_queue 这一句。

//file: kernel/wait.c
void
prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{unsigned long flags;wait->flags &= ~WQ_FLAG_EXCLUSIVE;spin_lock_irqsave(&q->lock, flags);if (list_empty(&wait->task_list))__add_wait_queue(q, wait);set_current_state(state);spin_unlock_irqrestore(&q->lock, flags);
}

接下来 Linux 就会选择下一个就绪状态的进程来执行。这就是阻塞原理的上半段,就是进程修改自己的状态,主动交出 CPU 的执行权。当有数据到达的时候,内核首先将数据包放到该 socket 的接收队列中。然后扫描一下 socket 等待队列,然后发现:“呦呵,有进程阻塞在这个 socket 上面哎,好唤醒它”。

具体到代码里就是 __wake_up_common 这个函数会访问 socket 的等待队列。

//file: kernel/sched/core.c
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,int nr_exclusive, int wake_flags, void *key)
{wait_queue_t *curr, *next;list_for_each_entry_safe(curr, next, &q->task_list, task_list) {unsigned flags = curr->flags;if (curr->func(curr, mode, wake_flags, key) &&(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)break;}
}

__wake_up_common 中找出一个等待队列项 curr,然后调用其回调函数 curr->func,来完成进程的唤醒。不过,要注意的是,这个唤醒只是把相应的进程放到可运行队列里而已。真正的执行还得等其它进程主动释放 CPU 或者是时间片到了之后,内核把其它进程拿下以后才能真正获得 CPU 并开始执行。
参考:图解 | 深入理解高性能网络开发路上的绊脚石 - 同步阻塞网络 IO说到这里,你可能还会问了。内核是如何接收包的,毕竟唤醒用户进程是它干的。难道它不是一个死循环么?是的,并不是。 网卡上收到数据包的时候,是通过硬中断唤醒内核进程处理,硬中断会触发软中断。有了软中断请求以后,ksoftirqd 内核线程才开始执行。来从网卡上取包,处理,放到接收队列,然后唤醒用户进程。
参见:图解Linux网络包接收过程
究其根源,是由网卡的硬中断来触发的。如果一段时间内没有网络包处理,那么没有死循环来消耗 CPU 的。对网络底层还有啥不理解的,来看看我的公众号「开发内功修炼」 或许可以帮你解开一些困惑。

Github: GitHub - yanfeizhang/coder-kung-fu: 开发内功修炼
哦对了,想理解多路复用,来看看我的这一篇吧,也是从源码角度深入分析的。图解 | 深入揭秘 epoll 是如何实现 IO 多路复用的!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/376782.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

把txt文件中的json字符串写到plist文件中

- (void)json2Plist {NSString *filePath [self applicationDocumentsDirectoryFileName:"json"];NSMutableArray *tempArray [[NSMutableArray alloc] initWithContentsOfFile:filePath];//第一次添加数据时,数组为空if (tempArray.count 0) {tempArray [NSMuta…

树的存储结构2 - 数据结构和算法42

树的存储结构 让编程改变世界 Change the world by program 孩子表示法 我们这次换个角度来考虑,由于树中每个结点可能有多棵子树,可以考虑用多重链表来实现。 就像我们虽然有计划生育,但我们还是无法确保每个家庭只养育一个孩子的冲动&a…

海量数据去重

海量数据去重 一个文件中有40亿条数据,每条数据是一个32位的数字串,设计算法对其去重,相同的数字串仅保留一个,内存限制1G. 方法一:排序 对所有数字串进行排序,重复的数据传必然相邻,保留第一…

Sharepoint 2013 发布功能(Publishing features)

一、默认情况下,在创建网站集时,只有选择的模板为‘ Publishing Portal(发布门户)’与‘ Enterprise Wiki(企业 Wiki)’时才默认启用发布功能,如下图所示: 二、发布功能包含两块&…

【原】android启动时白屏或者黑屏的问题

解决应用启动时白屏或者黑屏的问题 由于Activity只能到onResume时,才能展示到前台,所以,如果为MAIN activity设置背景的话,无论onCreate-onResume速度多快,都会出现短暂的白屏或者黑屏 其实解决的办法很简单&#xff0…

【草稿】windows + vscode 远程开发

主要分为三个步骤: 1、开启openssh服务 2、通过ssh命令连接到远程服务器 3、通过vscode连接远程服务器进行开发调试 ssh概念 SSH是较可靠,专为远程登陆会话和其他网络服务提供安全性得协议,利用ssh协议可以有效防止远程管理过程中得信息…

POJ3185(简单BFS,主要做测试使用)

没事做水了一道POJ的简单BFS的题目 这道题的数据范围是20,所以状态总数就是&#xff08;1<<20&#xff09; 第一次提交使用STL的queue&#xff0c;并且是在队首判断是否达到终点&#xff0c;达到终点就退出&#xff0c;超时&#xff1a;&#xff08;其实这里我是很不明白…

tomcat站点配置

tomcat版本&#xff1a;tomcat5.5.91、打开tomcat\conf\server.xml&#xff0c;在里面找到<Engine name"Catalina" defaultHost"localhost">.....</Engine>2、在<Engine name"Catalina" defaultHost"localhost"><…

新的视频会议模式:StarlineProject

目录效果展示部分用户参与度部分技术细节机械装置以及硬件配置。视频系统照明人脸跟踪压缩和传输图像渲染音频系统step1&#xff1a;捕获音频step2&#xff1a;音频去噪处理step3&#xff1a;压缩、传输、解压step4&#xff1a;渲染可以改进的点效果展示部分 〔映维网〕谷歌光场…

HDU 3934

/*这是用的有旋转卡壳的思想。 首先确定i&#xff0c;j&#xff0c;对k进行循环&#xff0c;知道找到第一个k使得cross(i,j,k)>cross(i,j,k1),如果ki进入下一次循环。 对j&#xff0c;k进行旋转&#xff0c;每次循环之前更新最大值&#xff0c;然后固定一个j&#xff0c;同样…

[ios] UILocalNotification实现本地的闹钟提醒【转】

http://www.cnblogs.com/jiangshiyong/archive/2012/06/06/2538204.html转载于:https://www.cnblogs.com/jinjiantong/archive/2013/04/01/2992624.html

sql server根据表中数据生成insert语句

几个收藏的根据数据库生成Insert语句的存储过程[修正版]----根据表中数据生成insert语句的存储过程--建立存储过程&#xff0c;执行spGenInsertSQL 表名--感谢playyuer----感谢szyicol--CREATEproc[dbo].[spGenInsertSQL](tablenamevarchar(256))asbegindeclaresqlvarchar(8000…

Javascript eval()函数 基础回顾

如果您想详细了解ev al和JSON请参考以下链接&#xff1a; eval &#xff1a;https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Global_Functions/Eval JSON&#xff1a;http://www.json.org/ eval函数的工作原理 eval函数会评估一个给定的含有JavaScript代码的…

杂感无题|

今天中午和组里面的人吃饭&#xff0c;聊起了科兴跳楼的事情。这事其实前几天我华为的mentor就转给我了&#xff0c;当时也没太在意&#xff0c;在脉脉上看了看&#xff0c;也不知晓是谁&#xff0c;想着可能又是抑郁症吧。 饭后依旧绕着食堂散步&#xff0c;ly说那个人好像还是…

uva1366_Martian Mining_简单DP

题目不难&#xff0c;却想了好长时间&#xff0c;目测自己DP还是很水。。。囧 思路&#xff1a;舍f[i][j]为前i行j列的最大矿总量不难推出状态转移方程为f[i][j]max(f[i-1][j]line[i][j],f[i][j-1]row[j][i]) 其中line[i][j]为第i行前j个A矿的和&#xff08;a[i][1]a[i][2]...a…

数学图形之Boy surface

这是一个姓Boy的人发现的,所以取名为Boy surface.该图形与罗马图形有点相似,都是三分的图形.它甚至可以说是由罗马曲面变化而成的. 本文将展示几种Boy曲面的生成算法和切图,使用自己定义语法的脚本代码生成数学图形.相关软件参见:数学图形可视化工具,该软件免费开源.QQ交流群: …

开个定时器给echarts组件配置定时更新

我在js文件中开了个定时器&#xff0c;每1s从后端获取数据并解析&#xff0c;然后用异步方法就渲染不出来&#xff0c;改成同步就可以了。 这个解决方法来自于这篇文章&#xff0c;我出的问题和他一样&#xff1a;关于ajax中readyState的值一直为1的问题 这里将ajax参数修改为f…

SDK 操作 list-view control 实例 -- 遍历进程

遍历窗口&#xff0c;获得控件句柄 1 EnumChildWindows(hwndDlg, (WNDENUMPROC)EnumChildProc, NULL); 回调函数 1 BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam )2 {3 char strCLSName[MAXBYTE] {0};4 GetClassName(hwnd, strCLSName, MAXBYTE);5 if (…

推荐一份不错的清除默认样式的CSS样式

时间过得真快&#xff0c;离 Reset CSS 研究&#xff08;八卦篇&#xff09; 已经 3 个多月了。废话少说&#xff0c;赶紧将技术篇写完吧。 回顾与反思 第一份 reset css 是 Tantek 的 undohtml.css, 很简单的代码&#xff0c;Tantek 根据自己的需要&#xff0c;对浏览器的默认…

python深浅拷贝

在python中&#xff0c;对象赋值实际上是对象的引用。当创建一个对象&#xff0c;然后把它赋给另一个变量的时候&#xff0c;python并没有拷贝这个对象&#xff0c;而只是拷贝了这个对象的引用。 所以一个结构类型被赋给另外一个对象的时候&#xff0c;尽可能不使用 &#xff…