日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2023/12/1 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 转载|网络编程中阻塞式函数的底层逻辑 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

逛知乎看到的,覺得寫的挺透徹的,轉載一下,原文鏈接: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 多路復用的!

總結

以上是生活随笔為你收集整理的转载|网络编程中阻塞式函数的底层逻辑的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。