0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Linux網(wǎng)絡(luò)子系統(tǒng)的實(shí)現(xiàn)

科技綠洲 ? 來源:Linux開發(fā)架構(gòu)之路 ? 作者:Linux開發(fā)架構(gòu)之路 ? 2023-11-11 11:25 ? 次閱讀

Linux網(wǎng)絡(luò)子系統(tǒng)的分層

Linux網(wǎng)絡(luò)子系統(tǒng)實(shí)現(xiàn)需要:

l 支持不同的協(xié)議族 ( INET, INET6, UNIX, NETLINK...)

l 支持不同的網(wǎng)絡(luò)設(shè)備

l 支持統(tǒng)一的BSD socket API

需要屏蔽協(xié)議、硬件、平臺(tái)(API)的差異,因而采用分層結(jié)構(gòu):

系統(tǒng)調(diào)用提供用戶的應(yīng)用程序訪問內(nèi)核的唯一途徑。協(xié)議無(wú)關(guān)接口由socket layer來實(shí)現(xiàn)的,其提供一組通用功能,以支持各種不同的協(xié)議。網(wǎng)絡(luò)協(xié)議層為socket層提供具體協(xié)議接口——proto{},實(shí)現(xiàn)具體的協(xié)議細(xì)節(jié)。設(shè)備無(wú)關(guān)接口,提供一組通用函數(shù)供底層網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序使用。設(shè)備驅(qū)動(dòng)與特定網(wǎng)卡設(shè)備相關(guān),定義了具體的協(xié)議細(xì)節(jié),會(huì)分配一個(gè)net_device結(jié)構(gòu),然后用其必需的例程進(jìn)行初始化。

TCP/IP分層模型

在TCP/IP網(wǎng)絡(luò)分層模型里,整個(gè)協(xié)議棧被分成了物理層、鏈路層、網(wǎng)絡(luò)層,傳輸層和應(yīng)用層。物理層對(duì)應(yīng)的是網(wǎng)卡和網(wǎng)線,應(yīng)用層對(duì)應(yīng)的是我們常見的Nginx,F(xiàn)TP等等各種應(yīng)用。Linux實(shí)現(xiàn)的是鏈路層、網(wǎng)絡(luò)層和傳輸層這三層。

在Linux內(nèi)核實(shí)現(xiàn)中,鏈路層協(xié)議靠網(wǎng)卡驅(qū)動(dòng)來實(shí)現(xiàn),內(nèi)核協(xié)議棧來實(shí)現(xiàn)網(wǎng)絡(luò)層和傳輸層。內(nèi)核對(duì)更上層的應(yīng)用層提供socket接口來供用戶進(jìn)程訪問。我們用Linux的視角來看到的TCP/IP網(wǎng)絡(luò)分層模型應(yīng)該是下面這個(gè)樣子的。

圖片

首先我們梳理一下每層模型的職責(zé):

鏈路層:對(duì)0和1進(jìn)行分組,定義數(shù)據(jù)幀,確認(rèn)主機(jī)的物理地址,傳輸數(shù)據(jù);

網(wǎng)絡(luò)層:定義IP地址,確認(rèn)主機(jī)所在的網(wǎng)絡(luò)位置,并通過IP進(jìn)行MAC尋址,對(duì)外網(wǎng)數(shù)據(jù)包進(jìn)行路由轉(zhuǎn)發(fā);

傳輸層:定義端口,確認(rèn)主機(jī)上應(yīng)用程序的身份,并將數(shù)據(jù)包交給對(duì)應(yīng)的應(yīng)用程序;

應(yīng)用層:定義數(shù)據(jù)格式,并按照對(duì)應(yīng)的格式解讀數(shù)據(jù)。

然后再把每層模型的職責(zé)串聯(lián)起來,用一句通俗易懂的話講就是:

當(dāng)你輸入一個(gè)網(wǎng)址并按下回車鍵的時(shí)候,首先,應(yīng)用層協(xié)議對(duì)該請(qǐng)求包做了格式定義;緊接著傳輸層協(xié)議加上了雙方的端口號(hào),確認(rèn)了雙方通信的應(yīng)用程序;然后網(wǎng)絡(luò)協(xié)議加上了雙方的IP地址,確認(rèn)了雙方的網(wǎng)絡(luò)位置;最后鏈路層協(xié)議加上了雙方的MAC地址,確認(rèn)了雙方的物理位置,同時(shí)將數(shù)據(jù)進(jìn)行分組,形成數(shù)據(jù)幀,采用廣播方式,通過傳輸介質(zhì)發(fā)送給對(duì)方主機(jī)。而對(duì)于不同網(wǎng)段,該數(shù)據(jù)包首先會(huì)轉(zhuǎn)發(fā)給網(wǎng)關(guān)路由器,經(jīng)過多次轉(zhuǎn)發(fā)后,最終被發(fā)送到目標(biāo)主機(jī)。目標(biāo)機(jī)接收到數(shù)據(jù)包后,采用對(duì)應(yīng)的協(xié)議,對(duì)幀數(shù)據(jù)進(jìn)行組裝,然后再通過一層一層的協(xié)議進(jìn)行解析,最終被應(yīng)用層的協(xié)議解析并交給服務(wù)器處理。

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

圖片

基于TCP/IP協(xié)議棧的send/recv在應(yīng)用層,傳輸層,網(wǎng)絡(luò)層和鏈路層中具體函數(shù)調(diào)用過程已經(jīng)有很多人研究,本文引用一張比較完善的圖如下:

圖片

以上說明基本大致說明了TCP/IP中TCP,UDP協(xié)議包在網(wǎng)絡(luò)子系統(tǒng)中的實(shí)現(xiàn)流程。本文主要在鏈路層中,即關(guān)于網(wǎng)卡收?qǐng)?bào)觸發(fā)中斷到進(jìn)入網(wǎng)絡(luò)層之間的過程探究。

Linux 網(wǎng)卡收包時(shí)的中斷處理問題

中斷,一般指硬件中斷,多由系統(tǒng)自身或與之鏈接的外設(shè)(如鍵盤、鼠標(biāo)、網(wǎng)卡等)產(chǎn)生。中斷首先是處理器提供的一種響應(yīng)外設(shè)請(qǐng)求的機(jī)制,是處理器硬件支持的特性。一個(gè)外設(shè)通過產(chǎn)生一種電信號(hào)通知中斷控制器,中斷控制器再向處理器發(fā)送相應(yīng)的信號(hào)。處理器檢測(cè)到了這個(gè)信號(hào)后就會(huì)打斷自己當(dāng)前正在做的工作,轉(zhuǎn)而去處理這次中斷(所以才叫中斷)。當(dāng)然在轉(zhuǎn)去處理中斷和中斷返回時(shí)都有保護(hù)現(xiàn)場(chǎng)和返回現(xiàn)場(chǎng)的操作,這里不贅述。

   那軟中斷又是什么呢?我們知道在中斷處理時(shí)CPU沒法處理其它事物,對(duì)于網(wǎng)卡來說,如果每次網(wǎng)卡收包時(shí)中斷的時(shí)間都過長(zhǎng),那很可能造成丟包的可能性。當(dāng)然我們不能完全避免丟包的可能性,以太包的傳輸是沒有100%保證的,所以網(wǎng)絡(luò)才有協(xié)議棧,通過高層的協(xié)議來保證連續(xù)數(shù)據(jù)傳輸?shù)臄?shù)據(jù)完整性(比如在協(xié)議發(fā)現(xiàn)丟包時(shí)要求重傳)。但是即使有協(xié)議保證,那我們也不能肆無(wú)忌憚的使用中斷,中斷的時(shí)間越短越好,盡快放開處理器,讓它可以去響應(yīng)下次中斷甚至進(jìn)行調(diào)度工作。基于這樣的考慮,我們將中斷分成了上下兩部分,上半部分就是上面說的中斷部分,需要快速及時(shí)響應(yīng),同時(shí)需要越快結(jié)束越好。而下半部分就是完成一些可以推后執(zhí)行的工作。對(duì)于網(wǎng)卡收包來說,網(wǎng)卡收到數(shù)據(jù)包,通知內(nèi)核數(shù)據(jù)包到了,中斷處理將數(shù)據(jù)包存入內(nèi)存這些都是急切需要完成的工作,放到上半部完成。而解析處理數(shù)據(jù)包的工作則可以放到下半部去執(zhí)行。

軟中斷就是下半部使用的一種機(jī)制,它通過軟件模仿硬件中斷的處理過程,但是和硬件沒有關(guān)系,單純的通過軟件達(dá)到一種異步處理的方式。其它下半部的處理機(jī)制還包括tasklet,工作隊(duì)列等。依據(jù)所處理的場(chǎng)合不同,選擇不同的機(jī)制,網(wǎng)卡收包一般使用軟中斷。對(duì)應(yīng)NET_RX_SOFTIRQ這個(gè)軟中斷,軟中斷的類型如下:

enum
{
        HI_SOFTIRQ=0,
        TIMER_SOFTIRQ,
        NET_TX_SOFTIRQ,
        NET_RX_SOFTIRQ,
        BLOCK_SOFTIRQ,
        IRQ_POLL_SOFTIRQ,
        TASKLET_SOFTIRQ,
        SCHED_SOFTIRQ,
        HRTIMER_SOFTIRQ,
        RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
        NR_SOFTIRQS
};

通過以上可以了解到,Linux中斷注冊(cè)顯然應(yīng)該包括網(wǎng)卡的硬中斷,包處理的軟中斷兩個(gè)步驟。

l 注冊(cè)網(wǎng)卡中斷

我們以一個(gè)具體的網(wǎng)卡驅(qū)動(dòng)為例,比如e1000。其模塊初始化函數(shù)就是:

static int __init e1000_init_module(void)
{
int ret;
        pr_info("%s - version %sn", e1000_driver_string, e1000_driver_version);
        pr_info("%sn", e1000_copyright);
        ret = pci_register_driver(&e1000_driver);
...
return ret;

}

其中e1000_driver這個(gè)結(jié)構(gòu)體是一個(gè)關(guān)鍵,這個(gè)結(jié)構(gòu)體中很主要的一個(gè)方法就是.probe方法,也就是e1000_probe():

/**                                                  

 * e1000_probe - Device Initialization Routine         
 * @pdev: PCI device information struct                    
 * @ent: entry in e1000_pci_tbl     
 *                                
 * Returns 0 on success, negative on failure                                                                               
 *                                                                                                               
 * e1000_probe initializes an adapter identified by a pci_dev structure.                                                               
 * The OS initialization, configuring of the adapter private structure,                                                                  
 * and a hardware reset occur.                                                      
 **/
static int e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
...
...
        netdev- >netdev_ops = &e1000_netdev_ops;
        e1000_set_ethtool_ops(netdev);
...
...
}

這個(gè)函數(shù)很長(zhǎng),我們不都列出來,這是e1000主要的初始化函數(shù),即使從注釋都能看出來。我們留意其注冊(cè)了netdev的netdev_ops,用的是e1000_netdev_ops這個(gè)結(jié)構(gòu)體:

static const struct net_device_ops e1000_netdev_ops = {
        .ndo_open               = e1000_open,
        .ndo_stop               = e1000_close,
        .ndo_start_xmit         = e1000_xmit_frame,
        .ndo_set_rx_mode        = e1000_set_rx_mode,
        .ndo_set_mac_address    = e1000_set_mac,
        .ndo_tx_timeout         = e1000_tx_timeout,
...
...
};

這個(gè)e1000的方法集里有一個(gè)重要的方法,e1000_open,我們要說的中斷的注冊(cè)就從這里開始:

/**           
 * e1000_open - Called when a network interface is made active  
 * @netdev: network interface device structure            
 *                                                 
 * Returns 0 on success, negative value on failure     
 *     
 * The open entry point is called when a network interface is made                                                                                                    
 * active by the system (IFF_UP).  At this point all resources needed                                                                            
 * for transmit and receive operations are allocated, the interrupt                                                     
 * handler is registered with the OS, the watchdog task is started,                                                                                                     
 * and the stack is notified that the interface is ready.                                                                                                             
 **/
int e1000_open(struct net_device *netdev)
{
struct e1000_adapter *adapter = netdev_priv(netdev);
struct e1000_hw *hw = &adapter- >hw;
...
...
        err = e1000_request_irq(adapter);
...
}

e1000在這里注冊(cè)了中斷:

static int e1000_request_irq(struct e1000_adapter *adapter)
{
struct net_device *netdev = adapter- >netdev;
        irq_handler_t handler = e1000_intr;
int irq_flags = IRQF_SHARED;
int err;
        err = request_irq(adapter- >pdev- >irq, handler, irq_flags, netdev- >name,
...
...
}

如上所示,這個(gè)被注冊(cè)的中斷處理函數(shù),也就是handler,就是e1000_intr()。我們不展開這個(gè)中斷處理函數(shù)看了,我們知道中斷處理函數(shù)在這里被注冊(cè)了,在網(wǎng)絡(luò)包來的時(shí)候會(huì)觸發(fā)這個(gè)中斷函數(shù)。

l 注冊(cè)軟中斷

內(nèi)核初始化期間,softirq_init會(huì)注冊(cè)TASKLET_SOFTIRQ以及HI_SOFTIRQ相關(guān)聯(lián)的處理函數(shù)。

void __init softirq_init(void)
{
    ......
    open_softirq(TASKLET_SOFTIRQ, tasklet_action);
    open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

網(wǎng)絡(luò)子系統(tǒng)分兩種soft IRQ。NET_TX_SOFTIRQ和NET_RX_SOFTIRQ,分別處理發(fā)送數(shù)據(jù)包和接收數(shù)據(jù)包。這兩個(gè)soft IRQ在net_dev_init函數(shù)(net/core/dev.c)中注冊(cè):

open_softirq(NET_TX_SOFTIRQ, net_tx_action);

open_softirq(NET_RX_SOFTIRQ, net_rx_action);

收發(fā)數(shù)據(jù)包的軟中斷處理函數(shù)被注冊(cè)為net_rx_action和net_tx_action。其中open_softirq實(shí)現(xiàn)為:

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

圖片

圖片

  • 從硬中斷到軟中斷

圖片

Linux 網(wǎng)絡(luò)啟動(dòng)的準(zhǔn)備工作

首先在開始收包之前,Linux要做許多的準(zhǔn)備工作:

  1. 創(chuàng)建ksoftirqd線程,為它設(shè)置好它自己的線程函數(shù),后面就指望著它來處理軟中斷呢。
  2. 協(xié)議棧注冊(cè),linux要實(shí)現(xiàn)許多協(xié)議,比如arp,icmp,ip,udp,tcp,每一個(gè)協(xié)議都會(huì)將自己的處理函數(shù)注冊(cè)一下,方便包來了迅速找到對(duì)應(yīng)的處理函數(shù)
  3. 網(wǎng)卡驅(qū)動(dòng)初始化,每個(gè)驅(qū)動(dòng)都有一個(gè)初始化函數(shù),內(nèi)核會(huì)讓驅(qū)動(dòng)也初始化一下。在這個(gè)初始化過程中,把自己的DMA準(zhǔn)備好,把NAPI的poll函數(shù)地址告訴內(nèi)核
  4. 啟動(dòng)網(wǎng)卡,分配RX,TX隊(duì)列,注冊(cè)中斷對(duì)應(yīng)的處理函數(shù)

l 創(chuàng)建ksoftirqd內(nèi)核線程

Linux的軟中斷都是在專門的內(nèi)核線程(ksoftirqd)中進(jìn)行的,因此我們非常有必要看一下這些進(jìn)程是怎么初始化的,這樣我們才能在后面更準(zhǔn)確地了解收包過程。該進(jìn)程數(shù)量不是1個(gè),而是N個(gè),其中N等于你的機(jī)器的核數(shù)。

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

圖片

相關(guān)代碼如下:

//file: kernel/softirq.c


static struct smp_hotplug_thread softirq_threads = {
.store
= &ksoftirqd,
.thread_should_run
= ksoftirqd_should_run,
.thread_fn
= run_ksoftirqd,
.thread_comm
= "ksoftirqd/%u",

};

圖片

圖片

當(dāng)ksoftirqd被創(chuàng)建出來以后,它就會(huì)進(jìn)入自己的線程循環(huán)函數(shù)ksoftirqd_should_run和run_ksoftirqd了。不停地判斷有沒有軟中斷需要被處理。這里需要注意的一點(diǎn)是,軟中斷不僅僅只有網(wǎng)絡(luò)軟中斷,還有其它類型。

l 創(chuàng)建ksoftirqd內(nèi)核線程

圖片

linux內(nèi)核通過調(diào)用subsys_initcall來初始化各個(gè)子系統(tǒng),在源代碼目錄里你可以grep出許多對(duì)這個(gè)函數(shù)的調(diào)用。這里我們要說的是網(wǎng)絡(luò)子系統(tǒng)的初始化,會(huì)執(zhí)行到net_dev_init函數(shù)。

圖片

圖片

圖片

在這個(gè)函數(shù)里,會(huì)為每個(gè)CPU都申請(qǐng)一個(gè)softnet_data數(shù)據(jù)結(jié)構(gòu),在這個(gè)數(shù)據(jù)結(jié)構(gòu)里的poll_list是等待驅(qū)動(dòng)程序?qū)⑵鋚oll函數(shù)注冊(cè)進(jìn)來,稍后網(wǎng)卡驅(qū)動(dòng)初始化的時(shí)候我們可以看到這一過程。

另外open_softirq注冊(cè)了每一種軟中斷都注冊(cè)一個(gè)處理函數(shù)。 NET_TX_SOFTIRQ的處理函數(shù)為net_tx_action,NET_RX_SOFTIRQ的為net_rx_action。繼續(xù)跟蹤open_softirq后發(fā)現(xiàn)這個(gè)注冊(cè)的方式是記錄在softirq_vec變量里的。后面ksoftirqd線程收到軟中斷的時(shí)候,也會(huì)使用這個(gè)變量來找到每一種軟中斷對(duì)應(yīng)的處理函數(shù)。

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

內(nèi)核實(shí)現(xiàn)了網(wǎng)絡(luò)層的ip協(xié)議,也實(shí)現(xiàn)了傳輸層的tcp協(xié)議和udp協(xié)議。 這些協(xié)議對(duì)應(yīng)的實(shí)現(xiàn)函數(shù)分別是ip_rcv(),tcp_v4_rcv()和udp_rcv()。和我們平時(shí)寫代碼的方式不一樣的是,內(nèi)核是通過注冊(cè)的方式來實(shí)現(xiàn)的。 Linux內(nèi)核中的fs_initcall和subsys_initcall類似,也是初始化模塊的入口。fs_initcall調(diào)用inet_init后開始網(wǎng)絡(luò)協(xié)議棧注冊(cè)。 通過inet_init,將這些函數(shù)注冊(cè)到了inet_protos和ptype_base數(shù)據(jù)結(jié)構(gòu)中

相關(guān)代碼如下

//file: net/ipv4/af_inet.c

static struct packet_type ip_packet_type __read_mostly = {
    .type = cpu_to_be16(ETH_P_IP),
    .func = ip_rcv,
};

static const struct net_protocol udp_protocol = {
    .handler =  udp_rcv,
    .err_handler =  udp_err,
    .no_policy =    1,
    .netns_ok = 1,
};

static const struct net_protocol tcp_protocol = {
    .early_demux    =   tcp_v4_early_demux,
    .handler    =   tcp_v4_rcv,
    .err_handler    =   tcp_v4_err,
    .no_policy  =   1,
    .netns_ok   =   1,
};

圖片

圖片

圖片

擴(kuò)展一下,如果看一下ip_rcv和udp_rcv等函數(shù)的代碼能看到很多協(xié)議的處理過程。例如,ip_rcv中會(huì)處理netfilter和iptable過濾,如果你有很多或者很復(fù)雜的 netfilter 或 iptables 規(guī)則,這些規(guī)則都是在軟中斷的上下文中執(zhí)行的,會(huì)加大網(wǎng)絡(luò)延遲。再例如,udp_rcv中會(huì)判斷socket接收隊(duì)列是否滿了。對(duì)應(yīng)的相關(guān)內(nèi)核參數(shù)是net.core.rmem_max和net.core.rmem_default。如果有興趣,建議大家好好讀一下inet_init這個(gè)函數(shù)的代碼。

l 網(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)的代碼位于drivers/net/ethernet/intel/igb/igb_main.c

驅(qū)動(dòng)的pci_register_driver調(diào)用完成后,Linux內(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方法(igb_driver的probe方法是igb_probe)。驅(qū)動(dòng)probe方法執(zhí)行的目的就是讓設(shè)備ready,對(duì)于igb網(wǎng)卡,其igb_probe位于drivers/net/ethernet/intel/igb/igb_main.c下。主要執(zhí)行的操作如下:

圖片

第5步中我們看到,網(wǎng)卡驅(qū)動(dòng)實(shí)現(xiàn)了ethtool所需要的接口,也在這里注冊(cè)完成函數(shù)地址的注冊(cè)。當(dāng) ethtool 發(fā)起一個(gè)系統(tǒng)調(diào)用之后,內(nèi)核會(huì)找到對(duì)應(yīng)操作的回調(diào)函數(shù)。對(duì)于igb網(wǎng)卡來說,其實(shí)現(xiàn)函數(shù)都在drivers/net/ethernet/intel/igb/igb_ethtool.c下。 相信你這次能徹底理解ethtool的工作原理了吧? 這個(gè)命令之所以能查看網(wǎng)卡收發(fā)包統(tǒng)計(jì)、能修改網(wǎng)卡自適應(yīng)模式、能調(diào)整RX 隊(duì)列的數(shù)量和大小,是因?yàn)閑thtool命令最終調(diào)用到了網(wǎng)卡驅(qū)動(dòng)的相應(yīng)方法,而不是ethtool本身有這個(gè)超能力。

第6步注冊(cè)的igb_netdev_ops中包含的是igb_open等函數(shù),該函數(shù)在網(wǎng)卡被啟動(dòng)的時(shí)候會(huì)被調(diào)用。

//file: drivers/net/ethernet/intel/igb/igb_main.
......
static const struct net_device_ops igb_netdev_ops = {
  .ndo_open               = igb_open,
  .ndo_stop               = igb_close,
  .ndo_start_xmit         = igb_xmit_frame,
  .ndo_get_stats64        = igb_get_stats64,
  .ndo_set_rx_mode        = igb_set_rx_mode,
  .ndo_set_mac_address    = igb_set_mac,
  .ndo_change_mtu         = igb_change_mtu,
  .ndo_do_ioctl           = igb_ioctl,......
}

第7步中,在igb_probe初始化過程中,還調(diào)用到了igb_alloc_q_vector。他注冊(cè)了一個(gè)NAPI機(jī)制所必須的poll函數(shù),對(duì)于igb網(wǎng)卡驅(qū)動(dòng)來說,這個(gè)函數(shù)就是igb_poll,如下代碼所示。

static int igb_alloc_q_vector(struct igb_adapter *adapter,
int v_count, int v_idx,
int txr_count, int txr_idx,
int rxr_count, int rxr_idx)
{
    ......
/* initialize NAPI */
    netif_napi_add(adapter- >netdev, &q_vector- >napi,
               igb_poll, 64);
}

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

當(dāng)上面的初始化都完成以后,就可以啟動(dòng)網(wǎng)卡了。回憶前面網(wǎng)卡驅(qū)動(dòng)初始化時(shí),我們提到了驅(qū)動(dòng)向內(nèi)核注冊(cè)了 structure net_device_ops 變量,它包含著網(wǎng)卡啟用、發(fā)包、設(shè)置mac 地址等回調(diào)函數(shù)(函數(shù)指針)。當(dāng)啟用一個(gè)網(wǎng)卡時(shí)(例如,通過 ifconfig eth0 up),net_device_ops 中的 igb_open方法會(huì)被調(diào)用。它通常會(huì)做以下事情:

圖片

//file: drivers/net/ethernet/intel/igb/igb_main.c
static int __igb_open(struct net_device *netdev, bool resuming)
{
/* allocate transmit descriptors */
    err = igb_setup_all_tx_resources(adapter);
/* allocate receive descriptors */
    err = igb_setup_all_rx_resources(adapter);
/* 注冊(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。在igb_setup_all_rx_resources這一步操作中,分配了RingBuffer,并建立內(nèi)存和Rx隊(duì)列的映射關(guān)系。(Rx Tx 隊(duì)列的數(shù)量和大小可以通過 ethtool 進(jìn)行配置)。我們?cè)俳又粗袛嗪瘮?shù)注冊(cè)igb_request_irq:

static int igb_request_irq(struct igb_adapter *adapter)
{
if (adapter- >msix_entries) {
        err = igb_request_msix(adapter);
if (!err)
goto request_done;
        ......
    }
}

static int igb_request_msix(struct igb_adapter *adapter)
{
    ......
for (i = 0; i < adapter- >num_q_vectors; i++) {
        ...
        err = request_irq(adapter- >msix_entries[vector].vector,
                  igb_msix_ring, 0, q_vector- >name,
    }

在上面的代碼中跟蹤函數(shù)調(diào)用, __igb_open => igb_request_irq => igb_request_msix, 在igb_request_msix中我們看到了,對(duì)于多隊(duì)列的網(wǎng)卡,為每一個(gè)隊(duì)列都注冊(cè)了中斷,其對(duì)應(yīng)的中斷處理函數(shù)是igb_msix_ring(該函數(shù)也在drivers/net/ethernet/intel/igb/igb_main.c下)。 我們也可以看到,msix方式下,每個(gè) RX 隊(duì)列有獨(dú)立的MSI-X 中斷,從網(wǎng)卡硬件中斷的層面就可以設(shè)置讓收到的包被不同的 CPU處理。(可以通過 irqbalance ,或者修改 /proc/irq/IRQ_NUMBER/smp_affinity能夠修改和CPU的綁定行為)。

到此準(zhǔn)備工作完成。

Linux網(wǎng)絡(luò)包:中斷到網(wǎng)絡(luò)層接收

網(wǎng)卡收包從整體上是網(wǎng)線中的高低電平轉(zhuǎn)換到網(wǎng)卡FIFO存儲(chǔ)再拷貝到系統(tǒng)主內(nèi)存(DDR3)的過程,其中涉及到網(wǎng)卡控制器,CPU,DMA,驅(qū)動(dòng)程序,在OSI模型中屬于物理層和鏈路層,如下圖所示。

圖片

圖片

l 中斷上半文

物理網(wǎng)卡收到數(shù)據(jù)包的處理流程如上圖左半部分所示,詳細(xì)步驟如下:

  1. 網(wǎng)卡收到數(shù)據(jù)包,先將高低電平轉(zhuǎn)換到網(wǎng)卡fifo存儲(chǔ),網(wǎng)卡申請(qǐng)ring buffer的描述,根據(jù)描述找到具體的物理地址,從fifo隊(duì)列物理網(wǎng)卡會(huì)使用DMA將數(shù)據(jù)包寫到了該物理地址,,其實(shí)就是skb_buffer中.
  2. 這個(gè)時(shí)候數(shù)據(jù)包已經(jīng)被轉(zhuǎn)移到skb_buffer中,因?yàn)槭荄MA寫入,內(nèi)核并沒有監(jiān)控?cái)?shù)據(jù)包寫入情況,這時(shí)候NIC觸發(fā)一個(gè)硬中斷,每一個(gè)硬件中斷會(huì)對(duì)應(yīng)一個(gè)中斷號(hào),且指定一個(gè)vCPU來處理,如上圖vcpu2收到了該硬件中斷.
  3. 硬件中斷的中斷處理程序,調(diào)用驅(qū)動(dòng)程序完成,a.啟動(dòng)軟中斷
  4. 硬中斷觸發(fā)的驅(qū)動(dòng)程序會(huì)禁用網(wǎng)卡硬中斷,其實(shí)這時(shí)候意思是告訴NIC,再來數(shù)據(jù)不用觸發(fā)硬中斷了,把數(shù)據(jù)DMA拷入系統(tǒng)內(nèi)存即可
  5. 硬中斷觸發(fā)的驅(qū)動(dòng)程序會(huì)啟動(dòng)軟中斷,啟用軟中斷目的是將數(shù)據(jù)包后續(xù)處理流程交給軟中斷慢慢處理,這個(gè)時(shí)候退出硬件中斷了,但是注意和網(wǎng)絡(luò)有關(guān)的硬中斷,要等到后續(xù)開啟硬中斷后,才有機(jī)會(huì)再次被觸發(fā)
  6. NAPI觸發(fā)軟中斷,觸發(fā)napi系統(tǒng)
  7. 消耗ringbuffer指向的skb_buffer
  8. NAPI循環(huán)處理ringbuffer數(shù)據(jù),處理完成
  9. 啟動(dòng)網(wǎng)絡(luò)硬件中斷,有數(shù)據(jù)來時(shí)候就可以繼續(xù)觸發(fā)硬件中斷,繼續(xù)通知CPU來消耗數(shù)據(jù)包.

其實(shí)上述過程過程簡(jiǎn)單描述為:網(wǎng)卡收到數(shù)據(jù)包,DMA到內(nèi)核內(nèi)存,中斷通知內(nèi)核數(shù)據(jù)有了,內(nèi)核按輪次處理消耗數(shù)據(jù)包,一輪處理完成后,開啟硬中斷。其核心就是網(wǎng)卡和內(nèi)核其實(shí)是生產(chǎn)和消費(fèi)模型,網(wǎng)卡生產(chǎn),內(nèi)核負(fù)責(zé)消費(fèi),生產(chǎn)者需要通知消費(fèi)者消費(fèi);如果生產(chǎn)過快會(huì)產(chǎn)生丟包,如果消費(fèi)過慢也會(huì)產(chǎn)生問題。也就說在高流量壓力情況下,只有生產(chǎn)消費(fèi)優(yōu)化后,消費(fèi)能力夠快,此生產(chǎn)消費(fèi)關(guān)系才可以正常維持,所以如果物理接口有丟包計(jì)數(shù)時(shí)候,未必是網(wǎng)卡存在問題,也可能是內(nèi)核消費(fèi)的太慢。

關(guān)于CPU與ksoftirqd的關(guān)系可以描述如下:

圖片

l 網(wǎng)卡收到的數(shù)據(jù)寫入到內(nèi)核內(nèi)存

NIC在接收到數(shù)據(jù)包之后,首先需要將數(shù)據(jù)同步到內(nèi)核中,這中間的橋梁是rx ring buffer。它是由NIC和驅(qū)動(dòng)程序共享的一片區(qū)域,事實(shí)上,rx ring buffer存儲(chǔ)的并不是實(shí)際的packet數(shù)據(jù),而是一個(gè)描述符,這個(gè)描述符指向了它真正的存儲(chǔ)地址,具體流程如下:

  1. 驅(qū)動(dòng)在內(nèi)存中分配一片緩沖區(qū)用來接收數(shù)據(jù)包,叫做sk_buffer;
  2. 將上述緩沖區(qū)的地址和大?。唇邮彰枋龇?,加入到rx ring buffer。描述符中的緩沖區(qū)地址是DMA使用的物理地址;
  3. 驅(qū)動(dòng)通知網(wǎng)卡有一個(gè)新的描述符;
  4. 網(wǎng)卡從rx ring buffer中取出描述符,從而獲知緩沖區(qū)的地址和大小;
  5. 網(wǎng)卡收到新的數(shù)據(jù)包;
  6. 網(wǎng)卡將新數(shù)據(jù)包通過DMA直接寫到sk_buffer中。

圖片

當(dāng)驅(qū)動(dòng)處理速度跟不上網(wǎng)卡收包速度時(shí),驅(qū)動(dòng)來不及分配緩沖區(qū),NIC接收到的數(shù)據(jù)包無(wú)法及時(shí)寫到sk_buffer,就會(huì)產(chǎn)生堆積,當(dāng)NIC內(nèi)部緩沖區(qū)寫滿后,就會(huì)丟棄部分?jǐn)?shù)據(jù),引起丟包。這部分丟包為rx_fifo_errors,在 /proc/net/dev中體現(xiàn)為fifo字段增長(zhǎng),在ifconfig中體現(xiàn)為overruns指標(biāo)增長(zhǎng)。

l 中斷下半文

ksoftirqd內(nèi)核線程處理軟中斷,即中斷下半部分軟中斷處理過程:

1.NAPI(以e1000網(wǎng)卡為例):net_rx_action() -> e1000_clean() -> e1000_clean_rx_irq() -> e1000_receive_skb() -> netif_receive_skb()

2.非NAPI(以dm9000網(wǎng)卡為例):net_rx_action() -> process_backlog() -> netif_receive_skb()

最后網(wǎng)卡驅(qū)動(dòng)通過netif_receive_skb()將sk_buff上送協(xié)議棧。

圖片

內(nèi)核線程初始化的時(shí)候,我們介紹了ksoftirqd中兩個(gè)線程函數(shù)ksoftirqd_should_run和run_ksoftirqd。其中ksoftirqd_should_run代碼如下:

圖片

#define local_softirq_pending() 

__IRQ_STAT(smp_processor_id(), __softirq_pending)

這里看到和硬中斷中調(diào)用了同一個(gè)函數(shù)local_softirq_pending。使用方式不同的是硬中斷位置是為了寫入標(biāo)記,這里僅僅只是讀取。如果硬中斷中設(shè)置了NET_RX_SOFTIRQ,這里自然能讀取的到。接下來會(huì)真正進(jìn)入線程函數(shù)中run_ksoftirqd處理:

static void run_ksoftirqd(unsigned int cpu)
{
    local_irq_disable();
if (local_softirq_pending()) {
        __do_softirq();
        rcu_note_context_switch(cpu);
        local_irq_enable();
        cond_resched();
return;
    }
    local_irq_enable();
}

在__do_softirq中,判斷根據(jù)當(dāng)前CPU的軟中斷類型,調(diào)用其注冊(cè)的action方法。

asmlinkage void __do_softirq(void)

圖片

在網(wǎng)絡(luò)子系統(tǒng)初始化小節(jié),我們看到我們?yōu)镹ET_RX_SOFTIRQ注冊(cè)了處理函數(shù)net_rx_action。所以net_rx_action函數(shù)就會(huì)被執(zhí)行到了。

這里需要注意一個(gè)細(xì)節(jié),硬中斷中設(shè)置軟中斷標(biāo)記,和ksoftirq的判斷是否有軟中斷到達(dá),都是基于smp_processor_id()的。這意味著只要硬中斷在哪個(gè)CPU上被響應(yīng),那么軟中斷也是在這個(gè)CPU上處理的。所以說,如果你發(fā)現(xiàn)你的Linux軟中斷CPU消耗都集中在一個(gè)核上的話,做法是要把調(diào)整硬中斷的CPU親和性,來將硬中斷打散到不通的CPU核上去。

我們?cè)賮戆丫械竭@個(gè)核心函數(shù)net_rx_action上來。

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;
    }
}

函數(shù)開頭的time_limit和budget是用來控制net_rx_action函數(shù)主動(dòng)退出的,目的是保證網(wǎng)絡(luò)包的接收不霸占CPU不放。 等下次網(wǎng)卡再有硬中斷過來的時(shí)候再處理剩下的接收數(shù)據(jù)包。其中budget可以通過內(nèi)核參數(shù)調(diào)整。 這個(gè)函數(shù)中剩下的核心邏輯是獲取到當(dāng)前CPU變量softnet_data,對(duì)其poll_list進(jìn)行遍歷, 然后執(zhí)行到網(wǎng)卡驅(qū)動(dòng)注冊(cè)到的poll函數(shù)。對(duì)于igb網(wǎng)卡來說,就是igb驅(qū)動(dòng)力的igb_poll函數(shù)了。

/**
 *  igb_poll - NAPI Rx polling callback
 *  @napi: napi polling structure
 *  @budget: count of how many packets we should handle
 **/
static int igb_poll(struct napi_struct *napi, int budget)
{
    ...
if (q_vector- >tx.ring)
        clean_complete = igb_clean_tx_irq(q_vector);
if (q_vector- >rx.ring)
        clean_complete &= igb_clean_rx_irq(q_vector, budget);
    ...
}

在讀取操作中,igb_poll的重點(diǎn)工作是對(duì)igb_clean_rx_irq的調(diào)用。

static bool igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget)
{
    ...
do {
/* retrieve a buffer from the ring */
        skb = igb_fetch_rx_buffer(rx_ring, rx_desc, skb);
/* fetch next buffer in frame if non-eop */
        if (igb_is_non_eop(rx_ring, rx_desc))
continue;
        }
/* verify the packet layout is correct */
        if (igb_cleanup_headers(rx_ring, rx_desc, skb)) {
            skb = NULL;
continue;
        }
/* populate checksum, timestamp, VLAN, and protocol */
        igb_process_skb_fields(rx_ring, rx_desc, skb);
        napi_gro_receive(&q_vector- >napi, skb);
}

igb_fetch_rx_buffer和igb_is_non_eop的作用就是把數(shù)據(jù)幀從RingBuffer上取下來。為什么需要兩個(gè)函數(shù)呢?因?yàn)橛锌赡軒级喽鄠€(gè)RingBuffer,所以是在一個(gè)循環(huán)中獲取的,直到幀尾部。獲取下來的一個(gè)數(shù)據(jù)幀用一個(gè)sk_buff來表示。收取完數(shù)據(jù)以后,對(duì)其進(jìn)行一些校驗(yàn),然后開始設(shè)置sbk變量的timestamp, VLAN id, protocol等字段。接下來進(jìn)入到napi_gro_receive中:

//file: net/core/dev.c
gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
{
    skb_gro_reset_offset(skb);
return napi_skb_finish(dev_gro_receive(napi, skb), skb);
}

dev_gro_receive這個(gè)函數(shù)代表的是網(wǎng)卡GRO特性,可以簡(jiǎn)單理解成把相關(guān)的小包合并成一個(gè)大包就行,目的是減少傳送給網(wǎng)絡(luò)棧的包數(shù),這有助于減少 CPU 的使用量。我們暫且忽略,直接看napi_skb_finish, 這個(gè)函數(shù)主要就是調(diào)用了netif_receive_skb。

//file: net/core/dev.c
static gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb)
{
switch (ret) {
case GRO_NORMAL:
if (netif_receive_skb(skb))
            ret = GRO_DROP;
break;
    ......
}

在netif_receive_skb中,數(shù)據(jù)包將被送到協(xié)議棧中,接下來在網(wǎng)絡(luò)層協(xié)議層的處理流程便不再贅述。

總結(jié)

l send發(fā)包過程

1、網(wǎng)卡驅(qū)動(dòng)創(chuàng)建tx descriptor ring(一致性DMA內(nèi)存),將tx descriptor ring的總線地址寫入網(wǎng)卡寄存器TDBA

2、協(xié)議棧通過dev_queue_xmit()將sk_buff下送網(wǎng)卡驅(qū)動(dòng)

3、網(wǎng)卡驅(qū)動(dòng)將sk_buff放入tx descriptor ring,更新TDT

4、DMA感知到TDT的改變后,找到tx descriptor ring中下一個(gè)將要使用的descriptor

5、DMA通過PCI總線將descriptor的數(shù)據(jù)緩存區(qū)復(fù)制到Tx FIFO

6、復(fù)制完后,通過MAC芯片將數(shù)據(jù)包發(fā)送出去

7、發(fā)送完后,網(wǎng)卡更新TDH,啟動(dòng)硬中斷通知CPU釋放數(shù)據(jù)緩存區(qū)中的數(shù)據(jù)包

l recv收包過程

1、網(wǎng)卡驅(qū)動(dòng)創(chuàng)建rx descriptor ring(一致性DMA內(nèi)存),將rx descriptor ring的總線地址寫入網(wǎng)卡寄存器RDBA

2、網(wǎng)卡驅(qū)動(dòng)為每個(gè)descriptor分配sk_buff和數(shù)據(jù)緩存區(qū),流式DMA映射數(shù)據(jù)緩存區(qū),將數(shù)據(jù)緩存區(qū)的總線地址保存到descriptor

3、網(wǎng)卡接收數(shù)據(jù)包,將數(shù)據(jù)包寫入Rx FIFO

4、DMA找到rx descriptor ring中下一個(gè)將要使用的descriptor

5、整個(gè)數(shù)據(jù)包寫入Rx FIFO后,DMA通過PCI總線將Rx FIFO中的數(shù)據(jù)包復(fù)制到descriptor的數(shù)據(jù)緩存區(qū)

6、復(fù)制完后,網(wǎng)卡啟動(dòng)硬中斷通知CPU數(shù)據(jù)緩存區(qū)中已經(jīng)有新的數(shù)據(jù)包了,CPU執(zhí)行硬中斷函數(shù):

NAPI(以e1000網(wǎng)卡為例):e1000_intr() -> __napi_schedule() -> __raise_softirq_irqoff(NET_RX_SOFTIRQ)

非NAPI(以dm9000網(wǎng)卡為例):dm9000_interrupt() -> dm9000_rx() -> netif_rx() -> napi_schedule() -> __napi_schedule() -> __raise_softirq_irqoff(NET_RX_SOFTIRQ)

7、ksoftirqd執(zhí)行軟中斷函數(shù)net_rx_action():

NAPI(以e1000網(wǎng)卡為例):net_rx_action() -> e1000_clean() -> e1000_clean_rx_irq() -> e1000_receive_skb() -> netif_receive_skb()

非NAPI(以dm9000網(wǎng)卡為例):net_rx_action() -> process_backlog() -> netif_receive_skb()

8、網(wǎng)卡驅(qū)動(dòng)通過netif_receive_skb()將sk_buff上送協(xié)議棧

圖片

Linux網(wǎng)絡(luò)子系統(tǒng)的分層

Linux網(wǎng)絡(luò)子系統(tǒng)實(shí)現(xiàn)需要:

  • 支持不同的協(xié)議族 ( INET, INET6, UNIX, NETLINK...)
  • 支持不同的網(wǎng)絡(luò)設(shè)備
  • 支持統(tǒng)一的BSD socket API

需要屏蔽協(xié)議、硬件、平臺(tái)(API)的差異,因而采用分層結(jié)構(gòu):

系統(tǒng)調(diào)用提供用戶的應(yīng)用程序訪問內(nèi)核的唯一途徑。協(xié)議無(wú)關(guān)接口由socket layer來實(shí)現(xiàn)的,其提供一組通用功能,以支持各種不同的協(xié)議。網(wǎng)絡(luò)協(xié)議層為socket層提供具體協(xié)議接口——proto{},實(shí)現(xiàn)具體的協(xié)議細(xì)節(jié)。設(shè)備無(wú)關(guān)接口,提供一組通用函數(shù)供底層網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序使用。設(shè)備驅(qū)動(dòng)與特定網(wǎng)卡設(shè)備相關(guān),定義了具體的協(xié)議細(xì)節(jié),會(huì)分配一個(gè)net_device結(jié)構(gòu),然后用其必需的例程進(jìn)行初始化。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 接口
    +關(guān)注

    關(guān)注

    33

    文章

    8355

    瀏覽量

    150517
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11161

    瀏覽量

    208466
  • 網(wǎng)絡(luò)設(shè)備

    關(guān)注

    0

    文章

    304

    瀏覽量

    29552
  • 傳輸層
    +關(guān)注

    關(guān)注

    0

    文章

    29

    瀏覽量

    10868
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Linux驅(qū)動(dòng)分析之input子系統(tǒng)

    Linux內(nèi)核為了能夠處理各種不同類型的輸入設(shè)備,比如: 觸摸屏 ,鼠標(biāo) , 鍵盤 , 操縱桿等設(shè)備 ,設(shè)計(jì)并實(shí)現(xiàn)Linux 輸入子系統(tǒng) ,它為驅(qū)動(dòng)和應(yīng)用提供了統(tǒng)一的接口函數(shù),方便
    發(fā)表于 02-01 10:38 ?502次閱讀

    Linux LED子系統(tǒng)詳解

    Linux LED子系統(tǒng)詳解
    的頭像 發(fā)表于 06-10 10:37 ?1456次閱讀
    <b class='flag-5'>Linux</b> LED<b class='flag-5'>子系統(tǒng)</b>詳解

    網(wǎng)絡(luò)子系統(tǒng)在鏈路層的收發(fā)過程剖析

    網(wǎng)絡(luò)子系統(tǒng)在鏈路層的收發(fā)過程剖析
    發(fā)表于 08-15 17:58

    嵌入式linux內(nèi)核的五個(gè)子系統(tǒng)

    嵌入式linux內(nèi)核的五個(gè)子系統(tǒng)分享到: Linux內(nèi)核主要由進(jìn)程調(diào)度(SCHED)、內(nèi)存管理(MM)、虛擬文件系統(tǒng)(VFS)、網(wǎng)絡(luò)接口(
    發(fā)表于 09-10 14:09

    基于USB設(shè)備的Linux網(wǎng)絡(luò)驅(qū)動(dòng)程序開發(fā)

    介紹Linux 的體系結(jié)構(gòu)及其網(wǎng)絡(luò)子系統(tǒng),并結(jié)合USB 設(shè)備在Linux 下的訪問機(jī)制,給出了一種USB 網(wǎng)絡(luò)驅(qū)動(dòng)程序的設(shè)計(jì)方法。該設(shè)計(jì)方法充分利用
    發(fā)表于 08-11 11:23 ?20次下載

    基于Linux內(nèi)核輸入子系統(tǒng)的驅(qū)動(dòng)研究

    Linux因其完全開放的特性和穩(wěn)定優(yōu)良的性能深受歡迎,當(dāng)推出了內(nèi)核輸入子系統(tǒng)后,更方便了嵌入式領(lǐng)域的驅(qū)動(dòng)開放。介紹了Linux的設(shè)備驅(qū)動(dòng)基礎(chǔ),詳細(xì)闡述了基于Linux內(nèi)核輸入
    發(fā)表于 09-12 16:38 ?23次下載

    Linux內(nèi)核輸入子系統(tǒng)的驅(qū)動(dòng)研究

    Linux內(nèi)核輸入子系統(tǒng)的驅(qū)動(dòng)研究
    發(fā)表于 10-31 14:41 ?14次下載
    <b class='flag-5'>Linux</b>內(nèi)核輸入<b class='flag-5'>子系統(tǒng)</b>的驅(qū)動(dòng)研究

    詳細(xì)了解Linux設(shè)備模型中的input子系統(tǒng)

    linux輸入子系統(tǒng)linux input subsystem)從上到下由三層實(shí)現(xiàn),分別為:輸入子系統(tǒng)事件處理層(EventHandler
    發(fā)表于 05-12 09:04 ?1018次閱讀
    詳細(xì)了解<b class='flag-5'>Linux</b>設(shè)備模型中的input<b class='flag-5'>子系統(tǒng)</b>

    驅(qū)動(dòng)之路-網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)基本原理和框架

    Linux網(wǎng)絡(luò)子系統(tǒng)的頂部是系統(tǒng)調(diào)用接口層。它為用戶空間提供的應(yīng)用程序提供了一種訪問內(nèi)核網(wǎng)絡(luò)子系統(tǒng)的方法(socket)。位于其下面是一個(gè)協(xié)議無(wú)關(guān)層,它提供一種通用的方法來使用傳輸層協(xié)
    發(fā)表于 05-15 16:57 ?1218次閱讀
    驅(qū)動(dòng)之路-<b class='flag-5'>網(wǎng)絡(luò)</b>設(shè)備驅(qū)動(dòng)基本原理和框架

    Linux網(wǎng)絡(luò)子系統(tǒng)的DMA機(jī)制是如何的實(shí)現(xiàn)

    先用“圖1”大體上說明幾種控制方式的區(qū)別,其中黃線代表程序輪詢方式,綠線代表中斷方式,紅線代表DMA方式,黑線代表RDMA方式,藍(lán)線代表公用的線。可以看出DMA方式與程序輪詢方式還有中斷方式的區(qū)別是傳輸數(shù)據(jù)跳過了CPU,直接和主存交流。
    的頭像 發(fā)表于 06-03 16:05 ?4343次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>網(wǎng)絡(luò)子系統(tǒng)</b>的DMA機(jī)制是如何的<b class='flag-5'>實(shí)現(xiàn)</b>的

    TensorRT條件用于實(shí)現(xiàn)網(wǎng)絡(luò)子圖的條件執(zhí)行

    IIfConditional實(shí)現(xiàn)了一個(gè) if-then-else 流控制結(jié)構(gòu),該結(jié)構(gòu)提供基于動(dòng)態(tài)布爾輸入的網(wǎng)絡(luò)子圖的條件執(zhí)行。它由一個(gè)布爾標(biāo)量predicate condition和兩個(gè)分支子圖定義
    的頭像 發(fā)表于 05-18 10:02 ?1083次閱讀

    Windows 子系統(tǒng)助力 Linux 2.0

    Windows 子系統(tǒng)助力 Linux 2.0
    的頭像 發(fā)表于 01-04 11:17 ?598次閱讀

    Linux系統(tǒng)中NFC子系統(tǒng)架構(gòu)分析

    目前在Linux系統(tǒng)中,每個(gè)廠家都使用不同的方式實(shí)現(xiàn)NFC驅(qū)動(dòng),然后自己在應(yīng)用層上面做適配。但是Linux也已經(jīng)推出NFC子系統(tǒng),很多廠家也
    發(fā)表于 01-04 14:01 ?1838次閱讀

    Linux reset子系統(tǒng)有什么功能

    Linux reset子系統(tǒng) reset子系統(tǒng)非常簡(jiǎn)單,與clock子系統(tǒng)非常類似,但在驅(qū)動(dòng)實(shí)現(xiàn)上,reset驅(qū)動(dòng)更簡(jiǎn)單。 因?yàn)閏lock驅(qū)
    的頭像 發(fā)表于 09-27 14:06 ?675次閱讀
    <b class='flag-5'>Linux</b> reset<b class='flag-5'>子系統(tǒng)</b>有什么功能

    Simplelink? Wi-Fi? CC3x3x網(wǎng)絡(luò)子系統(tǒng)電源管理

    電子發(fā)燒友網(wǎng)站提供《Simplelink? Wi-Fi? CC3x3x網(wǎng)絡(luò)子系統(tǒng)電源管理.pdf》資料免費(fèi)下載
    發(fā)表于 09-23 11:17 ?0次下載
    Simplelink? Wi-Fi? CC3x3x<b class='flag-5'>網(wǎng)絡(luò)子系統(tǒng)</b>電源管理