偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

接收一個(gè)網(wǎng)絡(luò)包到底會(huì)用到多少個(gè)隊(duì)列?

網(wǎng)絡(luò) 通信技術(shù)
什么情況下會(huì)出現(xiàn)accpet socket沒有被用戶態(tài)進(jìn)程讀取呢?比如work thread通過read()讀取到一段數(shù)據(jù)后便開始直接處理這段數(shù)據(jù)而耽擱了下一段數(shù)據(jù)的讀取。

今天這篇文章,主要嘗試回答下面兩個(gè)問題:

  • 內(nèi)核從網(wǎng)卡那里收到一個(gè)網(wǎng)絡(luò)包,到最終將其所攜帶的payload完整遞交給應(yīng)用層,中間涉及到多少個(gè)隊(duì)列?
  • 為什么需要這么多各種各樣的隊(duì)列呢?

來吧,進(jìn)入正題。

1. 大圖介紹

照例,先來介紹一下為本文所準(zhǔn)備的大圖。這張圖是在之前的文章用圖之上修改而來,主要是添加了在TCP層所涉及到的隊(duì)列。據(jù)說這叫重復(fù)利用。

這張圖用來描繪內(nèi)核從物理網(wǎng)卡以及虛擬網(wǎng)卡接收到網(wǎng)絡(luò)包之后的數(shù)據(jù)流。

估計(jì)你注意到了圖中的 1(1.a、1.b),2(2.a、2.b、2.c),3 這樣的標(biāo)號(hào)。對(duì)內(nèi)核而言,1和2是網(wǎng)絡(luò)包的接收入口,而3是網(wǎng)絡(luò)包的處理入口。

具體來說,1和2表示線路1和線路2,它倆分別代表網(wǎng)絡(luò)包從物理網(wǎng)卡進(jìn)入內(nèi)核以及從虛擬網(wǎng)卡進(jìn)入內(nèi)核所涉及到的一些關(guān)鍵操作。標(biāo)號(hào)3表示的是內(nèi)核線程從這個(gè)入口位置獲取待處理網(wǎng)絡(luò)設(shè)備。

圖中最右邊是TCP/IP協(xié)議棧。對(duì)于一個(gè)skb而言,協(xié)議棧對(duì)其的處理是在內(nèi)核線程這個(gè)上下文中進(jìn)行的。了解到這點(diǎn)很重要,我們總得知道到底是誰在替我們負(fù)重前行。

圖中的藍(lán)色寬箭頭表示網(wǎng)絡(luò)包流向用戶態(tài)的數(shù)據(jù)通道。但箭頭在TCP層由實(shí)心變成了空心,這是因?yàn)閷?duì)于不同類型的網(wǎng)路包,用戶態(tài)所拿到的數(shù)據(jù)是不一樣的。在TCP層之下的所有層,大家處理的數(shù)據(jù)結(jié)構(gòu)都是skb。而到了TCP層則需要關(guān)心這個(gè)skb到底是與握手相關(guān)還是與數(shù)據(jù)包相關(guān)。

如你所料,skb穿過鏈路層和IP層的時(shí)候,會(huì)涉及到bridge-netfilter和netfilter(iptables)所設(shè)置的基于規(guī)則的過濾過程,還有路由過程。

圖 1:數(shù)據(jù)接收流程中的隊(duì)列鳥瞰圖

我們從左到右,從下至上,順著網(wǎng)絡(luò)包流過的路徑,看看沿途中會(huì)碰到哪些隊(duì)列。

我們說Network namespace用來隔離包括網(wǎng)卡(Network Interface)、回環(huán)設(shè)備(Loopback Device)、網(wǎng)絡(luò)棧、IP地址、端口等等在內(nèi)的網(wǎng)絡(luò)資源。下文所提的各類隊(duì)列也是這樣一種被隔離了的資源,所以圖1中所畫的所有這些隊(duì)列在不同的network ns中都各有一份。

2. RingBuffer

每個(gè)網(wǎng)卡在內(nèi)存里會(huì)有若干個(gè)隊(duì)列,每個(gè)這樣的隊(duì)列叫做RingBuffer。顧名思義,它是一個(gè)環(huán)形緩沖區(qū)。當(dāng)物理網(wǎng)卡收到網(wǎng)絡(luò)包,會(huì)通過DMA將其拷貝到RingBuffer。當(dāng)RingBuffer滿的時(shí)候,新來的數(shù)據(jù)包將給丟棄。

這是網(wǎng)絡(luò)包碰到的第一個(gè)隊(duì)列。那么誰負(fù)責(zé)將這個(gè)隊(duì)列里面的網(wǎng)絡(luò)包消費(fèi)掉呢?答案是內(nèi)核線程,也即圖中的ksoftirqd。詳見后文。

3. Per CPU 隊(duì)列

每個(gè)CPU有一個(gè)自己專屬的數(shù)據(jù)結(jié)構(gòu)softnet_data。其上附有兩個(gè)隊(duì)列poll_list和input_pkt_queue。這兩個(gè)隊(duì)列里的內(nèi)容都由ksoftirqd來消費(fèi)。圖1中所標(biāo)示的ksoftirqd/4表示這個(gè)內(nèi)核線程與第4個(gè)CPU核綁定在一起,也即它只會(huì)處理這個(gè)核所擁有的softnet_data上的數(shù)據(jù)。

3.1 input_pkt_queue

物理網(wǎng)卡由RingBuffer來緩存網(wǎng)絡(luò)包,那虛擬網(wǎng)卡要發(fā)送出去的數(shù)據(jù)暫存在哪里呢?如圖1中2.a所示,放在input_pkt_queue這個(gè)隊(duì)列里。這個(gè)過程是在函數(shù)enqueue_to_backlog()中完成的。

3.2 poll_list

所有的待處理的網(wǎng)卡會(huì)掛到每個(gè)CPU專屬的poll_list上。我們可以將poll_list想象成晾曬香腸的架子,而每個(gè)網(wǎng)絡(luò)設(shè)備則如同香腸一樣掛到架子上面等待ksoftirqd處理。

待處理的網(wǎng)卡包括物理網(wǎng)卡和虛擬網(wǎng)卡。簡單地來說,只要需要圖1中的內(nèi)核線程處理網(wǎng)絡(luò)包,就需要將這個(gè)網(wǎng)卡掛載到poll_list隊(duì)列上。

那么這個(gè)掛載動(dòng)作是由誰完成的呢?

  • 對(duì)于物理網(wǎng)卡,由中斷服務(wù)程序負(fù)責(zé)將網(wǎng)卡掛到poll_list上。如圖1中步驟1.a所示。
  • 對(duì)于虛擬網(wǎng)卡,如veth或者lo,則在enqueue_to_backlog()函數(shù)中將虛擬網(wǎng)卡掛到poll_list上。如圖1中步驟2.b所示。

到這里,我們已經(jīng)碰到了三種不同的隊(duì)列了,前兩者緩存數(shù)據(jù),而后者緩存設(shè)備列表。其中RingBuffer和input_pkt_queue隊(duì)列都用于緩存網(wǎng)絡(luò)包,只是一個(gè)服務(wù)的對(duì)象是物理網(wǎng)卡而另一個(gè)則是虛擬網(wǎng)卡。poll_list隊(duì)列用于緩存需要內(nèi)核線程處理的設(shè)備。

無論網(wǎng)絡(luò)包是位于哪個(gè)隊(duì)列里,內(nèi)核線程的啟動(dòng)意味著網(wǎng)絡(luò)包開始進(jìn)入TCP/IP協(xié)議棧。下面我們來看看在協(xié)議棧處理過程中用到的隊(duì)列有哪幾個(gè)。

4. listening socket所用隊(duì)列

對(duì)于服務(wù)器而言,一個(gè)典型的架構(gòu)是 “監(jiān)聽線程+工作線程池” 組合。下面是偽代碼。

void main(){
int listening_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// 綁定 ip 和端口
bind(listening_socket, ...);

// 監(jiān)聽
listen(listening_socket, 3 /*backlog*/);

while(accept_socket = accept(listening_socket)){
// 將accept_socket交給一個(gè)worker thread去讀取網(wǎng)絡(luò)數(shù)據(jù)
pthread_t worker_thread;
new_sock = malloc(sizeof(int));
*new_sock = client_sock;

// 創(chuàng)建一個(gè)工作線程
if( pthread_create( &worker_thread , NULL , connection_handler , (void*) new_sock) < 0)
{
return 1;
}
...
}
}

這段代碼的骨架挺簡單,主線程為listening thread,用于創(chuàng)建一個(gè)listening_socket,并負(fù)責(zé)基于它來接收客戶端的TCP連接。每一次客戶端與服務(wù)器的成功連接都會(huì)使得accept()函數(shù)返回一個(gè)accept_socket,listening thread還會(huì)創(chuàng)建一個(gè)work thread并讓它基于accept_socket與客戶端通信。

這些work thread匯聚成了一個(gè)工作線程池。當(dāng)然,實(shí)際工作的代碼可不會(huì)創(chuàng)建無數(shù)個(gè)work thread,當(dāng)已創(chuàng)建的work thread數(shù)量到達(dá)一個(gè)閾值后,創(chuàng)建動(dòng)作就需要轉(zhuǎn)變成從線程池中提溜一個(gè)線程出來這樣的操作。

我將這段代碼中與本文相關(guān)的關(guān)鍵點(diǎn)列在這里:

  • 這段代碼完成了 “監(jiān)聽線程+工作線程池” 組合這樣的骨架。
  • 監(jiān)聽線程操作的socket是listening_socket。
  • 監(jiān)聽線程針對(duì)listening_socket,設(shè)置了一個(gè)大小為3的backlog。
  • 工作線程操作的socket是accept_socket。

在內(nèi)核中,為每個(gè)listening socket 維護(hù)了兩個(gè)隊(duì)列,它們都與連接管理相關(guān)。

  • 已經(jīng)建立了連接的隊(duì)列,這些連接暫時(shí)還沒有被work thread領(lǐng)走。隊(duì)列里面的每個(gè)連接已經(jīng)完成了三次握手,且處于ESTABLISHED狀態(tài)。這個(gè)隊(duì)列的名字叫 icsk_accept_queue,如圖1中accept_queue所示 。
  • 還沒有完全建立連接的隊(duì)列,隊(duì)列里面的每個(gè)連接還沒有完成三次握手,處于 SYN_REVD 的狀態(tài)。這個(gè)隊(duì)列也叫半連接隊(duì)列,syn queue。

示例代碼中,在調(diào)用listen()函數(shù)的時(shí)候,將backlog設(shè)置為3。它的作用其實(shí)是在控制這個(gè)icsk_accept_queue的大小。而syn queue大小則可以通過 /proc/sys/net/ipv4/tcp_max_syn_backlog配置。

服務(wù)端調(diào)用 accept() 函數(shù),其實(shí)是從第一個(gè)隊(duì)列icsk_accept_queue中拿出一個(gè)已經(jīng)完成的連接進(jìn)行數(shù)據(jù)處理。如果這個(gè)隊(duì)列里是空的,那表示目前還沒有已完成握手的連接,那就把listening thread阻塞等待吧,反正它暫時(shí)也沒其它事可做。

5. accept socket所用隊(duì)列

每個(gè)accept socket包含有4種不同的隊(duì)列:backlog隊(duì)列、prequeue隊(duì)列、sk_receive_queue隊(duì)列和out_of_order_queue隊(duì)列。

其中prequeue隊(duì)列在17年后的Linux版本中已經(jīng)取消了,故本文略過這個(gè)隊(duì)列。

5.1 backlog 隊(duì)列

當(dāng)網(wǎng)絡(luò)包到達(dá)TCP,但是與之相關(guān)的accpet socket沒有被用戶態(tài)進(jìn)程讀取中,那么協(xié)議棧會(huì)調(diào)用tcp_add_backlog()將這個(gè)網(wǎng)絡(luò)包暫存至backlog隊(duì)列中。這樣做的目的是讓內(nèi)核線程可以盡快處理下一個(gè)網(wǎng)絡(luò)包。

什么情況下會(huì)出現(xiàn)accpet socket沒有被用戶態(tài)進(jìn)程讀取呢?比如work thread通過read()讀取到一段數(shù)據(jù)后便開始直接處理這段數(shù)據(jù)而耽擱了下一段數(shù)據(jù)的讀取。

注意這個(gè)地方的backlog隊(duì)列和前文listening socket處所提及的backlog參數(shù)是兩回事。

5.2 sk_receive_queue和out_of_order_queue隊(duì)列

當(dāng)然如果work thread因?yàn)檎{(diào)用read()被阻塞了,表示它正在這個(gè)accpet socket上急切地等待數(shù)據(jù)的到來,這個(gè)時(shí)候協(xié)議棧就會(huì)把網(wǎng)絡(luò)包優(yōu)先通過函數(shù)skb_copy_datagram_msg()直接給它了。但work thread處理能力也有限度,一直給它喂數(shù)據(jù)也會(huì)噎死它,那更多的網(wǎng)絡(luò)包就需要sk_receive_queue隊(duì)列和out_of_order_queue隊(duì)列的幫忙了。

sk_receive_queue隊(duì)列的作用很好理解,它里面存放的是按照seq number排好序的數(shù)據(jù)。但我們都知道跨internet的傳輸會(huì)使得網(wǎng)絡(luò)包以亂序方式到達(dá),這個(gè)時(shí)候就需要把這些亂序的包先放到out_of_order_queue隊(duì)列排隊(duì)了。

應(yīng)用程序可以讀取到sk_receive_queue隊(duì)列和backlog隊(duì)列中的內(nèi)容,但無法直接訪問out_of_order_queue隊(duì)列。為了體現(xiàn)這一點(diǎn),二哥在圖1中特意做了處理:out_of_order_queue隊(duì)列沒有出現(xiàn)在通往用戶態(tài)的數(shù)據(jù)通道上。當(dāng)協(xié)議棧發(fā)現(xiàn)out_of_order_queue隊(duì)列中的亂續(xù)包和新到的包可以拼湊成完整有序的數(shù)據(jù)流后,就將網(wǎng)絡(luò)包從out_of_order_queue隊(duì)列移動(dòng)到sk_receive_queue隊(duì)列。

5.3 消費(fèi)隊(duì)列

work thread所調(diào)用的read()函數(shù)在內(nèi)核態(tài)最終通過函數(shù)tcp_recvmsg()來讀取暫存在sk_receive_queue中的數(shù)據(jù)。

每次這個(gè)sk_receive_queue隊(duì)列中的內(nèi)容處理完畢后,tcp_recvmsg()還會(huì)繼續(xù)處理backlog隊(duì)列里面累積的網(wǎng)絡(luò)包。

6. 為什么需要隊(duì)列

行文至此,我們來回答文首的第二個(gè)問題:為什么需要這么多各種各樣的隊(duì)列呢?

答案是:效率。

把整個(gè)面向TCP連接的網(wǎng)絡(luò)包接收處理流程稍作總結(jié),我們會(huì)發(fā)現(xiàn)重要的參與者有如下幾個(gè):

  • 網(wǎng)卡,包括物理的和虛擬的網(wǎng)卡:負(fù)責(zé)接收網(wǎng)絡(luò)包。
  • 內(nèi)核線程:消費(fèi)網(wǎng)絡(luò)包,負(fù)責(zé)調(diào)用TCP/IP協(xié)議棧函數(shù)將亂序到達(dá)的網(wǎng)絡(luò)包整理還原成data streaming。
  • 應(yīng)用程序:接收、消費(fèi)data streaming。

網(wǎng)卡和內(nèi)核線程操作的對(duì)象都是網(wǎng)絡(luò)包,只是各自關(guān)注的焦點(diǎn)不同而已。它們完成自己負(fù)責(zé)的操作任務(wù)后,需要盡快地將網(wǎng)絡(luò)包交給繼任者,以便抽身去處理下一個(gè)網(wǎng)絡(luò)包。遞交網(wǎng)絡(luò)包的時(shí)候,繼任者可能正在忙,你總不能在那邊傻等對(duì)吧?這個(gè)時(shí)候隊(duì)列的出現(xiàn)就起到了很好的緩沖作用。

比如圖1中的內(nèi)核線程就是這樣,它不斷地從poll_list里面拿出需要處理的網(wǎng)卡并處理網(wǎng)卡里的網(wǎng)絡(luò)包。處理好的網(wǎng)絡(luò)包送進(jìn)下文所說的幾個(gè)隊(duì)列中留待繼任者繼續(xù)處理。

我們還可以將所有這些參與者想象成制造業(yè)價(jià)值流(源于精益原則)中不同的工作中心。在這個(gè)價(jià)值流中,不同的工作中心之間通常會(huì)轉(zhuǎn)移各自的輸出(半)成品,并通過倉庫來進(jìn)行一定程度的緩存,倉庫類似本文的隊(duì)列。

7. 總結(jié)

文末做一個(gè)總結(jié)。

為了可以高效地處理網(wǎng)絡(luò)包,同時(shí)又可以讓接收數(shù)據(jù)的各個(gè)重要組成模塊以松耦合的方式合作,各式各樣的隊(duì)列參與了網(wǎng)絡(luò)包的接收過程。

  • RingBuffer和input_pkt_queue最先用于緩存網(wǎng)卡所接收到的網(wǎng)絡(luò)包。
  • poll_list用于告訴內(nèi)核線程,當(dāng)前有哪些網(wǎng)絡(luò)設(shè)備正在排隊(duì)等待它的處理。
  • Server端一般用到listening socket和accept socket。

listening socket用于監(jiān)聽客戶端的連接并負(fù)責(zé)生成后者,它維護(hù)了兩個(gè)隊(duì)列,分別用于緩存已經(jīng)握手成功的但還沒有被工作線程領(lǐng)走的連接和還未完成三次握手的連接。

accept socket用于針對(duì)具體的連接進(jìn)行數(shù)據(jù)通信。它用到了backlog、sk_receive_queue和out_of_order_queue這三個(gè)隊(duì)列。

文中所用高清大圖已傳至二哥的github:https://github.com/LanceHBZhang/LanceAndCloudnative。

責(zé)任編輯:武曉燕 來源: 二哥聊云原生
相關(guān)推薦

2023-09-26 16:44:14

光模塊

2020-08-04 16:56:50

Java方法參數(shù)

2023-09-04 08:08:59

2020-06-16 11:00:40

線程Java代碼

2019-01-08 09:23:16

Java字符串編碼

2024-11-06 08:49:46

2020-11-11 10:10:20

調(diào)用函數(shù)參數(shù)變量

2023-06-25 10:04:50

自動(dòng)駕駛智能

2019-12-20 09:31:23

TCPHTTP瀏覽器

2019-05-29 15:17:43

TCPHTTPSSL

2019-07-09 06:13:09

TCPHTTP網(wǎng)絡(luò)協(xié)議

2020-12-25 13:13:22

程序員數(shù)據(jù)軟件

2013-03-06 17:27:36

僵尸網(wǎng)絡(luò)

2019-12-19 10:28:46

5G基站網(wǎng)絡(luò)

2020-07-20 15:20:44

ThreadLocalJava多線程

2019-12-16 09:26:05

Java設(shè)計(jì)操作系統(tǒng)

2021-03-29 08:47:24

線程面試官線程池

2019-01-02 16:31:33

程序員技術(shù)互聯(lián)網(wǎng)

2019-11-14 16:05:29

TCPHTTP前端

2019-01-21 16:54:24

車聯(lián)網(wǎng)智能手機(jī)系統(tǒng)iOS
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)