linux网络收包过程
記錄一下linux數(shù)據(jù)包從網(wǎng)卡進(jìn)入?yún)f(xié)議棧的過(guò)程,不涉及驅(qū)動(dòng),不涉及其他層的協(xié)議處理。
內(nèi)核是如何知道網(wǎng)卡收到數(shù)據(jù)的,這就涉及到網(wǎng)卡和內(nèi)核的交互方式:
輪詢(poll):內(nèi)核周期性的檢查網(wǎng)卡,查看是否收到數(shù)據(jù)。優(yōu)點(diǎn):數(shù)據(jù)包非常多的時(shí)候,這種處理方法會(huì)非常快速有效。缺點(diǎn):數(shù)據(jù)包少的時(shí)候會(huì)CPU總是輪詢卻沒(méi)有收到數(shù)據(jù)包,造成CPU資源的浪費(fèi)。這種方法很少使用。
中斷(interrupt):網(wǎng)卡收到數(shù)據(jù)就給內(nèi)核發(fā)送硬件中斷打斷內(nèi)核的正常運(yùn)行,讓內(nèi)核來(lái)處理數(shù)據(jù)包。優(yōu)點(diǎn):在數(shù)據(jù)包少的時(shí)候CPU能及時(shí)中斷其他任務(wù)來(lái)處理數(shù)據(jù)包,比較高效。缺點(diǎn):數(shù)據(jù)包多的時(shí)候每個(gè)數(shù)據(jù)包都引發(fā)一次中斷,造成CPU頻繁地在收包過(guò)程和其他過(guò)程之間切換,浪費(fèi)時(shí)間。在極端情況下收包中斷可能會(huì)一直搶占CPU造成軟中斷無(wú)法運(yùn)行,收包隊(duì)列得不到處理,進(jìn)而造成大量丟包。這就是所謂的receive-livelock。
Llinux早期是采用中斷的方式處理數(shù)據(jù)包的,之后引入了另一種方式NAPI,NAPI結(jié)合了輪詢和中斷的優(yōu)點(diǎn),在數(shù)據(jù)包少的時(shí)候采用中斷方式,數(shù)據(jù)包多的時(shí)候采用輪詢的方式,從而在兩種極端情況下也會(huì)有比較好的表現(xiàn)。
在NAPI下收包的過(guò)程
先看一個(gè)比較關(guān)鍵的結(jié)構(gòu)softnet_data,每個(gè)邏輯CPU都有一個(gè)softnet_data結(jié)構(gòu),這個(gè)結(jié)構(gòu)的poll_list是非常重要的。
struct softnet_data {struct net_device *output_queue;//收?qǐng)?bào)隊(duì)列,這個(gè)隊(duì)列是給傳統(tǒng)收?qǐng)?bào)方法兼容新收?qǐng)?bào)架構(gòu)用的(backlog_dev),用來(lái)模擬NAPI的struct sk_buff_head input_pkt_queue;//用于收包的net_devicestruct list_head poll_list;struct sk_buff *completion_queue;//backlog_dev是一個(gè)偽造的net_device用來(lái)來(lái)處理input_pkt_queue里的數(shù)據(jù)struct net_device backlog_dev; /* Sorry. 8) */ };收包過(guò)程可以分成兩步:
第一步的中斷的代碼可以參考drivers/net/tg3.c文件的tg3_interrupt函數(shù),中斷發(fā)生的時(shí)候它會(huì)調(diào)用netif_rx_schedule把當(dāng)前網(wǎng)卡的net_device插入當(dāng)前CPU的softnet_data的poll_list鏈表。netif_rx_schedule函數(shù)又調(diào)度了軟中斷NET_RX_SOFTIRQ。大致結(jié)構(gòu)就是下圖這樣子
第二步軟中斷在合適的時(shí)機(jī)得以執(zhí)行,看一下他的執(zhí)行過(guò)程:
static void net_rx_action(struct softirq_action *h) {struct softnet_data *queue = &__get_cpu_var(softnet_data);unsigned long start_time = jiffies;//預(yù)算,所有網(wǎng)卡的總配額int budget = netdev_budget;void *have;local_irq_disable();while (!list_empty(&queue->poll_list)) {struct net_device *dev;//預(yù)算用完了,或者時(shí)間太長(zhǎng)了,跳出等下一輪處理if (budget <= 0 || jiffies - start_time > 1)goto softnet_break;local_irq_enable();dev = list_entry(queue->poll_list.next,struct net_device, poll_list);have = netpoll_poll_lock(dev);if (dev->quota <= 0 || dev->poll(dev, &budget)) {netpoll_poll_unlock(have);local_irq_disable();//沒(méi)處理完,放到隊(duì)尾準(zhǔn)備下次處理,注意是list_move_tai,不是//list_insert_taillist_move_tail(&dev->poll_list, &queue->poll_list);if (dev->quota < 0)dev->quota += dev->weight;elsedev->quota = dev->weight;} else {netpoll_poll_unlock(have);dev_put(dev);local_irq_disable();}}//省略部分代碼 }軟中斷不能長(zhǎng)時(shí)間占用CPU,否則會(huì)造成用戶態(tài)進(jìn)程長(zhǎng)時(shí)間得不到調(diào)度,net_rx_action也一樣。所以net_rx_action函數(shù)每次執(zhí)行最多會(huì)處理budget個(gè)數(shù)據(jù)包(所有網(wǎng)卡都算),同時(shí)這budget個(gè)數(shù)據(jù)包也需要平均分配,不能只處理一個(gè)網(wǎng)卡造成其他網(wǎng)卡得不到處理,net_device的weight和quota是用來(lái)處理這個(gè)問(wèn)題的。這個(gè)代碼的大概意思是每次從poll_list里取出一個(gè)網(wǎng)卡,調(diào)用該網(wǎng)卡的poll函數(shù)盡可能多的收包(但是不會(huì)超過(guò)weight),poll函數(shù)收包后調(diào)用netif_receive_skb把數(shù)據(jù)包放入?yún)f(xié)議棧。如果網(wǎng)卡里的數(shù)據(jù)包沒(méi)處理完就會(huì)把net_device繼續(xù)放到poll_list鏈表等待下一次軟中斷繼續(xù)處理,如果網(wǎng)卡里的數(shù)據(jù)包處理完了就把該net_device從poll_list摘除。
傳統(tǒng)中斷收包方式
linux網(wǎng)卡驅(qū)動(dòng)還有部分是用的傳統(tǒng)中斷收包方式,為了兼容也都挪到了NAPI架構(gòu)上。用softnet_data結(jié)構(gòu)的backlog_dev偽造了一個(gè)net_device。中斷發(fā)生的時(shí)候把數(shù)據(jù)包放到了softnet_data結(jié)構(gòu)的input_pkt_queue鏈表里。
static int __init net_dev_init(void) { //省略部分代碼 for_each_possible_cpu(i) {struct softnet_data *queue;queue = &per_cpu(softnet_data, i);skb_queue_head_init(&queue->input_pkt_queue);queue->completion_queue = NULL;INIT_LIST_HEAD(&queue->poll_list);set_bit(__LINK_STATE_START, &queue->backlog_dev.state);queue->backlog_dev.weight = weight_p;queue->backlog_dev.poll = process_backlog;atomic_set(&queue->backlog_dev.refcnt, 1);} }軟中斷的處理過(guò)程和NAPI類似。
轉(zhuǎn)載于:https://www.cnblogs.com/4a8a08f09d37b73795649038408b5f33/p/11475249.html
總結(jié)
以上是生活随笔為你收集整理的linux网络收包过程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java 多线程-生产者、消费者
- 下一篇: Linux 查找目录下大于*M的文件