小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

數(shù)據(jù)報(bào)的接收過程詳解---從網(wǎng)卡到L3層(非NAPI,即接收數(shù)據(jù)采用中斷方式) - 技術(shù)文...

 mrjbydd 2010-11-30
數(shù)據(jù)報(bào)的接收過程詳解---從網(wǎng)卡到L3層(非NAPI,即接收數(shù)據(jù)采用中斷方式)
來源: ChinaUnix博客  日期: 2008.12.17 13:07 (共有0條評(píng)論) 我要評(píng)論
 
剛來實(shí)驗(yàn)室的時(shí)候主要看的就是數(shù)據(jù)報(bào)在協(xié)議棧的具體傳輸過程,當(dāng)時(shí)有過記錄,但是很凌亂,最近又回頭看了看相關(guān)知識(shí)和內(nèi)核源代碼,算是理清了思路,特整理在此.本篇筆記寫的是2.4中數(shù)據(jù)報(bào)的接收過程,從網(wǎng)卡到網(wǎng)絡(luò)層的具體路線,2.4中大部分網(wǎng)卡采用的是中斷的方式接收數(shù)據(jù)(好像是從2.5以后開始支持NAPI的,不太確定),本篇筆記總結(jié)的是非NAPI,即采用中斷接受數(shù)據(jù)的路線.ok,開始進(jìn)入主題.

當(dāng)網(wǎng)卡接收到一個(gè)數(shù)據(jù)報(bào)之后,產(chǎn)生一個(gè)中斷通知內(nèi)核,然后內(nèi)核會(huì)調(diào)用相關(guān)的中斷處理函數(shù).一般,中斷處理程序做如下工作:
1,把數(shù)據(jù)報(bào)拷貝到一個(gè)sk_buff中.
2,初始化sk_buff中的一些成員變量,為以后傳輸?shù)缴蠈佑?特別是skb->protocol,標(biāo)示了上層的具體協(xié)議,后面會(huì)調(diào)用相應(yīng)的接收函數(shù).
3,更新網(wǎng)卡的狀態(tài).
4,調(diào)用netif_rx將數(shù)據(jù)報(bào)送往上層(采用NAPI時(shí),此處將調(diào)用netif_rx_schdule).

看netif_rx源代碼之前首先要了解一下softnet_data結(jié)構(gòu).此結(jié)構(gòu)是基于cpu的而不是device,即每個(gè)cpu對(duì)應(yīng)一個(gè)softnet_data.
struct softnet_data
{            
        /*throttle用于擁塞控制,當(dāng)擁塞時(shí)被設(shè)置,此后來的數(shù)據(jù)包都被丟棄*/
        int throttle;
        /*netif_rx返回的擁塞級(jí)別*/
        int cng_level;
        int avg_blog;
        /*input_pkt_queue是skb的隊(duì)列,接收到的skb全都進(jìn)入到此隊(duì)列等待后續(xù)處理*/
        struct sk_buff_head input_pkt_queue;
        /*poll_list是一個(gè)雙向鏈表,鏈表的成員是有接收數(shù)據(jù)等待處理的device*/
        struct list_head poll_list;
        /*net_device鏈表,成員為有數(shù)據(jù)報(bào)要發(fā)送的device*/
        struct net_device *output_queue;
        /*完成發(fā)送的數(shù)據(jù)包等待釋放的隊(duì)列*/
        struct sk_buff *completion_queue;
        /*注意,backlog_dev不是一個(gè)指針,而是一個(gè)net_device實(shí)體,代表了調(diào)用net_rx_action時(shí)的device*/
        struct net_device backlog_dev;
};

ok,了解了softnet_data結(jié)構(gòu)體后接下來看netif_rx的源代碼.

/*
*主要工作:
*1,初始化sk_buff的一些域值,比如數(shù)據(jù)報(bào)接收的時(shí)間截.
*2,把接收到的數(shù)據(jù)報(bào)入input_pkt_queue接收隊(duì)列,并且通知內(nèi)核然后觸發(fā)相應(yīng)的軟中斷,即NET_RX_SOFTIRQ.
* 2.1:當(dāng)隊(duì)列為空時(shí),調(diào)用netif_rx_schedule,觸發(fā)軟中斷.
* 2.2:當(dāng)隊(duì)列非空時(shí),直接將數(shù)據(jù)報(bào)入隊(duì)列,因?yàn)榇藭r(shí)已經(jīng)調(diào)用了軟中斷,所以無需再調(diào)用.
*3,更新相關(guān)狀態(tài)信息.
*執(zhí)行完后,流程來到net_rx_action
*/
int netif_rx(struct sk_buff *skb)
{
    int this_cpu = smp_processor_id();
    struct softnet_data *queue;
    unsigned long flags;
    //如果接收到的數(shù)據(jù)包時(shí)間截未設(shè)置,設(shè)置時(shí)間截
    if (skb->stamp.tv_sec == 0)
        do_gettimeofday(&skb->stamp);
    queue = &softnet_data[this_cpu];
    local_irq_save(flags); //disable irqs on local cpu
    netdev_rx_stat[this_cpu].total++;
    if (queue->input_pkt_queue.qlen = netdev_max_backlog) {
        if (queue->input_pkt_queue.qlen) {
            if (queue->throttle)
                goto drop;
enqueue:
            dev_hold(skb->dev); //即automic_inc(&(dev)->refcnt)累加設(shè)備引入計(jì)數(shù)器
            __skb_queue_tail(&queue->input_pkt_queue,skb); //把skb入input_pkt_queue隊(duì)列,此處只是指針的指向,而不是數(shù)據(jù)報(bào)的拷貝,目的是節(jié)省時(shí)間.
            local_irq_restore(flags); //eable irqs on local cpu
#ifndef OFFLINE_SAMPLE
            get_sample_stats(this_cpu);
#endif
            return queue->cng_level;//返回?fù)砣燃?jí)
        }
     //驅(qū)動(dòng)程序不斷的調(diào)用netif_rx,將數(shù)據(jù)包入隊(duì)操作,當(dāng)qlen==0時(shí),執(zhí)行下面代碼
     //如果設(shè)置了擁塞位,將其設(shè)為0
        if (queue->throttle) {
            queue->throttle = 0;
#ifdef CONFIG_NET_HW_FLOWCONTROL
            if (atomic_dec_and_test(&netdev_dropping))
                netdev_wakeup();
#endif
        }
        /*
         netif_rx_schedule主要完成兩件事
         1):將接收skb的device加入"處理數(shù)據(jù)包的設(shè)備"的鏈表當(dāng)中
         2):觸發(fā)軟中斷函數(shù),進(jìn)行數(shù)據(jù)包接收處理,接收軟中斷的處理函數(shù)為net_rx_action
        */
        netif_rx_schedule(&queue->backlog_dev); //只有當(dāng)input_pkt_queue為空時(shí)才調(diào)用,非空時(shí)只是將數(shù)據(jù)報(bào)入隊(duì)列,因?yàn)槿绻?duì)列非空,則已經(jīng)調(diào)用了NET_RX_SOFTIRQ軟中斷,所以沒必要在執(zhí)行netif_rx_schedule去調(diào)用軟中斷了.
        goto enqueue;
    }
    /*如果隊(duì)列無空閑空間,設(shè)置擁塞位*/
    if (queue->throttle == 0) {
        queue->throttle = 1;
        netdev_rx_stat[this_cpu].throttled++;
#ifdef CONFIG_NET_HW_FLOWCONTROL
        atomic_inc(&netdev_dropping);
#endif
    }
drop:
    netdev_rx_stat[this_cpu].dropped++;
    local_irq_restore(flags);
    kfree_skb(skb);
    return NET_RX_DROP;
}

看完netif_rx后有一個(gè)問題,當(dāng)隊(duì)列為空的時(shí)候會(huì)調(diào)用netif_rx_schedule,此函數(shù)將會(huì)把接收skb的device連接到poll_list鏈表,但如果隊(duì)列非空時(shí),為什么直接把數(shù)據(jù)放到input_pkt_queue里了?
想了想,應(yīng)該是因?yàn)檫@樣:因?yàn)椴捎肗API技術(shù)的網(wǎng)卡接收到數(shù)據(jù)報(bào)后不會(huì)調(diào)用netif_rx,而直接調(diào)用netif_rx_schedule,所以調(diào)用netif_rx的都是非NAPI的網(wǎng)卡,那么默認(rèn)的包處理函數(shù)都是process_backlog,也就是說,所有數(shù)據(jù)報(bào)的處理函數(shù)是一樣的,所以當(dāng)隊(duì)列非空時(shí),直接將接收到的skb放入接收隊(duì)列即可.
以上純粹是個(gè)人的理解,不知道對(duì)不對(duì),如果不對(duì),有知道的朋友希望告訴下,謝謝.

netif_rx返回后,由netif_rx_schedule調(diào)用軟中斷處理函數(shù),所以控制權(quán)轉(zhuǎn)交到net_rx_action.

/*
*接收軟中斷NET_RX_SOFTIRQ的處理函數(shù)
*當(dāng)調(diào)用poll_list中的device時(shí),如果網(wǎng)卡采用的是NAPI技術(shù),則調(diào)用網(wǎng)卡自定義的poll函數(shù),如果是非NAPI(目前大多數(shù)網(wǎng)卡都是采用此技術(shù)),則調(diào)用默認(rèn)的process_backlog函數(shù).
*由于此筆記研究的是非NAPI,所以控制權(quán)轉(zhuǎn)交到process_backlog函數(shù).
*/
static void net_rx_action(struct softirq_action *h)
{
    int this_cpu = smp_processor_id();
    struct softnet_data *queue = &softnet_data[this_cpu]; //獲得與cpu相關(guān)的softnet_data,因?yàn)槊總€(gè)cpu只有一個(gè)softnet_data
    unsigned long start_time = jiffies;
    int budget = netdev_max_backlog; //默認(rèn)值為300,系統(tǒng)每次從隊(duì)列中最多取出300個(gè)skb處理
    br_read_lock(BR_NETPROTO_LOCK);
    local_irq_disable();
    while (!list_empty(&queue->poll_list)) {
        struct net_device *dev;
        //當(dāng)處理時(shí)間持續(xù)超過一個(gè)時(shí)鐘滴答時(shí),會(huì)再出發(fā)一個(gè)中斷NET_RX_SOFTIRQ
        if (budget = 0 || jiffies - start_time > 1)
            goto softnet_break;
        local_irq_enable();
        //取得poll_list鏈表中的設(shè)備
        dev = list_entry(queue->poll_list.next, struct net_device, poll_list);
        //調(diào)用設(shè)備的poll函數(shù),處理接收數(shù)據(jù)包,采用輪尋技術(shù)的網(wǎng)卡,將調(diào)用它真實(shí)的poll函數(shù)
        //而對(duì)于采用傳統(tǒng)中斷處理的設(shè)備,它們調(diào)用的都將是backlog_dev的process_backlog函數(shù)
        //如果一次poll未處理完全部數(shù)據(jù)報(bào),則將device移至鏈表尾部,等待下一次調(diào)用.
        if (dev->quota = 0 || dev->poll(dev, &budget)) {
            //由于要對(duì)softnet_data進(jìn)行操作,則必須禁止中斷.
            local_irq_disable();
            //把device從表頭移除,移到鏈表尾
            list_del(&dev->poll_list);
            list_add_tail(&dev->poll_list, &queue->poll_list);
            if (dev->quota  0)
                dev->quota += dev->weight;
            else
                dev->quota = dev->weight;
        } else {
            dev_put(dev); //當(dāng)device中的數(shù)據(jù)報(bào)被處理完畢(網(wǎng)卡采用NAPI技術(shù)時(shí)),遞減device的引用計(jì)數(shù) dcreases the reference count .
            local_irq_disable();
        }
    }
    local_irq_enable();
    br_read_unlock(BR_NETPROTO_LOCK);
    return;
softnet_break:
    netdev_rx_stat[this_cpu].time_squeeze++;
    //如果此時(shí)又有中斷發(fā)生,出發(fā)NET_RX_SOFTIRQ中斷,再此調(diào)用net_rx_action函數(shù).
    __cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ);
    local_irq_enable();
    br_read_unlock(BR_NETPROTO_LOCK);
}

軟中斷處理函數(shù)的主體既是調(diào)用數(shù)據(jù)報(bào)的處理函數(shù),網(wǎng)卡采用NAPI技術(shù)的情況下,則調(diào)用device本身定義的poll函數(shù),如果是非NAPI,則調(diào)用的是默認(rèn)的process_backlog函數(shù),既然本筆記研究的是非NAPI的情況,那么下面來研究下process_backlog函數(shù).

/*
*把數(shù)據(jù)報(bào)從input_pkt_queue中去出來后,交由netif_receive_skb處理,netif_receive_skb為真正的包處理函數(shù).
*注意,無論是基于NAPI還是非NAPI,最后的包處理函數(shù)都是netif_receive_skb.
*/
static int process_backlog(struct net_device *blog_dev, int *budget)
{
    int work = 0;
   
    //quota為一次處理數(shù)據(jù)包的數(shù)量,blog_dev->quota的值由netif_rx_schedule初始化為全局變量weight_p的值,默認(rèn)值為64
    int quota = min(blog_dev->quota, *budget);
    int this_cpu = smp_processor_id();
    struct softnet_data *queue = &softnet_data[this_cpu];
    unsigned long start_time = jiffies;
  //循環(huán)取出skb,交由netif_receive_skb處理,直至隊(duì)列為空
    for (;;) {
        struct sk_buff *skb;
        struct net_device *dev;
        local_irq_disable();
        skb = __skb_dequeue(&queue->input_pkt_queue);
        if (skb == NULL)
            goto job_done;
        local_irq_enable();
        dev = skb->dev;
        //注意此處,取出數(shù)據(jù)報(bào)后,直接交由netif_receive_skb處理.
        netif_receive_skb(skb);
        dev_put(dev); //dev引用計(jì)數(shù)-1
        work++;
        if (work >= quota || jiffies - start_time > 1)
        break;
#ifdef CONFIG_NET_HW_FLOWCONTROL
        if (queue->throttle && queue->input_pkt_queue.qlen  no_cong_thresh ) {
            if (atomic_dec_and_test(&netdev_dropping)) {
                queue->throttle = 0;
                netdev_wakeup();
                break;
            }
        }
#endif
    }
    //更新quota
    blog_dev->quota -= work;
    *budget -= work;
    return -1;
job_done:
    blog_dev->quota -= work;
    *budget -= work;
    list_del(&blog_dev->poll_list);
    clear_bit(__LINK_STATE_RX_SCHED, &blog_dev->state);
    if (queue->throttle) {
        queue->throttle = 0;
#ifdef CONFIG_NET_HW_FLOWCONTROL
        if (atomic_dec_and_test(&netdev_dropping))
            netdev_wakeup();
#endif
    }
    local_irq_enable();
    return 0;
}
注意,就像注釋中所說,無論NAPI還是非NAPI,最后的包處理函數(shù)都是netif_receive_skb.所以此函數(shù)比較重要.下面來分析下此函數(shù).
/*
  netif_receive_skb作用:
    對(duì)每一個(gè)接收到的skb,到已注冊(cè)的協(xié)議類型中去匹配,先是匹配ptype_all鏈表,ptype_all中注冊(cè)的struct packet_type表示要接收處理所有協(xié)議的數(shù)據(jù),對(duì)于
    匹配到的struct packet_tpye結(jié)構(gòu)(dev為NULL或者dev等于skb的dev),調(diào)用其func成員,把skb傳遞給它處理.
    匹配完ptype_all后,再匹配ptype_base數(shù)組中注冊(cè)的協(xié)議類型,skb有一個(gè)成員protocol,其值即為以太網(wǎng)首部中的幀類型,在ptype_base中匹配到協(xié)議相同,并且
    dev符合要求的,調(diào)用其func成員即可.
    此函數(shù)中數(shù)據(jù)報(bào)的流向:sniffer(如果有)->Diverter(分流器)->bridge(如果有)->l3協(xié)議的處理函數(shù)(比如ip協(xié)議的ip_rcv)
*/
int netif_receive_skb(struct sk_buff *skb)
{
    //packet_type結(jié)構(gòu)體見下面
    struct packet_type *ptype, *pt_prev;
    int ret = NET_RX_DROP;
    unsigned short type = skb->protocol;
   
    //如果數(shù)據(jù)包沒設(shè)置時(shí)間截,設(shè)置之
    if (skb->stamp.tv_sec == 0)
        do_gettimeofday(&skb->stamp);
    skb_bond(skb); //使skb->dev指向主設(shè)備,多個(gè)interfaces可以在一起集中管理,這時(shí)候要有個(gè)頭頭管理這些接口,如果skb對(duì)應(yīng)的device來自這樣一個(gè)group,
     //則傳遞到L3層之前應(yīng)使skb->dev指向master
    netdev_rx_stat[smp_processor_id()].total++;
#ifdef CONFIG_NET_FASTROUTE
    if (skb->pkt_type == PACKET_FASTROUTE) {
        netdev_rx_stat[smp_processor_id()].fastroute_deferred_out++;
        return dev_queue_xmit(skb);
    }
#endif
    skb->h.raw = skb->nh.raw = skb->data;
    pt_prev = NULL;
   
    //ptype_all是雙向鏈表,ptpye_base是一個(gè)哈希表
    //初始化時(shí)協(xié)議類型為ETH_P_ALL時(shí),將packet_type結(jié)構(gòu)加入到ptype_all列表中   
    //如果不是,模15后加到ptype_base數(shù)組中,此數(shù)組相當(dāng)于hash鏈表
   
    //這里針對(duì)協(xié)議類型為ETH_P_ALL的情況進(jìn)行處理,對(duì)于ip協(xié)議來說
    //類型定義為ETH_P_IP,因此不在這里處理
   
    for (ptype = ptype_all; ptype; ptype = ptype->next) {
  //處理器處理所有的網(wǎng)絡(luò)設(shè)備接收的包或找到設(shè)備匹配的包處理器
      if (!ptype->dev || ptype->dev == skb->dev) {
            if (pt_prev) {
                //老版本內(nèi)核時(shí)
                if (!pt_prev->data) {
                 /* Deliver skb to an old protocol, which is not threaded well
                       or which do not understand shared skbs.
                */
                    ret = deliver_to_old_ones(pt_prev, skb, 0);
                } else {
                    //發(fā)給相應(yīng)的處理函數(shù),下面兩條相當(dāng)于deliver_skb
                    //有協(xié)議對(duì)skb處理,所以u(píng)se加1
                    atomic_inc(&skb->users);
                    //傳遞的是skb的指針,所以可以看出是原數(shù)據(jù)報(bào)本身.即如果在此修改skb,則會(huì)影響到后面的數(shù)據(jù)流向.
                    ret = pt_prev->func(skb, skb->dev, pt_prev);
                }
            }
            pt_prev = ptype;
        }
    }
#ifdef CONFIG_NET_DIVERT
  /*如果配置有DIVERT(分流器),則交由分流器處理*/
    if (skb->dev->divert && skb->dev->divert->divert)
        ret = handle_diverter(skb);
#endif /* CONFIG_NET_DIVERT */
    /*如果配置有BRIDGE或者有BRIDGE模塊,則交由橋處理*/        
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
    if (skb->dev->br_port != NULL &&
     br_handle_frame_hook != NULL) {
        return handle_bridge(skb, pt_prev);
    }
#endif
  //這里針對(duì)各種協(xié)議進(jìn)行處理,eg:ip包的類型為ETH_P_IP,因此在這里處理
    //&15的意思是模15
    for (ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) {
        if (ptype->type == type &&
         (!ptype->dev || ptype->dev == skb->dev)) {
            if (pt_prev) { //pt_prev指向具體的協(xié)議類型
                if (!pt_prev->data) {
                    ret = deliver_to_old_ones(pt_prev, skb, 0);
                } else {
                    atomic_inc(&skb->users);
                    ret = pt_prev->func(skb, skb->dev, pt_prev);
                }
            }
            pt_prev = ptype;
        }
    }
  //1:當(dāng)上面的兩個(gè)數(shù)組只有一個(gè)元素時(shí)
  //2:訪問上面兩個(gè)數(shù)組的最后一個(gè)ptype_type,執(zhí)行下面的語句
    if (pt_prev) {
        if (!pt_prev->data) {
            ret = deliver_to_old_ones(pt_prev, skb, 1);
        } else {
            //當(dāng)只有一個(gè)協(xié)議的時(shí)候,user不加1
            ret = pt_prev->func(skb, skb->dev, pt_prev);
        }
    } else { ///////表示搜索完ptype_all和ptype_base后沒找到匹配的,free掉skb
        kfree_skb(skb);
        /* Jamal, now you will not able to escape explaining
         * me how you were going to use this. :-)
         */
        ret = NET_RX_DROP;
    }
  return ret;
}
其中packet_type的定義如下:
struct packet_type
    {
        unsigned short type;
        //一般的dev設(shè)置為NULL,表示將接收從任何設(shè)備接收的數(shù)據(jù)包
        struct net_device *dev;
        int (*func)(struct sk_buff*,strcut net_device*,
                                                                struct packet_type *); //接收處理函數(shù)
        void *data;//private to the packet type
        struct packet_type *next;
    };
注意,在網(wǎng)絡(luò)初始化代碼里有這么一條語句:struct packet_type * ptype_all = NULL.就是說ptype_all在初始化的時(shí)候?yàn)榭?用于指向Eth_P_ALL類型的packet_type結(jié)構(gòu),注冊(cè)在這里的函數(shù),可以接收到所有的數(shù)據(jù)報(bào).包括輸出的數(shù)據(jù)包,看代碼可知,由于傳遞的是一個(gè)指針,所以在此修改skb的一切操作將會(huì)影響后面的處理過程.
如果想要在數(shù)據(jù)包傳遞到網(wǎng)絡(luò)層之前對(duì)數(shù)據(jù)報(bào)進(jìn)行處理,則可以考慮的地點(diǎn)可以有如下幾個(gè):
sniffer,diverter,bridge. 其中sniffer本人已經(jīng)親自實(shí)現(xiàn)過,即在ptype_all中注冊(cè)自己的函數(shù),可以接收到所有出去或者進(jìn)入的數(shù)據(jù)包.
最后,數(shù)據(jù)包會(huì)傳到l3層,如果是ip協(xié)議,則相應(yīng)的處理函數(shù)為ip_rcv,到此數(shù)據(jù)報(bào)從網(wǎng)卡到l3層的接收過程已經(jīng)完畢.即總的路線是:netif_rx-->net_rx_action-->process_backlog-->netif_receive_skb-->sniffer(如果有)-->diverter(如果有)-->bridge(如果有)-->ip_rcv(或者其他的l3層協(xié)議處理函數(shù))
ps1:弄了好幾天,終于算把這個(gè)數(shù)據(jù)包的接收過程系統(tǒng)的過了一遍,看過源代碼,感覺收獲不小,但是覺得知道的還不是很透徹,其中的設(shè)計(jì)理念等以后有機(jī)會(huì)一定要好好的研究研究.由于本次涉及到的東西相對(duì)來說比較多,而且涉及到內(nèi)核網(wǎng)絡(luò)部分,本人很菜,難免有理解錯(cuò)誤的地方,如果有錯(cuò)誤,請(qǐng)看到的朋友能夠及時(shí)給予指正,不勝感激.
ps2:從在網(wǎng)上查找資料,看相關(guān)書籍到通讀這一過程的源代碼,本人花費(fèi)了很多時(shí)間,因此對(duì)本篇筆記很是喜愛和重視,如果有需要轉(zhuǎn)載的朋友,請(qǐng)注明來自hmily8023.cublog.cn,謝謝.

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

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多