久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

【轉(zhuǎn)】Linux內(nèi)核網(wǎng)絡協(xié)議棧筆記4:接收網(wǎng)絡數(shù)據(jù)包詳細過程

 kylin_1983 2014-05-04

網(wǎng)絡數(shù)據(jù)接收過程,,從數(shù)據(jù)包到達網(wǎng)卡的物理接口開始,然后由網(wǎng)卡的驅(qū)動程序交給網(wǎng)絡協(xié)議棧,,最后經(jīng)過協(xié)議棧的一層層處理之后交給應用程序,。大致上是這樣的過程,但實際上有更多的細節(jié),。本文中主要介紹第一個和第二個步驟,。

我們本文中依然以一個Realtek 8139網(wǎng)卡為例(驅(qū)動程序為/drivers/net/8139too.c)。請注意在內(nèi)核代碼中receive都是用rx簡寫的,。

(1)注冊與激活軟中斷

在生成net_device對象及初始化的函數(shù)rtl8139_init_one中已經(jīng)初始化dev->open方法為rtl8139_open函數(shù)(在本系列文章2:初始化中的net_device對象中已經(jīng)介紹,,點這里查看)。在rtl8139_open函數(shù)(這個函數(shù)在網(wǎng)卡啟動時被調(diào)用)中注冊了一個中斷函數(shù)rtl8139_interrupt:

retval = request_irq (dev->irq, rtl8139_interrupt, SA_SHIRQ, dev->name, dev);

所以只要當網(wǎng)卡開啟后(狀態(tài)為up),,當網(wǎng)絡數(shù)據(jù)包到達時,,都會產(chǎn)生一個硬件中斷(這不同于后面的軟中斷)。這個硬件中斷由內(nèi)核調(diào)用中斷處理程序rtl8139_interrupt函數(shù)處理,。這個函數(shù)比較重要,,網(wǎng)卡發(fā)送或者接收數(shù)據(jù)時內(nèi)核都會調(diào)用這個函數(shù)處理中斷,而中斷的類型是根據(jù)網(wǎng)卡狀態(tài)寄存器的不同而確定的,。本文中僅涉及接收數(shù)據(jù)的中斷,,因此只給出了接收的代碼:

static irqreturn_t rtl8139_interrupt (int irq, void *dev_instance, struct pt_regs *regs)
{
    if (status & RxAckBits){
        if(netif_rx_schedule_prep(dev))
                 __netif_rx_schedule (dev);
     }
}

主要函數(shù)為__netif_rx_schedule(函數(shù)名意為:network interface receive schedule,即網(wǎng)絡接口接收調(diào)度),,因為當網(wǎng)卡接收到數(shù)據(jù)包之后,,馬上告知CPU在合適的時間去啟動調(diào)度程序,輪詢(poll)網(wǎng)卡,。

請注意:Linux接收網(wǎng)絡數(shù)據(jù)實際上有兩種方式,。
(a)中斷。每個數(shù)據(jù)包到達都會產(chǎn)生一個中斷,,然后由內(nèi)核調(diào)用中斷處理程序處理,。
(b)NAPI(New API)。Linux內(nèi)核2.6版本之后加入的新機制,,核心方法是:不采用中斷的方式讀取數(shù)據(jù),,而代之以首先采用中斷喚醒數(shù)據(jù)接收的服務程序,然后以POLL的方法來輪詢數(shù)據(jù),。
因此本文中只介紹NAPI的接收方式,。我們不再詳細介紹這種機制,網(wǎng)上可找到比較多的資料,可以參考IBM的技術(shù)文章:NAPI 技術(shù)在 Linux 網(wǎng)絡驅(qū)動上的應用和完善,。

__netif_rx_schedule函數(shù)的定義如下:

static inline void __netif_rx_schedule(struct net_device *dev)
{
         local_irq_save(flags);//disable interrupt
    //Add interface to tail of rx poll list
         list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
    //activate network rx softirq
         __raise_softirq_irqoff(NET_RX_SOFTIRQ);
         local_irq_restore(flags);
}

這個函數(shù)最核心的就是三步:

(a)local_irq_save:禁用中斷

(b)list_add_tail:將設(shè)備添加到softnet_data的poll_list中,。

(c)激活一個軟中斷NET_RX_SOFTIRQ。

======================================

說到這里我們必須介紹一個關(guān)鍵數(shù)據(jù)結(jié)構(gòu)softnet_data,,每個CPU都擁有一個這樣的網(wǎng)絡數(shù)據(jù)隊列(所以函數(shù)中使用了__get_cpu_var函數(shù)取得),定義如下:

struct softnet_data
{
    int             throttle;    /*為 1 表示當前隊列的數(shù)據(jù)包被禁止*/
    int             cng_level;    /*表示當前處理器的數(shù)據(jù)包處理擁塞程度*/
    int             avg_blog;    /*某個處理器的平均擁塞度*/
    struct sk_buff_head     input_pkt_queue;    /*接收緩沖區(qū)的sk_buff隊列*/
    struct list_head             poll_list;    /*POLL設(shè)備隊列頭*/
    struct net_device              output_queue;     /*網(wǎng)絡設(shè)備發(fā)送隊列的隊列頭*/
    struct sk_buff         completion_queue; /*完成發(fā)送的數(shù)據(jù)包等待釋放的隊列*/
    struct net_device     backlog_dev;    /*表示當前參與POLL處理的網(wǎng)絡設(shè)備*/
};

大致說明一下這個數(shù)據(jù)結(jié)構(gòu)的意義,。某個網(wǎng)卡產(chǎn)生中斷之后,,內(nèi)核就把這個網(wǎng)卡掛載到輪詢列表(poll_list)中。一個CPU會輪詢自己的列表中的每一個網(wǎng)卡,,看看它們是不是有新的數(shù)據(jù)包可以處理,。我們需要先用一個比喻說明這個數(shù)據(jù)結(jié)構(gòu)與輪詢的關(guān)系:網(wǎng)卡就是佃戶,CPU就是地主,。佃戶有自己種的糧食(網(wǎng)絡數(shù)據(jù)包),,但地主家也有糧倉(softnet_data)。地主要收糧的時候,,就會挨家挨戶的去催佃戶交糧,,放到自己的糧倉里。

=======================================

(2)軟中斷處理

我們知道:激活軟中斷之后,,并不是馬上會被處理的,。只有當遇到軟中斷的檢查點時,系統(tǒng)才會調(diào)用相應的軟中斷處理函數(shù),。

所有的網(wǎng)絡接收數(shù)據(jù)包的軟中斷處理函數(shù)都是net_rx_action,。這個函數(shù)的詳細注釋可以看IBM的那篇技術(shù)文章。其核心語句就是一個輪詢的函數(shù):

dev->poll

就調(diào)用了相應設(shè)備的poll函數(shù),。也就是說,,當CPU處理軟中斷時,才去輪詢網(wǎng)卡,,把數(shù)據(jù)放入softnet_data中,。

下面是整個中斷和輪詢過程的一個示意圖:

下面我們解釋一下poll函數(shù)具體干了什么事情。

而我們知道,,在Realtek 8139網(wǎng)卡的net_device對象中我們已經(jīng)注冊了一個poll函數(shù):

dev->poll = rtl8139_poll

那么一次poll就表示從網(wǎng)卡緩沖區(qū)取出一定量的數(shù)據(jù),。而rtl8139_poll函數(shù)中調(diào)用的主要函數(shù)就是rtl8139_rx函數(shù)。這個函數(shù)是完成從網(wǎng)卡取數(shù)據(jù),,分配skb緩沖區(qū)的核心函數(shù),。其核心代碼如下:

static int rtl8139_rx(struct net_device *dev, struct rtl8139_private *tp, int budget)
{
     skb = dev_alloc_skb (pkt_size + 2);
     eth_copy_and_sum (skb, &rx_ring[ring_offset + 4], pkt_size, 0);//memcpy
     skb->protocol =eth_type_trans (skb, dev);
     netif_receive_skb (skb);
}

工作主要分為4部分:

(a)給sk_buff數(shù)據(jù)結(jié)構(gòu)(skb)分配空間。

(b)從網(wǎng)卡的環(huán)形緩沖區(qū)rx_ring中拷貝出網(wǎng)絡數(shù)據(jù)包放到sk_buff對象skb中,。這個函數(shù)實質(zhì)上就是一個memcpy函數(shù),。

(c)在skb中標識其協(xié)議為以太網(wǎng)幀。

(d)調(diào)用netif_receice_skb函數(shù)。
netif_receive_skb函數(shù)相對比較重要,。函數(shù)主體是兩個循環(huán):

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);
         pt_prev = ptype;
         }
}

list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
    if (ptype->type == type && (!ptype->dev || ptype->dev == skb->dev))
     {
        if (pt_prev)
             ret = deliver_skb(skb, pt_prev);
         pt_prev =ptype;
     }
}

兩個循環(huán)分別遍歷了兩個鏈表:ptype_all和ptype_base,。前者是內(nèi)核中注冊的sniffer,后者則是注冊到內(nèi)核協(xié)議棧中的網(wǎng)絡協(xié)議類型,。如果skb中的協(xié)議類型type與ptype_base中的類型一致,,那么使用deliver_skb函數(shù)發(fā)送給這個協(xié)議一份,定義如下:

static __inline__ int deliver_skb(struct sk_buff *skb, struct packet_type *pt_prev)
{
         atomic_inc(&skb->users);
        return pt_prev->func(skb, skb->dev, pt_prev);
}

這個函數(shù)只是一個封裝函數(shù),,實際上調(diào)用了每個packet type結(jié)構(gòu)中注冊的處理函數(shù)func,。

struct packet_type {
         unsigned short           type;   
        struct net_device               *dev;   
        int                      (*func) (struct sk_buff *,
                          struct net_device *, struct packet_type *);
        void                    *af_packet_priv;
        structlist_head         list;
};

例如:IP包類型的處理函數(shù)就是ip_rcv(定義在/net/ipv4/ip_output.c文件中),定義如下:

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

這個包的類型是在ip_init協(xié)議初始化時添加到全局的ptype_base哈希數(shù)組中的:

void __init ip_init(void)
{
         dev_add_pack(&ip_packet_type);

    本站是提供個人知識管理的網(wǎng)絡存儲空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導購買等信息,,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請點擊一鍵舉報,。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多