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

127.0.0.1 之本機(jī)網(wǎng)絡(luò)通信過程知多少 ?!

網(wǎng)絡(luò) 通信技術(shù)
今天咱們就把 127.0.0.1 的網(wǎng)絡(luò) IO 問題搞搞清楚!為了方便討論,我把這個(gè)問題拆分成兩問:127.0.0.1 本機(jī)網(wǎng)絡(luò) IO 需要經(jīng)過網(wǎng)卡嗎?和外網(wǎng)網(wǎng)絡(luò)通信相比,在內(nèi)核收發(fā)流程上有啥差別?

[[401933]]

本文轉(zhuǎn)載自微信公眾號(hào)「開發(fā)內(nèi)功修煉」,作者張彥飛allen。轉(zhuǎn)載本文請(qǐng)聯(lián)系開發(fā)內(nèi)功修煉公眾號(hào)。

大家好,我是飛哥!

我們拆解完了 Linux 網(wǎng)絡(luò)包的接收過程,也搞定了網(wǎng)絡(luò)包的發(fā)送過程。內(nèi)核收發(fā)網(wǎng)絡(luò)包整體流程就算是摸清楚了。

正在飛哥對(duì)這兩篇文章洋洋得意的時(shí)候,收到了一位讀者的發(fā)來的提問:“飛哥, 127.0.0.1 本機(jī)網(wǎng)絡(luò) IO 是咋通信的”。額,,這題好像之前確實(shí)沒講到。。

現(xiàn)在本機(jī)網(wǎng)絡(luò) IO 應(yīng)用非常廣。在 php 中 一般 Nginx 和 php-fpm 是通過 127.0.0.1 來進(jìn)行通信的。在微服務(wù)中,由于 side car 模式的應(yīng)用,本機(jī)網(wǎng)絡(luò)請(qǐng)求更是越來越多。所以,我想如果能深度理解這個(gè)問題在實(shí)踐中將非常的有意義,在此感謝@文武 的提出。

今天咱們就把 127.0.0.1 的網(wǎng)絡(luò) IO 問題搞搞清楚!為了方便討論,我把這個(gè)問題拆分成兩問:

127.0.0.1 本機(jī)網(wǎng)絡(luò) IO 需要經(jīng)過網(wǎng)卡嗎?

和外網(wǎng)網(wǎng)絡(luò)通信相比,在內(nèi)核收發(fā)流程上有啥差別?

鋪墊完畢,拆解正式開始!!

一、跨機(jī)網(wǎng)路通信過程

在開始講述本機(jī)通信過程之前,我們還是先回顧一下跨機(jī)網(wǎng)絡(luò)通信。

1.1 跨機(jī)數(shù)據(jù)發(fā)送

從 send 系統(tǒng)調(diào)用開始,直到網(wǎng)卡把數(shù)據(jù)發(fā)送出去,整體流程如下:

在這幅圖中,我們看到用戶數(shù)據(jù)被拷貝到內(nèi)核態(tài),然后經(jīng)過協(xié)議棧處理后進(jìn)入到了 RingBuffer 中。隨后網(wǎng)卡驅(qū)動(dòng)真正將數(shù)據(jù)發(fā)送了出去。當(dāng)發(fā)送完成的時(shí)候,是通過硬中斷來通知 CPU,然后清理 RingBuffer。

不過上面這幅圖并沒有很好地把內(nèi)核組件和源碼展示出來,我們?cè)購拇a的視角看一遍。

等網(wǎng)絡(luò)發(fā)送完畢之后。網(wǎng)卡在發(fā)送完畢的時(shí)候,會(huì)給 CPU 發(fā)送一個(gè)硬中斷來通知 CPU。收到這個(gè)硬中斷后會(huì)釋放 RingBuffer 中使用的內(nèi)存。

1.2 跨機(jī)數(shù)據(jù)接收

當(dāng)數(shù)據(jù)包到達(dá)另外一臺(tái)機(jī)器的時(shí)候,Linux 數(shù)據(jù)包的接收過程開始了。

當(dāng)網(wǎng)卡收到數(shù)據(jù)以后,CPU發(fā)起一個(gè)中斷,以通知 CPU 有數(shù)據(jù)到達(dá)。當(dāng)CPU收到中斷請(qǐng)求后,會(huì)去調(diào)用網(wǎng)絡(luò)驅(qū)動(dòng)注冊(cè)的中斷處理函數(shù),觸發(fā)軟中斷。ksoftirqd 檢測(cè)到有軟中斷請(qǐng)求到達(dá),開始輪詢收包,收到后交由各級(jí)協(xié)議棧處理。當(dāng)協(xié)議棧處理完并把數(shù)據(jù)放到接收隊(duì)列的之后,喚醒用戶進(jìn)程(假設(shè)是阻塞方式)。

我們?cè)偻瑯訌膬?nèi)核組件和源碼視角看一遍。

1.3 跨機(jī)網(wǎng)絡(luò)通信匯總

二、本機(jī)發(fā)送過程

在第一節(jié)中,我們看到了跨機(jī)時(shí)整個(gè)網(wǎng)絡(luò)發(fā)送過程(嫌第一節(jié)流程圖不過癮,想繼續(xù)看源碼了解細(xì)節(jié)的同學(xué)可以參考 拆解 Linux 網(wǎng)絡(luò)包發(fā)送過程) 。

在本機(jī)網(wǎng)絡(luò) IO 的過程中,流程會(huì)有一些差別。為了突出重點(diǎn),將不再介紹整體流程,而是只介紹和跨機(jī)邏輯不同的地方。有差異的地方總共有兩個(gè),分別是路由和驅(qū)動(dòng)程序。

2.1 網(wǎng)絡(luò)層路由

發(fā)送數(shù)據(jù)會(huì)進(jìn)入?yún)f(xié)議棧到網(wǎng)絡(luò)層的時(shí)候,網(wǎng)絡(luò)層入口函數(shù)是 ip_queue_xmit。在網(wǎng)絡(luò)層里會(huì)進(jìn)行路由選擇,路由選擇完畢后,再設(shè)置一些 IP 頭、進(jìn)行一些 netfilter 的過濾后,將包交給鄰居子系統(tǒng)。

對(duì)于本機(jī)網(wǎng)絡(luò) IO 來說,特殊之處在于在 local 路由表中就能找到路由項(xiàng),對(duì)應(yīng)的設(shè)備都將使用 loopback 網(wǎng)卡,也就是我們常見的 lo。

我們來詳細(xì)看看路由網(wǎng)絡(luò)層里這段路由相關(guān)工作過程。從網(wǎng)絡(luò)層入口函數(shù) ip_queue_xmit 看起。

  1. //file: net/ipv4/ip_output.c 
  2. int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl) 
  3.  //檢查 socket 中是否有緩存的路由表 
  4.  rt = (struct rtable *)__sk_dst_check(sk, 0); 
  5.  if (rt == NULL) { 
  6.   //沒有緩存則展開查找 
  7.   //則查找路由項(xiàng), 并緩存到 socket 中 
  8.   rt = ip_route_output_ports(...); 
  9.   sk_setup_caps(sk, &rt->dst); 
  10.  } 

查找路由項(xiàng)的函數(shù)是 ip_route_output_ports,它又依次調(diào)用到 ip_route_output_flow、__ip_route_output_key、fib_lookup。調(diào)用過程省略掉,直接看 fib_lookup 的關(guān)鍵代碼。

  1. //file:include/net/ip_fib.h 
  2. static inline int fib_lookup(struct net *net, const struct flowi4 *flp, 
  3.         struct fib_result *res) 
  4.  struct fib_table *table
  5.  
  6.  table = fib_get_table(net, RT_TABLE_LOCAL); 
  7.  if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF)) 
  8.   return 0; 
  9.  
  10.  table = fib_get_table(net, RT_TABLE_MAIN); 
  11.  if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF)) 
  12.   return 0; 
  13.  return -ENETUNREACH; 

在 fib_lookup 將會(huì)對(duì) local 和 main 兩個(gè)路由表展開查詢,并且是先查 local 后查詢 main。我們?cè)?Linux 上使用命令名可以查看到這兩個(gè)路由表, 這里只看 local 路由表(因?yàn)楸緳C(jī)網(wǎng)絡(luò) IO 查詢到這個(gè)表就終止了)。

  1. #ip route list table local 
  2. local 10.143.x.y dev eth0 proto kernel scope host src 10.143.x.y 
  3. local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1 

從上述結(jié)果可以看出,對(duì)于目的是 127.0.0.1 的路由在 local 路由表中就能夠找到了。fib_lookup 工作完成,返回__ip_route_output_key 繼續(xù)。

  1. //file: net/ipv4/route.c 
  2. struct rtable *__ip_route_output_key(struct net *net, struct flowi4 *fl4) 
  3.  if (fib_lookup(net, fl4, &res)) { 
  4.  } 
  5.  if (res.type == RTN_LOCAL) { 
  6.   dev_out = net->loopback_dev; 
  7.   ... 
  8.  } 
  9.  
  10.  rth = __mkroute_output(&res, fl4, orig_oif, dev_out, flags); 
  11.  return rth; 

對(duì)于是本機(jī)的網(wǎng)絡(luò)請(qǐng)求,設(shè)備將全部都使用 net->loopback_dev,也就是 lo 虛擬網(wǎng)卡。

接下來的網(wǎng)絡(luò)層仍然和跨機(jī)網(wǎng)絡(luò) IO 一樣,最終會(huì)經(jīng)過 ip_finish_output,最終進(jìn)入到 鄰居子系統(tǒng)的入口函數(shù) dst_neigh_output 中。

本機(jī)網(wǎng)絡(luò) IO 需要進(jìn)行 IP 分片嗎?因?yàn)楹驼5木W(wǎng)絡(luò)層處理過程一樣會(huì)經(jīng)過 ip_finish_output 函數(shù)。在這個(gè)函數(shù)中,如果 skb 大于 MTU 的話,仍然會(huì)進(jìn)行分片。只不過 lo 的 MTU 比 Ethernet 要大很多。通過 ifconfig 命令就可以查到,普通網(wǎng)卡一般為 1500,而 lo 虛擬接口能有 65535。

在鄰居子系統(tǒng)函數(shù)中經(jīng)過處理,進(jìn)入到網(wǎng)絡(luò)設(shè)備子系統(tǒng)(入口函數(shù)是 dev_queue_xmit)。

2.2 網(wǎng)絡(luò)設(shè)備子系統(tǒng)

網(wǎng)絡(luò)設(shè)備子系統(tǒng)的入口函數(shù)是 dev_queue_xmit。簡(jiǎn)單回憶下之前講述跨機(jī)發(fā)送過程的時(shí)候,對(duì)于真的有隊(duì)列的物理設(shè)備,在該函數(shù)中進(jìn)行了一系列復(fù)雜的排隊(duì)等處理以后,才調(diào)用 dev_hard_start_xmit,從這個(gè)函數(shù) 再進(jìn)入驅(qū)動(dòng)程序來發(fā)送。在這個(gè)過程中,甚至還有可能會(huì)觸發(fā)軟中斷來進(jìn)行發(fā)送,流程如圖:

但是對(duì)于啟動(dòng)狀態(tài)的回環(huán)設(shè)備來說(q->enqueue 判斷為 false),就簡(jiǎn)單多了。沒有隊(duì)列的問題,直接進(jìn)入 dev_hard_start_xmit。接著進(jìn)入回環(huán)設(shè)備的“驅(qū)動(dòng)”里的發(fā)送回調(diào)函數(shù) loopback_xmit,將 skb “發(fā)送”出去。

我們來看下詳細(xì)的過程,從網(wǎng)絡(luò)設(shè)備子系統(tǒng)的入口 dev_queue_xmit 看起。

  1. //file: net/core/dev.c 
  2. int dev_queue_xmit(struct sk_buff *skb) 
  3.  q = rcu_dereference_bh(txq->qdisc); 
  4.  if (q->enqueue) {//回環(huán)設(shè)備這里為 false 
  5.   rc = __dev_xmit_skb(skb, q, dev, txq); 
  6.   goto out
  7.  } 
  8.  
  9.  //開始回環(huán)設(shè)備處理 
  10.  if (dev->flags & IFF_UP) { 
  11.   dev_hard_start_xmit(skb, dev, txq, ...); 
  12.   ... 
  13.  } 

在 dev_hard_start_xmit 中還是將調(diào)用設(shè)備驅(qū)動(dòng)的操作函數(shù)。

  1. //file: net/core/dev.c 
  2. int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, 
  3.    struct netdev_queue *txq) 
  4.  //獲取設(shè)備驅(qū)動(dòng)的回調(diào)函數(shù)集合 ops 
  5.  const struct net_device_ops *ops = dev->netdev_ops; 
  6.  
  7.  //調(diào)用驅(qū)動(dòng)的 ndo_start_xmit 來進(jìn)行發(fā)送 
  8.  rc = ops->ndo_start_xmit(skb, dev); 
  9.  ... 

2.3 “驅(qū)動(dòng)”程序

對(duì)于真實(shí)的 igb 網(wǎng)卡來說,它的驅(qū)動(dòng)代碼都在 drivers/net/ethernet/intel/igb/igb_main.c 文件里。順著這個(gè)路子,我找到了 loopback 設(shè)備的“驅(qū)動(dòng)”代碼位置:drivers/net/loopback.c。在 drivers/net/loopback.c

  1. //file:drivers/net/loopback.c 
  2. static const struct net_device_ops loopback_ops = { 
  3.  .ndo_init      = loopback_dev_init, 
  4.  .ndo_start_xmit= loopback_xmit, 
  5.  .ndo_get_stats64 = loopback_get_stats64, 
  6. }; 

所以對(duì) dev_hard_start_xmit 調(diào)用實(shí)際上執(zhí)行的是 loopback “驅(qū)動(dòng)” 里的 loopback_xmit。為什么我把“驅(qū)動(dòng)”加個(gè)引號(hào)呢,因?yàn)?loopback 是一個(gè)純軟件性質(zhì)的虛擬接口,并沒有真正意義上的驅(qū)動(dòng),它的工作流程大致如圖。

我們?cè)賮砜丛敿?xì)的代碼。

  1. //file:drivers/net/loopback.c 
  2. static netdev_tx_t loopback_xmit(struct sk_buff *skb, 
  3.      struct net_device *dev) 
  4.  //剝離掉和原 socket 的聯(lián)系 
  5.  skb_orphan(skb); 
  6.  
  7.  //調(diào)用netif_rx 
  8.  if (likely(netif_rx(skb) == NET_RX_SUCCESS)) { 
  9.  } 

在 skb_orphan 中先是把 skb 上的 socket 指針去掉了(剝離了出來)。

注意,在本機(jī)網(wǎng)絡(luò) IO 發(fā)送的過程中,傳輸層下面的 skb 就不需要釋放了,直接給接收方傳過去就行了。總算是省了一點(diǎn)點(diǎn)開銷。不過可惜傳輸層的 skb 同樣節(jié)約不了,還是得頻繁地申請(qǐng)和釋放。

接著調(diào)用 netif_rx,在該方法中 中最終會(huì)執(zhí)行到 enqueue_to_backlog 中(netif_rx -> netif_rx_internal -> enqueue_to_backlog)。

  1. //file: net/core/dev.c 
  2. static int enqueue_to_backlog(struct sk_buff *skb, int cpu, 
  3.          unsigned int *qtail) 
  4.  sd = &per_cpu(softnet_data, cpu); 
  5.  
  6.  ... 
  7.  __skb_queue_tail(&sd->input_pkt_queue, skb); 
  8.  
  9.  ... 
  10.  ____napi_schedule(sd, &sd->backlog); 

在 enqueue_to_backlog 把要發(fā)送的 skb 插入 softnet_data->input_pkt_queue 隊(duì)列中并調(diào)用 ____napi_schedule 來觸發(fā)軟中斷。

  1. //file:net/core/dev.c 
  2. static inline void ____napi_schedule(struct softnet_data *sd, 
  3.          struct napi_struct *napi) 
  4.  list_add_tail(&napi->poll_list, &sd->poll_list); 
  5.  __raise_softirq_irqoff(NET_RX_SOFTIRQ); 

只有觸發(fā)完軟中斷,發(fā)送過程就算是完成了。

三、本機(jī)接收過程

在跨機(jī)的網(wǎng)絡(luò)包的接收過程中,需要經(jīng)過硬中斷,然后才能觸發(fā)軟中斷。而在本機(jī)的網(wǎng)絡(luò) IO 過程中,由于并不真的過網(wǎng)卡,所以網(wǎng)卡實(shí)際傳輸,硬中斷就都省去了。直接從軟中斷開始,經(jīng)過 process_backlog 后送進(jìn)協(xié)議棧,大體過程如圖。

接下來我們?cè)倏锤敿?xì)一點(diǎn)的過程。

在軟中斷被觸發(fā)以后,會(huì)進(jìn)入到 NET_RX_SOFTIRQ 對(duì)應(yīng)的處理方法 net_rx_action 中(至于細(xì)節(jié)參見 圖解Linux網(wǎng)絡(luò)包接收過程 一文中的 3.2 小節(jié))。

  1. //file: net/core/dev.c 
  2. static void net_rx_action(struct softirq_action *h){ 
  3.  while (!list_empty(&sd->poll_list)) { 
  4.   work = n->poll(n, weight); 
  5.  } 

我們還記得對(duì)于 igb 網(wǎng)卡來說,poll 實(shí)際調(diào)用的是 igb_poll 函數(shù)。那么 loopback 網(wǎng)卡的 poll 函數(shù)是誰呢?由于poll_list 里面是 struct softnet_data 對(duì)象,我們?cè)?net_dev_init 中找到了蛛絲馬跡。

  1. //file:net/core/dev.c 
  2. static int __init net_dev_init(void) 
  3.  for_each_possible_cpu(i) { 
  4.   sd->backlog.poll = process_backlog; 
  5.  } 

原來struct softnet_data 默認(rèn)的 poll 在初始化的時(shí)候設(shè)置成了 process_backlog 函數(shù),來看看它都干了啥。

  1. static int process_backlog(struct napi_struct *napi, int quota) 
  2.  while(){ 
  3.   while ((skb = __skb_dequeue(&sd->process_queue))) { 
  4.    __netif_receive_skb(skb); 
  5.   } 
  6.  
  7.   //skb_queue_splice_tail_init()函數(shù)用于將鏈表a連接到鏈表b上, 
  8.   //形成一個(gè)新的鏈表b,并將原來a的頭變成空鏈表。 
  9.   qlen = skb_queue_len(&sd->input_pkt_queue); 
  10.   if (qlen) 
  11.    skb_queue_splice_tail_init(&sd->input_pkt_queue, 
  12.          &sd->process_queue); 
  13.    
  14.  } 

這次先看對(duì) skb_queue_splice_tail_init 的調(diào)用。源碼就不看了,直接說它的作用是把 sd->input_pkt_queue 里的 skb 鏈到 sd->process_queue 鏈表上去。

然后再看 __skb_dequeue, __skb_dequeue 是從 sd->process_queue 上取下來包來處理。這樣和前面發(fā)送過程的結(jié)尾處就對(duì)上了。發(fā)送過程是把包放到了 input_pkt_queue 隊(duì)列里,接收過程是在從這個(gè)隊(duì)列里取出 skb。

最后調(diào)用 __netif_receive_skb 將 skb(數(shù)據(jù)) 送往協(xié)議棧。在此之后的調(diào)用過程就和跨機(jī)網(wǎng)絡(luò) IO 又一致了。

送往協(xié)議棧的調(diào)用鏈?zhǔn)?__netif_receive_skb => __netif_receive_skb_core => deliver_skb 后 將數(shù)據(jù)包送入到 ip_rcv 中(詳情參見圖解Linux網(wǎng)絡(luò)包接收過程 一文中的 3.3 小節(jié))。

網(wǎng)絡(luò)再往后依次是傳輸層,最后喚醒用戶進(jìn)程,這里就不多展開了。

四、本機(jī)網(wǎng)絡(luò) IO 總結(jié)

我們來總結(jié)一下本機(jī)網(wǎng)絡(luò) IO 的內(nèi)核執(zhí)行流程。

回想下跨機(jī)網(wǎng)絡(luò) IO 的流程是

我們現(xiàn)在可以回顧下開篇的三個(gè)問題啦。

1)127.0.0.1 本機(jī)網(wǎng)絡(luò) IO 需要經(jīng)過網(wǎng)卡嗎?

通過本文的敘述,我們確定地得出結(jié)論,不需要經(jīng)過網(wǎng)卡。即使了把網(wǎng)卡拔了本機(jī)網(wǎng)絡(luò)是否還可以正常使用的。

2)數(shù)據(jù)包在內(nèi)核中是個(gè)什么走向,和外網(wǎng)發(fā)送相比流程上有啥差別?

總的來說,本機(jī)網(wǎng)絡(luò) IO 和跨機(jī) IO 比較起來,確實(shí)是節(jié)約了一些開銷。發(fā)送數(shù)據(jù)不需要進(jìn) RingBuffer 的驅(qū)動(dòng)隊(duì)列,直接把 skb 傳給接收協(xié)議棧(經(jīng)過軟中斷)。但是在內(nèi)核其它組件上,可是一點(diǎn)都沒少,系統(tǒng)調(diào)用、協(xié)議棧(傳輸層、網(wǎng)絡(luò)層等)、網(wǎng)絡(luò)設(shè)備子系統(tǒng)、鄰居子系統(tǒng)整個(gè)走了一個(gè)遍。連“驅(qū)動(dòng)”程序都走了(雖然對(duì)于回環(huán)設(shè)備來說只是一個(gè)純軟件的虛擬出來的東東)。所以即使是本機(jī)網(wǎng)絡(luò) IO,也別誤以為沒啥開銷。

最后再提一下,業(yè)界有公司基于 ebpf 來加速 istio 架構(gòu)中 sidecar 代理和本地進(jìn)程之間的通信。通過引入 BPF,才算是繞開了內(nèi)核協(xié)議棧的開銷,原理如下。

參見:https://cloud.tencent.com/developer/article/1671568

 

責(zé)任編輯:武曉燕 來源: 開發(fā)內(nèi)功修煉
相關(guān)推薦

2022-01-19 17:19:14

區(qū)塊鏈攻擊加密算法

2010-04-14 18:36:50

無線局域網(wǎng)絡(luò)

2009-08-24 17:20:13

C#網(wǎng)絡(luò)通信TCP連接

2020-11-12 08:52:16

Python

2019-04-29 10:26:49

TCP網(wǎng)絡(luò)協(xié)議網(wǎng)絡(luò)通信

2012-02-13 22:50:59

集群高可用

2024-08-06 10:07:15

2022-12-05 09:25:17

Kubernetes網(wǎng)絡(luò)模型網(wǎng)絡(luò)通信

2024-02-20 19:53:57

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

2010-06-09 11:31:55

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

2016-08-25 11:17:16

CaaS華為

2022-05-13 10:59:14

容器網(wǎng)絡(luò)通信

2009-10-16 08:48:08

2010-04-22 16:10:48

Aix操作系統(tǒng)網(wǎng)絡(luò)通信

2019-09-25 08:25:49

RPC網(wǎng)絡(luò)通信

2021-08-30 13:08:56

Kafka網(wǎng)絡(luò)通信

2024-10-31 10:03:17

2017-07-14 10:51:37

性能優(yōu)化SQL性能分析

2010-08-16 09:15:57

2021-12-04 11:17:32

Javascript繼承編程
點(diǎn)贊
收藏

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