国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核是如何接收到網(wǎng)絡(luò)包的

這篇具有很好參考價(jià)值的文章主要介紹了深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核是如何接收到網(wǎng)絡(luò)包的。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。


系列文章:
  1. 深入理解Linux網(wǎng)絡(luò)——內(nèi)核是如何接收到網(wǎng)絡(luò)包的
  2. 深入理解Linux網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之同步阻塞方案(BIO)
  3. 深入理解Linux網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之多路復(fù)用方案(epoll)
  4. 深入理解Linux網(wǎng)絡(luò)——內(nèi)核是如何發(fā)送網(wǎng)絡(luò)包的
  5. 深入理解Linux網(wǎng)絡(luò)——本機(jī)網(wǎng)絡(luò)IO
  6. 深入理解Linux網(wǎng)絡(luò)——TCP連接建立過(guò)程(三次握手源碼詳解)
  7. 深入理解Linux網(wǎng)絡(luò)——TCP連接的開銷

一、相關(guān)實(shí)際問(wèn)題

  1. RingBuffer是什么,為什么會(huì)丟包
  2. 網(wǎng)絡(luò)相關(guān)的硬中斷、軟中斷是什么
  3. Linux里的ksoftirqd內(nèi)核線程是干什么的
  4. 為什么網(wǎng)卡開啟多隊(duì)列能提升網(wǎng)絡(luò)性能
  5. tcpdump是如何工作的
  6. iptable/netfilter是在哪一層實(shí)現(xiàn)的
  7. tcpdump能否抓到被iptable封禁的包
  8. 網(wǎng)絡(luò)接收過(guò)程中如何查看CPU開銷
  9. DPDK是什么

二、數(shù)據(jù)是如何從網(wǎng)卡到協(xié)議棧的

1、Linux網(wǎng)絡(luò)收包總覽

Linux內(nèi)核以及網(wǎng)卡驅(qū)動(dòng)主要實(shí)現(xiàn)鏈路層、網(wǎng)絡(luò)層和傳輸層這三層上的功能,內(nèi)核為更上面的應(yīng)用層提供socket接口來(lái)支持用戶進(jìn)程訪問(wèn)。

深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核是如何接收到網(wǎng)絡(luò)包的,網(wǎng)絡(luò),網(wǎng)絡(luò),linux,tcp/ip,網(wǎng)卡,網(wǎng)絡(luò)協(xié)議

內(nèi)核和網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)是通過(guò)中斷的方式來(lái)處理的。當(dāng)設(shè)備上有數(shù)據(jù)達(dá)到時(shí),會(huì)給CPU的相關(guān)引腳觸發(fā)一個(gè)電壓變化,以通知CPU來(lái)處理數(shù)據(jù)(硬中斷)。對(duì)于網(wǎng)絡(luò)模塊來(lái)說(shuō),由于處理過(guò)程比較復(fù)雜和耗時(shí),如果在中斷函數(shù)中完成所有的處理,將會(huì)導(dǎo)致中斷處理函數(shù)(優(yōu)先級(jí)過(guò)高)過(guò)度占用CPU,使得CPU無(wú)法響應(yīng)其他設(shè)備,如鼠標(biāo)和鍵盤的消息。

因此Linux中斷處理函數(shù)是分上半部和下半部的。上半部只進(jìn)行最簡(jiǎn)單的工作,快速處理然后釋放CPU,接著CPU就可以允許其它中斷進(jìn)來(lái)。將剩下的絕大部分的工作都放到下部分,可以慢慢地處理。2.4以后的Linux內(nèi)核版本采用的下半部實(shí)現(xiàn)方式是軟中斷,由ksoftirqd內(nèi)核線程全權(quán)處理。

硬中斷是通過(guò)給CPU物理引腳施加電壓變化實(shí)現(xiàn)的,而軟中斷是通過(guò)給內(nèi)存中的一個(gè)變量賦予二進(jìn)制值以標(biāo)記有軟中斷發(fā)生。

軟中斷是Linux內(nèi)核用于處理一些不能在硬中斷上下文中完成的異步任務(wù)的機(jī)制。硬中斷處理程序(interrupt handlers)通常需要盡可能快地執(zhí)行并返回,以便CPU可以繼續(xù)執(zhí)行其他任務(wù)。因此,如果硬中斷處理程序需要進(jìn)行一些可能會(huì)花費(fèi)較多時(shí)間的操作(例如,處理網(wǎng)絡(luò)數(shù)據(jù)包或磁盤I/O),它通常會(huì)把這些操作安排到一個(gè)軟中斷中,然后快速返回。

總體工作步驟:

  1. 數(shù)據(jù)幀從外部網(wǎng)絡(luò)到達(dá)網(wǎng)卡
  2. 網(wǎng)卡收到數(shù)據(jù)后以DMA的方式將幀寫到內(nèi)存
  3. 硬中斷通知CPU
  4. CPU收到中斷請(qǐng)求后調(diào)用網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)注冊(cè)的中斷處理函數(shù),簡(jiǎn)單處理后發(fā)出軟中斷請(qǐng)求,然后迅速釋放CPU
  5. ksoftirqd內(nèi)核線程檢測(cè)到軟中斷請(qǐng)求到達(dá),調(diào)用poll(網(wǎng)卡驅(qū)動(dòng)程序注冊(cè)的函數(shù))開始輪詢收包
  6. 數(shù)據(jù)幀從RingBuffer上摘下來(lái)保存為一個(gè)skb(struct sk_buff對(duì)象的簡(jiǎn)稱,是Linux網(wǎng)絡(luò)模塊中的核心數(shù)據(jù)結(jié)構(gòu)體,各個(gè)層用到的數(shù)據(jù)包都是存在這個(gè)結(jié)構(gòu)體里的)
  7. 將包交由各級(jí)協(xié)議層處理,處理完后放到socket的接收隊(duì)列
  8. 內(nèi)核喚醒用戶進(jìn)程

2、Linux啟動(dòng)

Linux驅(qū)動(dòng)、內(nèi)核協(xié)議棧等模塊在能夠接受網(wǎng)卡數(shù)據(jù)包之前,要做很多的準(zhǔn)備工作才行。比如提前創(chuàng)建好ksoftirqd內(nèi)核線程,注冊(cè)好各個(gè)協(xié)議對(duì)應(yīng)的處理函數(shù),初始化網(wǎng)卡設(shè)備子系統(tǒng)并啟動(dòng)網(wǎng)卡等。只有這些都準(zhǔn)備好后才能真正開始接收數(shù)據(jù)包。

1)創(chuàng)建ksotfirqd內(nèi)核線程

Linux的軟中斷都是在專門的內(nèi)核線程ksoftirqd中進(jìn)行的,該線程的數(shù)量不是1個(gè),而是N個(gè),N即對(duì)應(yīng)機(jī)器的核數(shù)。

在Linux內(nèi)核中,軟中斷可以在幾種不同的上下文中被執(zhí)行:在硬中斷處理程序返回后,或者在內(nèi)核退出到用戶空間之前。然而,如果CPU在處理用戶空間的任務(wù),或者在處理某些不能被中斷的內(nèi)核任務(wù),那么軟中斷的處理可能會(huì)被推遲,在這種情況下,軟中斷的處理會(huì)被交給ksoftirqd。在大多數(shù)系統(tǒng)中,ksoftirqd線程大部分時(shí)間都處于休眠狀態(tài),因?yàn)榇蠖鄶?shù)軟中斷都能在其他上下文中立即被處理。但是,在高負(fù)載的系統(tǒng)中,ksoftirqd線程可能會(huì)變得非?;钴S,以幫助處理積壓的軟中斷。

ksoftirqd是一個(gè)優(yōu)先級(jí)比較低的內(nèi)核線程。當(dāng)系統(tǒng)中有其它更高優(yōu)先級(jí)的任務(wù)(例如用戶空間的任務(wù)或者其它內(nèi)核任務(wù))需要運(yùn)行時(shí),ksoftirqd可能會(huì)被調(diào)度器放在一邊,等待CPU空閑。只有當(dāng)CPU有足夠的空閑時(shí)間,或者當(dāng)軟中斷的處理不能再被推遲時(shí),ksoftirqd才會(huì)被調(diào)度運(yùn)行。這種設(shè)計(jì)可以確保CPU的時(shí)間被優(yōu)先用于處理用戶空間的任務(wù)和高優(yōu)先級(jí)的內(nèi)核任務(wù),同時(shí)還能確保軟中斷最終會(huì)被處理。也就是說(shuō),ksoftirqd提供了一種機(jī)制,用于在CPU有空閑時(shí)間時(shí)處理軟中斷,或者在軟中斷的處理不能再被推遲時(shí)處理軟中斷。

系統(tǒng)初始化的時(shí)候在kernel/smpboot.c中調(diào)用了smpboot_register_percpu_thread,該函數(shù)會(huì)進(jìn)一步執(zhí)行到spawn_ksoftirqd(位于kernel/ksoftirqd.c)來(lái)創(chuàng)建softirqd線程。

  1. ksoftirqd會(huì)在一個(gè)循環(huán)中運(yùn)行,這個(gè)循環(huán)檢查softirq_pending變量,看是否有軟中斷需要處理。
  2. 如果沒(méi)有軟中斷需要處理(softirq_pending是0),那么ksoftirqd就會(huì)通過(guò)調(diào)用schedule()函數(shù)將自己置于休眠狀態(tài)。在這種狀態(tài)下,ksoftirqd并不消耗CPU時(shí)間。
  3. 當(dāng)有新的軟中斷被安排時(shí),ksoftirqd會(huì)被喚醒。這通常是通過(guò)設(shè)置softirq_pending變量并喚醒ksoftirqd來(lái)實(shí)現(xiàn)的。

軟中斷不僅有網(wǎng)絡(luò)軟中斷,還有其他類型。Linux內(nèi)核在interrupt.h中定義了所有的軟中斷類型,其中網(wǎng)絡(luò)相關(guān)的是NET_TX_SOFTIRQ和NET_RX_SOFTIRQ。

  • NET_RX_SOFTIRQ:此軟中斷主要用于處理網(wǎng)絡(luò)設(shè)備的接收(Receive)部分
  • NET_TX_SOFTIRQ:此軟中斷用于處理網(wǎng)絡(luò)設(shè)備的發(fā)送(Transmit)部分

將網(wǎng)絡(luò)處理任務(wù)劃分為接收和發(fā)送兩部分,然后使用不同的軟中斷來(lái)處理的設(shè)計(jì),有利于提高網(wǎng)絡(luò)處理的效率。尤其在多核處理器系統(tǒng)中,不同的軟中斷可以在不同的CPU核心上并行運(yùn)行,從而進(jìn)一步提高處理速度。

2)網(wǎng)絡(luò)子系統(tǒng)初始化

在網(wǎng)絡(luò)子系統(tǒng)的初始化過(guò)程中,會(huì)為每個(gè)CPU初始化softnet_data,也會(huì)為RX_SOFTIRQ和TX_SOFTIRQ注冊(cè)處理函數(shù)。

struct softnet_data
{
    struct Qdisc *output_queue;
    struct Qdisc **output_queue_tailp; 
    struct list_head poll_list; // 一個(gè)設(shè)備列表,存儲(chǔ)的是需要被輪詢處理的設(shè)備。硬中斷處理程序會(huì)將設(shè)備添加到這個(gè)列表,然后軟中斷處理程序會(huì)輪詢這個(gè)列表,處理每個(gè)設(shè)備的接收隊(duì)列
    struct sk_buff *completion_queue; // 一個(gè)skb列表,用于存儲(chǔ)已經(jīng)處理但尚未釋放的數(shù)據(jù)包。軟中斷處理程序在處理完數(shù)據(jù)包后,會(huì)將數(shù)據(jù)包添加到這個(gè)隊(duì)列,然后在適當(dāng)?shù)臅r(shí)候釋放這些數(shù)據(jù)包
    struct sk_buff_head process_queue;
    ...
}
  1. 在Linux網(wǎng)絡(luò)子系統(tǒng)中,softnet_data數(shù)據(jù)結(jié)構(gòu)包含了各種網(wǎng)絡(luò)處理需要的數(shù)據(jù)和狀態(tài)信息,其中包括接收隊(duì)列和發(fā)送隊(duì)列。為了提高多核或多處理器系統(tǒng)的性能,操作系統(tǒng)會(huì)為每個(gè)CPU創(chuàng)建一個(gè)這樣的數(shù)據(jù)結(jié)構(gòu)。在處理網(wǎng)絡(luò)數(shù)據(jù)包時(shí),各個(gè)CPU可以根據(jù)它們各自的softnet_data數(shù)據(jù)結(jié)構(gòu)中的接收隊(duì)列和發(fā)送隊(duì)列來(lái)獨(dú)立進(jìn)行處理。

  2. 為軟中斷注冊(cè)處理函數(shù)使用方法open_softirq。NET_TX_SOFTIRQ的處理函數(shù)為net_tx_action,NET_RX_SOFTIRQ的處理函數(shù)為net_rx_action。具體實(shí)現(xiàn)方式是將軟中斷和處理函數(shù)的對(duì)應(yīng)關(guān)系記錄到softirq_vec數(shù)組,后續(xù)ksoftirqd線程收到軟中斷的時(shí)候就會(huì)使用這個(gè)變量來(lái)找到對(duì)應(yīng)的處理函數(shù)。

    void open_softirq(int nr, void (*action)(struct softirq_action *)
    {
        softirq_vec[nr].action = action;
    }
    

Linux內(nèi)核通過(guò)調(diào)用subsys_initcall來(lái)初始化各個(gè)子系統(tǒng),這里是要初始化網(wǎng)絡(luò)子系統(tǒng)(subsys_initcall(net_dev_init)),會(huì)執(zhí)行net_dev_init函數(shù)。

3)協(xié)議棧注冊(cè)

內(nèi)核實(shí)現(xiàn)了網(wǎng)絡(luò)層的IP協(xié)議和傳輸層的TCP、UDP協(xié)議,這些協(xié)議對(duì)應(yīng)的實(shí)現(xiàn)函數(shù)分別是ip_rcv()、tcp_v4_rcv()和udp_rcv()。

Linux內(nèi)核中通過(guò)fs_initcall(文件系統(tǒng)初始化,類似subsys_initcall,也是初始化模塊的入口)來(lái)調(diào)用inet_init進(jìn)行網(wǎng)絡(luò)協(xié)議棧的注冊(cè)。inet_init將上述的協(xié)議實(shí)現(xiàn)函數(shù)注冊(cè)到inet_protos(注冊(cè)u(píng)dp_rcv函數(shù)和tcp_v4_rcv函數(shù),是一個(gè)數(shù)組)和ptype_base(注冊(cè)ip_rcv函數(shù),是一個(gè)哈希表)數(shù)據(jù)結(jié)構(gòu)中。

即協(xié)議棧注冊(cè)后后,inet_protos中記錄著UDP、TCP的處理函數(shù)地址,ptype_base存儲(chǔ)著ip_rcv函數(shù)的處理地址。軟中斷中會(huì)通過(guò)ptype_base找到ip_rcv函數(shù)地址,進(jìn)而將IP包正確送到ip_rcv()中執(zhí)行,ip_rcv()會(huì)通過(guò)inet_protos找到TCP或UDP的處理函數(shù),再把包轉(zhuǎn)發(fā)給udp_rcv()或tcp_v4_rcv()函數(shù)。

如果去看ip_rcv()和udp_rcv()等函數(shù)的代碼,能看到很多協(xié)議的處理過(guò)程,如ip_rcv中會(huì)處理iptable netfilter過(guò)濾,udp_rcv中會(huì)判斷socket接收隊(duì)列是否滿了,對(duì)應(yīng)的相關(guān)內(nèi)核參數(shù)是net.core.rmem_max和net.core.rmem_default。

4)網(wǎng)卡驅(qū)動(dòng)初始化

每一個(gè)驅(qū)動(dòng)程序(不僅僅包括網(wǎng)卡驅(qū)動(dòng)程序)會(huì)使用module_init向內(nèi)核注冊(cè)一個(gè)初始化函數(shù),當(dāng)驅(qū)動(dòng)程序被加載時(shí),內(nèi)核會(huì)調(diào)用這個(gè)函數(shù)。以igb網(wǎng)卡驅(qū)動(dòng)程序?yàn)槔涑跏蓟瘮?shù)為

static int __init igb_init_module(void)
{
    ......
    ret = pci_register_driver(&igb_drvier);
    return ret;
}

通過(guò)module_init(igb_init_module),當(dāng)驅(qū)動(dòng)的pci_register_driver執(zhí)行完成后內(nèi)核就知道了該驅(qū)動(dòng)的相關(guān)信息,如igb網(wǎng)卡驅(qū)動(dòng)的igb_driver_name和igb_probe函數(shù)地址等。當(dāng)網(wǎng)卡設(shè)備被識(shí)別以后,內(nèi)核會(huì)調(diào)用其驅(qū)動(dòng)的probe方法讓設(shè)備處于ready狀態(tài),具體流程如下:

  1. 內(nèi)核調(diào)用驅(qū)動(dòng)probe
  2. 網(wǎng)卡驅(qū)動(dòng)獲取網(wǎng)卡MAC地址
  3. 網(wǎng)卡驅(qū)動(dòng)DMA初始化
  4. 注冊(cè)ethtool實(shí)現(xiàn)函數(shù)(ethtool命令之所以能夠查看網(wǎng)卡收發(fā)包統(tǒng)計(jì)、修改自適應(yīng)模式、調(diào)整RX隊(duì)列大小和數(shù)量,是因?yàn)榫W(wǎng)卡驅(qū)動(dòng)提供了相應(yīng)的方法)
  5. 注冊(cè)net_device_ops、netdev等變量(igb_device_ops變量中包含igb_open等函數(shù),在網(wǎng)卡啟動(dòng)時(shí)會(huì)被調(diào)用)
  6. NAPI初始化,注冊(cè)poll函數(shù)

當(dāng)一個(gè)網(wǎng)絡(luò)設(shè)備(例如以太網(wǎng)卡)被系統(tǒng)識(shí)別并初始化時(shí),設(shè)備驅(qū)動(dòng)程序會(huì)為設(shè)備的每個(gè)接收隊(duì)列創(chuàng)建一個(gè)napi_struct實(shí)例。在驅(qū)動(dòng)程序中,通常會(huì)有一段類似以下的代碼來(lái)創(chuàng)建和初始化napi_struct實(shí)例:

struct napi_struct *napi;

// 分配napi_struct實(shí)例
napi = kzalloc(sizeof(*napi), GFP_KERNEL);

// 初始化napi_struct實(shí)例
netif_napi_add(dev, napi, my_poll_function, WEIGHT); // dev是網(wǎng)絡(luò)設(shè)備,my_poll_function是設(shè)備驅(qū)動(dòng)定義的輪詢函數(shù),WEIGHT是在一個(gè)輪詢周期中設(shè)備可以處理的最大數(shù)據(jù)包數(shù)量

struct napi_struct {
    struct list_head poll_list; // 一個(gè)列表節(jié)點(diǎn),用于將這個(gè)napi_struct實(shí)例鏈接到softnet_data結(jié)構(gòu)的poll_list中
    unsigned long state; // 一個(gè)狀態(tài)變量,用于表示這個(gè)napi_struct實(shí)例的狀態(tài),例如它是否在poll_list中,是否正在被輪詢等
    int weight; // 這個(gè)成員定義了在一個(gè)輪詢周期中,這個(gè)設(shè)備可以處理的最大數(shù)據(jù)包數(shù)量
    int (*poll)(struct napi_struct *, int); // 函數(shù)指針,指向設(shè)備驅(qū)動(dòng)定義的輪詢函數(shù)。這個(gè)函數(shù)會(huì)被NAPI調(diào)用以處理設(shè)備的接收隊(duì)列
#ifdef CONFIG_NETPOLL
    spinlock_t poll_lock;
    int poll_owner;
    struct net_device *dev; // 一個(gè)指向?qū)?yīng)的net_device結(jié)構(gòu)的指針,表示這個(gè)napi_struct實(shí)例對(duì)應(yīng)的網(wǎng)絡(luò)設(shè)備
    struct list_head dev_list;
#endif
};

創(chuàng)建napi_struct實(shí)例后,驅(qū)動(dòng)程序通常會(huì)將其與相應(yīng)的接收隊(duì)列關(guān)聯(lián)起來(lái),每個(gè)napi_struct實(shí)例代表一個(gè)接收隊(duì)列。當(dāng)數(shù)據(jù)包到來(lái)時(shí),其會(huì)被添加到CPU的softnet_data中,之后在軟中斷時(shí)通過(guò)遍歷內(nèi)核就可以知道需要處理哪些接收隊(duì)列,以及怎么處理(poll函數(shù))。

5)網(wǎng)卡啟動(dòng)

以上的初始化都完成以后就可以啟動(dòng)網(wǎng)卡了。在上一步網(wǎng)卡驅(qū)動(dòng)初始化時(shí),驅(qū)動(dòng)向內(nèi)核注冊(cè)了net_device_ops變量,其中包含著網(wǎng)卡啟動(dòng)、發(fā)包、設(shè)置MAC地址等回調(diào)函數(shù)(函數(shù)指針)。當(dāng)啟動(dòng)一個(gè)網(wǎng)卡時(shí)(例如通過(guò)ifconfig eth0 up),net_device_ops變量中的ndo_open方法會(huì)被調(diào)用(這是一個(gè)函數(shù)指針,對(duì)于igb網(wǎng)卡來(lái)說(shuō)指向了igb_open方法)。具體啟動(dòng)的執(zhí)行流程如下:

  1. 啟動(dòng)網(wǎng)卡
  2. 內(nèi)核調(diào)用net_device_ops中注冊(cè)的open函數(shù),如igb_open
  3. 網(wǎng)卡驅(qū)動(dòng)分配RX,TX隊(duì)列內(nèi)存
  4. 網(wǎng)卡驅(qū)動(dòng)注冊(cè)中斷處理函數(shù)
  5. 網(wǎng)卡驅(qū)動(dòng)打開硬中斷,等待數(shù)據(jù)包到來(lái)
  6. 啟用NAPI

NAPI(New API,也被稱為 NAPI)是一個(gè)在 Linux 內(nèi)核中用于改善網(wǎng)絡(luò)性能的接口。NAPI 主要解決的問(wèn)題是當(dāng)網(wǎng)絡(luò)流量非常大時(shí),中斷處理的開銷過(guò)大問(wèn)題。

在早期的網(wǎng)絡(luò)接口設(shè)計(jì)中,每個(gè)接收到的數(shù)據(jù)包都會(huì)觸發(fā)一個(gè)硬件中斷,然后 CPU 停止當(dāng)前任務(wù),轉(zhuǎn)而去處理這個(gè)數(shù)據(jù)包。然而,在高流量的網(wǎng)絡(luò)中,數(shù)據(jù)包的接收頻率可能非常高,可能會(huì)導(dǎo)致大量的中斷,這可能會(huì)占據(jù)大部分 CPU 時(shí)間,導(dǎo)致所謂的"中斷風(fēng)暴"(interrupt storm)。

為了解決這個(gè)問(wèn)題,NAPI 引入了一種被稱為"輪詢"(polling)的機(jī)制。在輪詢模式下,網(wǎng)絡(luò)設(shè)備在接收到數(shù)據(jù)包時(shí)不再觸發(fā)中斷,而是簡(jiǎn)單地將數(shù)據(jù)包放入接收隊(duì)列。然后,CPU 定期輪詢這個(gè)隊(duì)列,處理所有待處理的數(shù)據(jù)包。在網(wǎng)絡(luò)流量非常大的情況下,這種方法可以減少 CPU 的中斷處理開銷,從而提高網(wǎng)絡(luò)性能。

NAPI 還包含一種"混合模式",在網(wǎng)絡(luò)流量較小的情況下使用中斷模式,當(dāng)網(wǎng)絡(luò)流量較大時(shí)自動(dòng)切換到輪詢模式。這種方式結(jié)合了兩種模式的優(yōu)點(diǎn),能夠在不同的網(wǎng)絡(luò)環(huán)境中提供較好的性能。

NAPI 是 Linux 內(nèi)核的一部分,被包括在 Linux 的網(wǎng)絡(luò)子系統(tǒng)中。許多現(xiàn)代的網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)都使用 NAPI 來(lái)提高性能。

igb_open方法實(shí)現(xiàn)如下:

static int __igb_open(struct net_device *netdev, bool resuming)
{
    // 分配傳輸描述符數(shù)組
    err = igb_setup_all_tx_resources(adpater);
    // 分配接收描述符數(shù)組
    err = igb_setup_all_rx_resources(adpater);
    // 注冊(cè)中斷處理函數(shù)
    err = igb_request_irq(adapter);
    if(err)
	goto err_req_irq;
    // 啟用NAPI
    for(i = 0; i < adapter->num_q_vectors; i++)
	napi_enable(&(adapter->q_vector[i]->napi));
    ......
}

以上代碼中,_igb_open函數(shù)調(diào)用了igb_setup_all_tx_resources和igb_setup_all_rx_resources,調(diào)用igb_setup_all_rx_resources時(shí)分配了RingBuffer,并建立內(nèi)存和Rx隊(duì)列的映射關(guān)系。實(shí)際上一個(gè)RingBuffer的內(nèi)部不是僅有一個(gè)環(huán)形隊(duì)列數(shù)組,而是兩個(gè),一個(gè)是內(nèi)核使用的指針數(shù)組(igb_rx_buffer),一個(gè)是給網(wǎng)卡硬件使用的bd數(shù)組(e1000_adv_rx_desc)。

在網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)中,環(huán)形緩沖區(qū)(Ring Buffer)通常有兩個(gè)核心組件:一個(gè)是用于存儲(chǔ)數(shù)據(jù)包描述符(packet descriptors)的環(huán)形數(shù)組,另一個(gè)是用于存儲(chǔ)實(shí)際數(shù)據(jù)包的環(huán)形數(shù)組。

  1. 描述符數(shù)組:這個(gè)數(shù)組存儲(chǔ)的是網(wǎng)絡(luò)數(shù)據(jù)包的描述符。描述符通常是一個(gè)數(shù)據(jù)結(jié)構(gòu),包含了一些元數(shù)據(jù),如數(shù)據(jù)包的長(zhǎng)度、數(shù)據(jù)包在數(shù)據(jù)數(shù)組中的位置,以及一些狀態(tài)信息,例如數(shù)據(jù)包是否已經(jīng)被處理等。這個(gè)數(shù)組通常被網(wǎng)卡硬件和設(shè)備驅(qū)動(dòng)共享,用于協(xié)調(diào)數(shù)據(jù)包的處理。
  2. 數(shù)據(jù)數(shù)組:這個(gè)數(shù)組實(shí)際存儲(chǔ)網(wǎng)絡(luò)數(shù)據(jù)包的數(shù)據(jù)。當(dāng)網(wǎng)絡(luò)設(shè)備接收到一個(gè)數(shù)據(jù)包時(shí),它會(huì)將數(shù)據(jù)包的數(shù)據(jù)復(fù)制到這個(gè)數(shù)組的一個(gè)位置,然后在描述符數(shù)組中添加一個(gè)新的描述符,指向這個(gè)數(shù)據(jù)包的位置。

在處理數(shù)據(jù)包時(shí),設(shè)備驅(qū)動(dòng)會(huì)首先查看描述符數(shù)組,找到待處理的數(shù)據(jù)包的描述符,然后根據(jù)描述符的信息,從數(shù)據(jù)數(shù)組中獲取并處理數(shù)據(jù)包的數(shù)據(jù)。

對(duì)于Intel的igb驅(qū)動(dòng),e1000_adv_rx_desc數(shù)組和igb_rx_buffer數(shù)組都是被網(wǎng)卡硬件和內(nèi)核驅(qū)動(dòng)共同使用的,但它們的使用方式有所不同:

  1. e1000_adv_rx_desc數(shù)組:這個(gè)隊(duì)列存儲(chǔ)的是接收描述符(Receive Descriptor),它們是網(wǎng)卡硬件和驅(qū)動(dòng)程序共享的數(shù)據(jù)結(jié)構(gòu),用于描述接收到的數(shù)據(jù)包。每一個(gè)接收描述符包含數(shù)據(jù)包的物理地址、長(zhǎng)度、狀態(tài)、錯(cuò)誤信息等。當(dāng)網(wǎng)卡硬件接收到一個(gè)數(shù)據(jù)包,會(huì)將數(shù)據(jù)包的信息填充到一個(gè)接收描述符中,然后將這個(gè)描述符放入e1000_adv_rx_desc的一個(gè)條目中
  2. igb_rx_buffer數(shù)組:這個(gè)隊(duì)列存儲(chǔ)的是指向sk_buff結(jié)構(gòu)的指針,sk_buff是網(wǎng)絡(luò)數(shù)據(jù)包在Linux內(nèi)核中的表示。當(dāng)網(wǎng)卡驅(qū)動(dòng)從網(wǎng)卡硬件接收到一個(gè)數(shù)據(jù)包時(shí),會(huì)為這個(gè)數(shù)據(jù)包在內(nèi)存中分配一個(gè)sk_buff,然后將這個(gè)sk_buff的指針?lè)湃雐gb_rx_buffer的一個(gè)條目中。

在注冊(cè)中斷處理函數(shù)時(shí),對(duì)于多隊(duì)列(多個(gè)RingBuffer)的網(wǎng)卡,每一個(gè)隊(duì)列都注冊(cè)了中斷。

前面在初始化網(wǎng)絡(luò)子系統(tǒng)時(shí)也注冊(cè)了中斷處理函數(shù),當(dāng)時(shí)使用的是open_softirq函數(shù),這個(gè)函數(shù)注冊(cè)的軟中斷的處理函數(shù),而這里是使用request_irq注冊(cè)硬中斷處理函數(shù)。

3、迎接數(shù)據(jù)的到來(lái)

1)硬中斷處理

當(dāng)數(shù)據(jù)幀從網(wǎng)線到達(dá)網(wǎng)卡上的時(shí)候,第一站是網(wǎng)卡的接收隊(duì)列。網(wǎng)卡在分配給自己的RingBuffer中尋找到可用的內(nèi)存位置,找到后DMA引擎會(huì)把數(shù)據(jù)DMA到網(wǎng)卡之前關(guān)聯(lián)的內(nèi)存里,到這個(gè)時(shí)候CPU都是無(wú)感的。當(dāng)DMA操作完成以后,網(wǎng)卡會(huì)向CPU發(fā)起一個(gè)硬中斷,通知CPU有數(shù)據(jù)到達(dá)。

具體流程如下:

  1. 網(wǎng)卡接收數(shù)據(jù)包,將數(shù)據(jù)包寫入Rx FIFO
  2. DMA找到rx descriptor ring中下一個(gè)將要使用的descriptor
  3. DMA通過(guò)PCI總線將Rx FIFO中的數(shù)據(jù)包復(fù)制到descriptor的數(shù)據(jù)緩存區(qū)
  4. 調(diào)用驅(qū)動(dòng)注冊(cè)的硬中斷處理函數(shù),通知CPU數(shù)據(jù)緩存區(qū)中已經(jīng)有新的數(shù)據(jù)包了

當(dāng)RingBuffer滿的時(shí)候新來(lái)的數(shù)據(jù)包將被丟棄。使用ifconfig命令查看網(wǎng)卡的時(shí)候,可以看到里面有個(gè)overruns,表示因?yàn)榄h(huán)形隊(duì)列滿被丟棄的包數(shù),可能需要通過(guò)ethtool命令來(lái)加大環(huán)形隊(duì)列的長(zhǎng)度。

深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核是如何接收到網(wǎng)絡(luò)包的,網(wǎng)絡(luò),網(wǎng)絡(luò),linux,tcp/ip,網(wǎng)卡,網(wǎng)絡(luò)協(xié)議

網(wǎng)卡硬中斷處理函數(shù)示例:

/file: drivers/net/ethernet/intel/igb/igb_main.c
static irqreturn_t igb_msix_ring(intirq, void *data){
    struct igb_q_vector *q_vector = data;
 
    /* Write the ITR value calculated from the previous interrupt. */
    igb_write_itr(q_vector);
 
    napi_schedule(&q_vector->napi);
    return IRQ_HANDLED;
}

函數(shù)接受一個(gè)irq參數(shù),它是觸發(fā)中斷的中斷請(qǐng)求號(hào)(不同隊(duì)列的請(qǐng)求號(hào)不同),和一個(gè)data參數(shù),它是注冊(cè)中斷處理函數(shù)時(shí)傳遞的上下文數(shù)據(jù)。在這個(gè)例子中,data被轉(zhuǎn)換為一個(gè)igb_q_vector指針,它代表一個(gè)網(wǎng)卡的接收或發(fā)送隊(duì)列。

在硬中斷處理中,只完成了很簡(jiǎn)單的工作。首先時(shí)記錄了硬件中斷的頻率(igb_write_itr),其次在napi_schedule中通過(guò)list_add_tail修改CPU變量softnet_data(前面網(wǎng)絡(luò)子系統(tǒng)初始化時(shí)為每個(gè)CPU創(chuàng)建的數(shù)據(jù)結(jié)構(gòu))中的poll_list(一個(gè)雙向列表,其中的設(shè)備都帶有輸入幀等著被處理),將驅(qū)動(dòng)傳過(guò)來(lái)的poll_list添加了進(jìn)來(lái)(每一個(gè)napi_struct都包含一個(gè)list_head類型的poll_list成員,這個(gè)poll_list成員用于將這個(gè)napi_struct實(shí)例鏈接到softnet_data的poll_list中)。之后便調(diào)用__raise_softirq_irqoff(NET_RX_SOFTIRQ)觸發(fā)一個(gè)軟中斷NET_SOFTIRQ,轉(zhuǎn)交給ksoftirq線程進(jìn)行軟中斷處理(輪詢列表中收到的數(shù)據(jù)包)

在Linux內(nèi)核中,list_head類型的數(shù)據(jù)結(jié)構(gòu)通常用于創(chuàng)建并管理一個(gè)鏈表。napi_struct結(jié)構(gòu)中的poll_list成員(類型為list_head)就是用來(lái)將一個(gè)napi_struct實(shí)例鏈接到softnet_data結(jié)構(gòu)中的poll_list成員的。每個(gè)隊(duì)列通常都會(huì)有一個(gè)關(guān)聯(lián)的napi_struct實(shí)例,其中提供了poll輪詢函數(shù)。當(dāng)我們說(shuō)一個(gè)napi_struct被添加到poll_list時(shí),實(shí)際上是指napi_struct中的poll_list字段(也是list_head類型)被插入到softnet_data的poll_list鏈表中,通過(guò)list_head這種間接方式,將napi_struct元素串聯(lián)在一起。

每個(gè)softnet_data結(jié)構(gòu)對(duì)應(yīng)一個(gè)CPU,而poll_list保存了這個(gè)CPU當(dāng)前需要處理的所有napi_struct實(shí)例。當(dāng)一個(gè)napi_struct需要被處理時(shí)(例如,當(dāng)網(wǎng)絡(luò)設(shè)備接收到一個(gè)數(shù)據(jù)包時(shí)),它的poll_list成員就會(huì)被添加到對(duì)應(yīng)的softnet_data的poll_list中。這樣,在處理軟中斷時(shí),內(nèi)核只需要遍歷每個(gè)CPU的softnet_data的poll_list,就可以找到并處理所有需要處理的napi_struct實(shí)例。這個(gè)過(guò)程通常是在net_rx_action函數(shù)中進(jìn)行的,這個(gè)函數(shù)會(huì)遍歷poll_list并調(diào)用每個(gè)napi_struct實(shí)例的輪詢函數(shù)。

2)ksoftirqd內(nèi)核線程處理軟中斷

深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核是如何接收到網(wǎng)絡(luò)包的,網(wǎng)絡(luò),網(wǎng)絡(luò),linux,tcp/ip,網(wǎng)卡,網(wǎng)絡(luò)協(xié)議

網(wǎng)絡(luò)包的接受處理過(guò)程主要都在ksoftirqd內(nèi)核線程中完成。ksoftirqd在創(chuàng)建完成之后就進(jìn)入線程循環(huán)函數(shù),如果硬中斷設(shè)置了NET_RX_SOFTIRQ這里自然就能讀取得到。接下來(lái)會(huì)根據(jù)當(dāng)前CPU的軟中斷類型(判斷softirq_pending標(biāo)志,在硬中斷中設(shè)置了)調(diào)用其注冊(cè)的action方法(同樣是在網(wǎng)絡(luò)子系統(tǒng)初始化階段中注冊(cè)的)。

需要注意的是,硬中斷中設(shè)置的軟中斷標(biāo)記,和ksoftirqd中的判斷是否有軟中斷到達(dá),都是基于smp_processor_id()的。這意味著只要硬中斷在哪個(gè)CPU上被響應(yīng),那么軟中斷也是在這個(gè)CPU上處理的。

如果發(fā)現(xiàn)Linux軟中斷的CPU消耗都集中在一個(gè)核心上,正確的做法應(yīng)該是調(diào)整硬中斷的CPU親和性,將硬中斷打散到不同的CPU核上去。

在Linux的NAPI(New API)網(wǎng)絡(luò)子系統(tǒng)中,poll_list是用來(lái)存儲(chǔ)正在輪詢模式下運(yùn)行的網(wǎng)絡(luò)設(shè)備的列表。每個(gè)在poll_list列表中的設(shè)備都會(huì)在軟中斷上下文中被輪詢,以處理這些設(shè)備的接收隊(duì)列中的數(shù)據(jù)包。在軟中斷處理函數(shù)中會(huì)獲取當(dāng)前CPU變量softnet_data,然后停用設(shè)備的硬中斷(防止硬中斷重復(fù)將設(shè)備添加到poll_list中),隨后開始輪詢softnet_data的poll_list中的每個(gè)設(shè)備,并調(diào)用設(shè)備注冊(cè)的poll函數(shù)(網(wǎng)卡驅(qū)動(dòng)程序初始化時(shí)注冊(cè)的)來(lái)處理設(shè)備的接收隊(duì)列中的數(shù)據(jù)包。當(dāng)接收隊(duì)列被處理完畢,設(shè)備會(huì)從poll_list中移除,并重新啟用設(shè)備的中斷。具體的實(shí)現(xiàn)邏輯如下:

static void net_rx_action(struct softirq_action *h){
    struct softnet_data *sd = &__get_cpu_var(softnet_data);
    unsigned long time_limit = jiffies + 2;
    int budget = netdev_budget;
    void *have;
 
    local_irq_disable();
    while(!list_empty(&sd->poll_list)) {
        ......
        n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);
 
        work = 0;
        if(test_bit(NAPI_STATE_SCHED, &n->state)) {
            work = n->poll(n, weight);
            trace_napi_poll(n);
        }
        budget -= work;
    }
}
  • softnet_data結(jié)構(gòu)的實(shí)例sd是使用__get_cpu_var宏獲取的。這個(gè)宏會(huì)返回當(dāng)前CPU的softnet_data實(shí)例。
  • list_empty(&sd->poll_list)檢查當(dāng)前CPU的softnet_data的poll_list是否為空。如果不為空,說(shuō)明有napi_struct實(shí)例需要被處理。
  • 使用list_first_entry宏從poll_list取出第一個(gè)napi_struct實(shí)例。
  • test_bit(NAPI_STATE_SCHED, &n->state)檢查napi_struct的狀態(tài),如果已經(jīng)被調(diào)度(即NAPI_STATE_SCHED標(biāo)志被設(shè)置),則調(diào)用它的輪詢函數(shù)n->poll。
  • n->poll(n, weight)是對(duì)應(yīng)網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序提供的輪詢函數(shù),用于處理網(wǎng)絡(luò)數(shù)據(jù)包。這個(gè)函數(shù)會(huì)返回處理的數(shù)據(jù)包的數(shù)量。
  • trace_napi_poll(n)調(diào)用追蹤函數(shù),用于追蹤和調(diào)試。
  • budget是處理數(shù)據(jù)包的預(yù)算,在每個(gè)軟中斷處理過(guò)程中,處理的數(shù)據(jù)包數(shù)量不能超過(guò)budget。

napi_struct包含一個(gè)指向與之關(guān)聯(lián)的網(wǎng)絡(luò)設(shè)備的指針,以及其他與設(shè)備特定操作(如數(shù)據(jù)包處理)相關(guān)的信息。這些信息可以間接地讓napi_struct獲取和操作相關(guān)的隊(duì)列。

在napi_struct的輪詢函數(shù)執(zhí)行完畢后,如果處理了所有等待處理的數(shù)據(jù)包,napi_struct實(shí)例通常會(huì)從softnet_data的poll_list中移除。這是通過(guò)調(diào)用napi_complete_done或__napi_complete_done這類函數(shù)來(lái)完成的。這樣做的原因是,在輪詢過(guò)程中,一旦napi_struct實(shí)例處理完了所有的數(shù)據(jù)包,就沒(méi)有必要保留在poll_list中了。將其從poll_list中移除可以避免在下一次軟中斷處理時(shí)對(duì)其進(jìn)行無(wú)用的處理,從而提高效率。然而,如果網(wǎng)絡(luò)設(shè)備繼續(xù)接收新的數(shù)據(jù)包,對(duì)應(yīng)的napi_struct實(shí)例可能會(huì)被重新添加到poll_list中,以便在下一次軟中斷處理中被處理。

poll函數(shù)中主要是將數(shù)據(jù)幀從RingBuffer中取下來(lái),然后發(fā)送到協(xié)議棧中。skb被從RingBuffer(數(shù)據(jù)隊(duì)列中包含了指向skb的指針)中取下來(lái)后,會(huì)再申請(qǐng)新的skb掛上去,避免后面的新包到來(lái)時(shí)沒(méi)有skb可用。收取完數(shù)據(jù)之后會(huì)對(duì)其進(jìn)行一些校驗(yàn),判斷數(shù)據(jù)幀是否正確,然后設(shè)置timestamp,VLAN id,protocol等字段。最后poll中還會(huì)將相關(guān)的小包合并成一個(gè)大包,以減少傳送給網(wǎng)絡(luò)棧的包數(shù),有助于減少對(duì)CPU的使用量。

3)網(wǎng)絡(luò)協(xié)議棧處理

netif_receive_skb函數(shù)會(huì)根據(jù)包的協(xié)議進(jìn)行處理,假如是UDP包,將包一次送到ip_rcv、udp_rcv等處理函數(shù)中進(jìn)行處理。具體邏輯在__netif_receive_skb_core函數(shù)中實(shí)現(xiàn)。

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc){
    ......
 
    //pcap邏輯,這里會(huì)將數(shù)據(jù)送入抓包點(diǎn)。tcpdump就是從這個(gè)入口獲取包的
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if(!ptype->dev || ptype->dev == skb->dev) {
            if(pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
    }
    ......
    list_for_each_entry_rcu(ptype,
            &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
        if(ptype->type == type &&
            (ptype->dev == null_or_dev || ptype->dev == skb->dev ||
             ptype->dev == orig_dev)) {
            if(pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
    }
}

代碼首先通過(guò)遍歷ptype_all鏈表處理所有注冊(cè)在此鏈表上的協(xié)議類型。ptype_all鏈表中包含的協(xié)議類型不會(huì)對(duì)數(shù)據(jù)包的協(xié)議類型做任何特殊假設(shè),因此所有的數(shù)據(jù)包都會(huì)被傳遞給這個(gè)鏈表中的處理函數(shù)。這個(gè)階段通常用于那些需要處理所有數(shù)據(jù)包的組件,例如網(wǎng)絡(luò)抓包工具(比如tcpdump)。tcmpdump是通過(guò)虛擬協(xié)議的方式工作的,它會(huì)將抓包函數(shù)以協(xié)議的形式掛到ptype_all上。

然后,代碼通過(guò)遍歷ptype_base哈希表來(lái)處理數(shù)據(jù)包的具體協(xié)議類型。ptype_base哈希表中包含的協(xié)議類型會(huì)對(duì)數(shù)據(jù)包的協(xié)議類型做特殊處理,因此只有滿足特定協(xié)議類型的數(shù)據(jù)包才會(huì)被傳遞給這個(gè)哈希表中的處理函數(shù)。ip_rcv函數(shù)地址就是存在這個(gè)哈希表中的。

在這兩個(gè)階段中,deliver_skb函數(shù)用于將數(shù)據(jù)包傳遞給一個(gè)協(xié)議處理函數(shù)。如果協(xié)議處理函數(shù)返回非零值,那么數(shù)據(jù)包的處理就會(huì)在這里結(jié)束;否則,數(shù)據(jù)包的處理會(huì)繼續(xù)進(jìn)行,直到所有的協(xié)議處理函數(shù)都被嘗試過(guò)。

4)IP層處理

對(duì)于IP包而言,在deliver_skb函數(shù)中會(huì)調(diào)用pt_prev->func來(lái)調(diào)用它的處理函數(shù),進(jìn)入ip_rcv(如果是ARP包則進(jìn)入arp_rcv)

//file: net/ipv4/ip_input.c
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev){
    ......
    return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);
}

參數(shù)skb是指向表示數(shù)據(jù)包的sk_buff結(jié)構(gòu)的指針,dev是接收到這個(gè)數(shù)據(jù)包的網(wǎng)絡(luò)設(shè)備,pt是與數(shù)據(jù)包協(xié)議類型匹配的packet_type結(jié)構(gòu),orig_dev是原始接收設(shè)備(在處理VLAN和其他封裝協(xié)議時(shí),dev和orig_dev可能會(huì)不同)。

在函數(shù)體中,NF_HOOK是一個(gè)宏,用于調(diào)用Netfilter框架的鉤子。Netfilter是Linux內(nèi)核的一部分,用于實(shí)現(xiàn)防火墻和其他網(wǎng)絡(luò)處理功能。NF_HOOK宏的參數(shù)表示要調(diào)用的鉤子類型和參數(shù)。

在這個(gè)例子中,NFPROTO_IPV4表示鉤子函數(shù)處理的是IPv4協(xié)議,NF_INET_PRE_ROUTING表示鉤子函數(shù)是在路由決策之前被調(diào)用的。最后一個(gè)參數(shù)ip_rcv_finish是在Netfilter處理完成后,用于繼續(xù)處理數(shù)據(jù)包的函數(shù)。

簡(jiǎn)單來(lái)說(shuō),這段代碼是在數(shù)據(jù)包被接收和解析為IPv4協(xié)議后,但在路由決策之前,通過(guò)Netfilter進(jìn)行進(jìn)一步處理,比如網(wǎng)絡(luò)地址轉(zhuǎn)換(NAT)、防火墻過(guò)濾等操作。完成這些操作后,處理流程會(huì)繼續(xù)到ip_rcv_finish函數(shù)。

在ip_rcv_finish函數(shù)中經(jīng)過(guò)層層調(diào)用最終來(lái)到ip_local_deliver_finish函數(shù),在這個(gè)函數(shù)中會(huì)使用inet_protos拿到協(xié)議的函數(shù)地址,根據(jù)包中的協(xié)議類型選擇分發(fā)。在這里skb包將會(huì)進(jìn)一步被派送到更上層的協(xié)議中,UDP或TCP

4、小結(jié)

開始收包前的準(zhǔn)備工作:

  1. 系統(tǒng)初始化時(shí)創(chuàng)建ksoftirqd線程
  2. 網(wǎng)絡(luò)子系統(tǒng)初始化,為每個(gè)CPU初始化softnet_data,為網(wǎng)絡(luò)收發(fā)軟中斷設(shè)置處理函數(shù)
  3. 協(xié)議棧注冊(cè),為ARP、IP、ICMP、UDP、TCP等協(xié)議注冊(cè)處理函數(shù)
  4. 網(wǎng)卡驅(qū)動(dòng)初始化,準(zhǔn)備好DMA,注冊(cè)ethtool實(shí)現(xiàn)函數(shù)和netdvice_ops等變量,初始化NAPI
  5. 啟動(dòng)網(wǎng)卡,分配RX、TX隊(duì)列,為每個(gè)隊(duì)列注冊(cè)硬中斷對(duì)應(yīng)的處理函數(shù),打開硬中斷等待數(shù)據(jù)包

數(shù)據(jù)到來(lái)后的處理:

  1. 數(shù)據(jù)進(jìn)入網(wǎng)卡Rx FIFO,通過(guò)DMA寫入內(nèi)存的RingBuffer,向CPU發(fā)起硬中斷
  2. CPU響應(yīng)硬中斷,調(diào)用網(wǎng)卡啟動(dòng)時(shí)注冊(cè)的中斷處理函數(shù)
  3. 中斷處理函數(shù)中將驅(qū)動(dòng)傳來(lái)的poll_list添加到CPU對(duì)應(yīng)的softnet_data的poll_list,發(fā)起軟中斷
  4. 內(nèi)核線程ksoftirqd發(fā)現(xiàn)軟中斷請(qǐng)求,關(guān)閉硬中斷
  5. ksoftirqd線程根據(jù)軟中斷類型選擇處理函數(shù),調(diào)用驅(qū)動(dòng)的poll函數(shù)收包
  6. poll函數(shù)摘下RIngBuffer上的skb,發(fā)到協(xié)議棧,并重新申請(qǐng)新的skb
  7. 協(xié)議棧根據(jù)數(shù)據(jù)幀的協(xié)議類型,找到對(duì)應(yīng)的處理函數(shù),如ip_rcv
  8. ip_rcv將包發(fā)送到上層協(xié)議處理函數(shù),如udp_rcv或tcp_rcv_v4

三、問(wèn)題解答

  1. RingBuffer是什么,為什么會(huì)丟包

    • RingBuffer是內(nèi)存中的一塊特殊區(qū)域,這個(gè)數(shù)據(jù)結(jié)構(gòu)包括數(shù)據(jù)環(huán)形隊(duì)列數(shù)組和描述符環(huán)形隊(duì)列數(shù)組。網(wǎng)卡在收到數(shù)據(jù)的時(shí)候以DMA的方式將包寫到RingBuffer中,軟中斷收包的時(shí)候skb取走,并申請(qǐng)新的skb重新掛上去。即指針數(shù)組是預(yù)先分配好的,而指向的skb是隨著收包過(guò)程動(dòng)態(tài)申請(qǐng)的。
    • RingBuffer有大小和長(zhǎng)度限制,當(dāng)滿了后新來(lái)的數(shù)據(jù)包就會(huì)被丟棄??梢酝ㄟ^(guò)ethtool工具(ethtool -g eth0)查看它的長(zhǎng)度,通過(guò)ethtool工具(ethtool -S eth0)或ifconfig工具(overruns指標(biāo))查看是否有RingBuffer移除發(fā)生。
    • 通過(guò)ethtool工具(ethtool -G eth1 rx 4096 tx 4096)可以修改RingBuffer的隊(duì)列長(zhǎng)度,不能超過(guò)最大允許值
    • 通過(guò)分配更大的RingBuffer可以解決偶發(fā)的瞬時(shí)丟包,但是排隊(duì)的包過(guò)多會(huì)增加處理網(wǎng)絡(luò)包的延時(shí)。
  2. 網(wǎng)絡(luò)相關(guān)的硬中斷、軟中斷是什么

    • 網(wǎng)卡將數(shù)據(jù)放到RingBuffer后就發(fā)起硬中斷,通知CPU處理
    • 硬中斷觸發(fā)軟中斷NET_SOFTIRQ,由ksoftirqd線程進(jìn)行處理
  3. Linux里的ksoftirqd內(nèi)核線程是干什么的

    • 內(nèi)核線程ksoftirqd中包含了所有的軟中斷處理邏輯,根據(jù)軟中斷的類型來(lái)執(zhí)行不同的處理函數(shù)。
    • 軟中斷的信息可以從/proc/softirqd中讀?。╟at /proc/softirqd)
  4. 為什么網(wǎng)卡開啟多隊(duì)列能提升網(wǎng)絡(luò)性能

    • 現(xiàn)在主流的網(wǎng)卡基本上都支持多隊(duì)列,通過(guò)ethtool(ethtool -l eth0)可以查看當(dāng)前網(wǎng)卡的多隊(duì)列情況,會(huì)顯示支持的最大隊(duì)列和當(dāng)前開啟的隊(duì)列數(shù)。通過(guò)sysfs偽文件系統(tǒng)可以看到真正生效的隊(duì)列數(shù)(ls /sys/class/net/eth0/queues)
    • 如果想增大隊(duì)列數(shù)量,可以通過(guò)ethtool -L eth0 combined 隊(duì)列數(shù)實(shí)現(xiàn)
    • 通過(guò)/proc/interrupts可以看到這些隊(duì)列對(duì)應(yīng)的硬件中斷號(hào),通過(guò)中斷號(hào)對(duì)應(yīng)的smp_affinity(cat /proc/irq/中斷號(hào)/smp_affinity)可以查看隊(duì)列親和的CPU核是哪一個(gè)
    • 每個(gè)隊(duì)列會(huì)有獨(dú)立的、不同的中斷號(hào),所以不同的隊(duì)列在收到數(shù)據(jù)包后可以分別向不同的CPU發(fā)起硬中斷通知,而哪個(gè)核響應(yīng)的硬中斷,那么該硬中斷發(fā)起的軟中斷任務(wù)就必然由該核來(lái)處理
    • 如果網(wǎng)絡(luò)包的接收頻率高而導(dǎo)致個(gè)別核si偏高,可以通過(guò)加大網(wǎng)卡隊(duì)列數(shù),并設(shè)置每個(gè)隊(duì)列中斷號(hào)上的smp_affinity,將各個(gè)隊(duì)列的硬中斷打散到不同的CPU,這樣后續(xù)的軟中斷的CPU開銷也可以分?jǐn)偟蕉鄠€(gè)核
  5. tcpdump是如何工作的

    • tcpdump工作在設(shè)備層,通過(guò)虛擬協(xié)議的方式工作的。它通過(guò)調(diào)用packet_create將抓包函數(shù)以協(xié)議的形式掛到ptype_all上。
    • 當(dāng)收包的時(shí)候,驅(qū)動(dòng)中實(shí)現(xiàn)的igb_poll函數(shù)最終會(huì)調(diào)用到_netif_receive_skb_core,這個(gè)函數(shù)會(huì)在將包發(fā)送到協(xié)議棧函數(shù)如ip_rcv之前,將包先送到ptype_all抓包點(diǎn)。
  6. iptable/netfilter是在哪一層實(shí)現(xiàn)的

    • 主要在IP、ARP等層實(shí)現(xiàn),可以通過(guò)搜索對(duì)NF_HOOK函數(shù)的引用來(lái)深入了解其實(shí)現(xiàn)
    • 如果配置過(guò)于復(fù)雜的規(guī)則會(huì)消耗過(guò)多的CPU,加大網(wǎng)絡(luò)延遲
  7. tcpdump能否抓到被iptable封禁的包

    • netfilter工作在IP、ARP等層,所以iptable封禁規(guī)則影響不到tcpdump抓包
    • 發(fā)包的時(shí)候則相反,netfilter在協(xié)議層就被過(guò)濾掉了,所以tcpdump獲取不到
  8. 網(wǎng)絡(luò)接收過(guò)程中如何查看CPU開銷

    • top指令可以查看。其中hi是處理硬中斷的開銷,si是處理軟中斷的開銷,都是通過(guò)百分比的形式來(lái)展示
  9. DPDK是什么

    • 數(shù)據(jù)包的接收需要內(nèi)核進(jìn)行非常復(fù)雜的工作,并且在數(shù)據(jù)接受完之后還需要將數(shù)據(jù)復(fù)制到用戶空間的內(nèi)存中。如果用戶進(jìn)程當(dāng)前是阻塞的,還需要喚醒它,又是一次上下文切換的開銷。
    • DPDK就是用于讓用戶進(jìn)程繞開內(nèi)核協(xié)議棧,自己直接從網(wǎng)卡接收數(shù)據(jù),省掉了繁雜的內(nèi)核協(xié)議棧處理、數(shù)據(jù)拷貝開銷、喚醒用戶進(jìn)程開銷等。

參考資料

Linux內(nèi)核網(wǎng)絡(luò)數(shù)據(jù)包處理流程 - 知乎 (zhihu.com)

《深入理解Linux網(wǎng)絡(luò)》—— 張彥飛文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-535996.html

到了這里,關(guān)于深入理解Linux內(nèi)核網(wǎng)絡(luò)——內(nèi)核是如何接收到網(wǎng)絡(luò)包的的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 深入理解 Linux 內(nèi)核

    深入理解 Linux 內(nèi)核

    Linux 內(nèi)核設(shè)計(jì)與實(shí)現(xiàn) 深入理解 Linux 內(nèi)核 深入理解 Linux 內(nèi)核(二) Linux 設(shè)備驅(qū)動(dòng)程序 Linux設(shè)備驅(qū)動(dòng)開發(fā)詳解 ??本文主要用來(lái)摘錄《深入理解 Linux 內(nèi)核》一書中學(xué)習(xí)知識(shí)點(diǎn),本書基于 Linux 2.6.11 版本,源代碼摘錄基于 Linux 2.6.34 ,兩者之間可能有些出入。 ??可參考 ? 1、

    2023年04月27日
    瀏覽(28)
  • 深入理解Linux內(nèi)核--內(nèi)存尋址

    使用80x86微處理器時(shí),需區(qū)分三種不同地址。 1.邏輯地址,每一個(gè)邏輯地址都由一個(gè)段和偏移量組成。 2.線性地址(虛擬地址),如果是32位系統(tǒng),則位一個(gè)32位無(wú)符號(hào)整數(shù)??杀磉_(dá)高達(dá)4GB地址。 3.物理地址,用于內(nèi)存芯片級(jí)內(nèi)存單元尋址。與從微處理器的地址引腳發(fā)到內(nèi)存總

    2024年02月13日
    瀏覽(26)
  • 深入理解Linux 內(nèi)核追蹤機(jī)制

    深入理解Linux 內(nèi)核追蹤機(jī)制

    Linux 存在眾多 tracing tools,比如 ftrace、perf,他們可用于內(nèi)核的調(diào)試、提高內(nèi)核的可觀測(cè)性。眾多的工具也意味著繁雜的概念,諸如 tracepoint、trace events、kprobe、eBPF 等,甚至讓人搞不清楚他們到底是干什么的。本文嘗試?yán)砬暹@些概念。 ? Probe Handler 如果我們想要追蹤內(nèi)核的一

    2024年02月15日
    瀏覽(30)
  • 深入理解Linux內(nèi)核——內(nèi)存管理(3)

    深入理解Linux內(nèi)核——內(nèi)存管理(3)

    提要:本系列文章主要參考 MIT 6.828課程 以及兩本書籍 《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》 對(duì)Linux內(nèi)核內(nèi)容進(jìn)行總結(jié)。 內(nèi)存管理的實(shí)現(xiàn)覆蓋了多個(gè)領(lǐng)域: 內(nèi)存中的物理內(nèi)存頁(yè)的管理 分配大塊內(nèi)存的伙伴系統(tǒng) 分配較小內(nèi)存的slab、slub、slob分配器 分配非連續(xù)內(nèi)存塊的

    2024年02月13日
    瀏覽(28)
  • 深入理解Linux內(nèi)核——內(nèi)存管理(2)

    深入理解Linux內(nèi)核——內(nèi)存管理(2)

    提要:本系列文章主要參考 MIT 6.828課程 以及兩本書籍 《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》 對(duì)Linux內(nèi)核內(nèi)容進(jìn)行總結(jié)。 內(nèi)存管理的實(shí)現(xiàn)覆蓋了多個(gè)領(lǐng)域: 內(nèi)存中的物理內(nèi)存頁(yè)的管理 分配大塊內(nèi)存的伙伴系統(tǒng) 分配較小內(nèi)存的slab、slub、slob分配器 分配非連續(xù)內(nèi)存塊的

    2024年02月13日
    瀏覽(28)
  • 深入理解Linux內(nèi)核——內(nèi)存管理(1)

    深入理解Linux內(nèi)核——內(nèi)存管理(1)

    提要:本系列文章主要參考 MIT 6.828課程 以及兩本書籍 《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》 對(duì)Linux內(nèi)核內(nèi)容進(jìn)行總結(jié)。 內(nèi)存管理的實(shí)現(xiàn)覆蓋了多個(gè)領(lǐng)域: 內(nèi)存中的物理內(nèi)存頁(yè)的管理 分配大塊內(nèi)存的伙伴系統(tǒng) 分配較小內(nèi)存的slab、slub、slob分配器 分配非連續(xù)內(nèi)存塊的

    2024年02月13日
    瀏覽(26)
  • 【深入理解Linux內(nèi)核鎖】三、原子操作

    系列文章 : 我的圈子:高級(jí)工程師聚集地 【深入理解Linux鎖機(jī)制】一、內(nèi)核鎖的由來(lái) 【深入理解Linux鎖機(jī)制】二、中斷屏蔽 【深入理解Linux鎖機(jī)制】三、原子操作 【深入理解Linux鎖機(jī)制】四、自旋鎖 【深入理解Linux鎖機(jī)制】五、衍生自旋鎖 【深入理解Linux鎖機(jī)制】六、信號(hào)

    2024年02月12日
    瀏覽(28)
  • 深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

    深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

    提要:本系列文章主要參考 MIT 6.828課程 以及兩本書籍 《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》 對(duì)Linux內(nèi)核內(nèi)容進(jìn)行總結(jié)。 內(nèi)存管理的實(shí)現(xiàn)覆蓋了多個(gè)領(lǐng)域: 內(nèi)存中的物理內(nèi)存頁(yè)的管理 分配大塊內(nèi)存的伙伴系統(tǒng) 分配較小內(nèi)存的slab、slub、slob分配器 分配非連續(xù)內(nèi)存塊的

    2024年02月10日
    瀏覽(22)
  • Linux源碼解讀系列是一套深入剖析Linux內(nèi)核源碼的教程,旨在幫助讀者理解Linux操作系統(tǒng)的底層原理和工作機(jī)制

    Linux源碼解讀系列是一套深入剖析Linux內(nèi)核源碼的教程,旨在幫助讀者理解Linux操作系統(tǒng)的底層原理和工作機(jī)制

    Linux源碼解讀系列是一套深入剖析Linux內(nèi)核源碼的教程,旨在幫助讀者理解Linux操作系統(tǒng)的底層原理和工作機(jī)制。該系列教程從Linux內(nèi)核的各個(gè)模塊入手,逐一分析其源碼實(shí)現(xiàn),并結(jié)合實(shí)際應(yīng)用場(chǎng)景進(jìn)行講解。通過(guò)學(xué)習(xí)本系列,讀者可以深入了解Linux操作系統(tǒng)的底層機(jī)制,掌握

    2024年01月21日
    瀏覽(26)
  • 深入理解Linux網(wǎng)絡(luò)——本機(jī)網(wǎng)絡(luò)IO

    深入理解Linux網(wǎng)絡(luò)——本機(jī)網(wǎng)絡(luò)IO

    系列文章: 深入理解Linux網(wǎng)絡(luò)——內(nèi)核是如何接收到網(wǎng)絡(luò)包的 深入理解Linux網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之同步阻塞方案(BIO) 深入理解Linux網(wǎng)絡(luò)——內(nèi)核與用戶進(jìn)程協(xié)作之多路復(fù)用方案(epoll) 深入理解Linux網(wǎng)絡(luò)——內(nèi)核是如何發(fā)送網(wǎng)絡(luò)包的 深入理解Linux網(wǎng)絡(luò)——本機(jī)網(wǎng)絡(luò)

    2024年02月15日
    瀏覽(26)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包