- 論壇徽章:
- 0
|
R.wen (rwen2012@126.com)
由于太長(zhǎng), 這只是一部分內(nèi)容,完整的文檔在附件中。
有興趣的請(qǐng)看看并幫忙指正,謝謝。
1),Skb_buff
/* To allow 64K frame to be packed as single skb without frag_list */
#define MAX_SKB_FRAGS (65536/PAGE_SIZE + 2)
typedef struct skb_frag_struct skb_frag_t;
struct skb_frag_struct {
struct page *page;
__u16 page_offset;
__u16 size;
};
/* This data is invariant across clones and lives at
* the end of the header data, ie. at skb->end.
*/
struct skb_shared_info {
atomic_t dataref;
unsigned short nr_frags;
unsigned short gso_size;
/* Warning: this field is not always filled in (UFO)! */
unsigned short gso_segs;
unsigned short gso_type;
unsigned int ip6_frag_id;
struct sk_buff *frag_list;
skb_frag_t frags[MAX_SKB_FRAGS];
};
Skb比較復(fù)雜的部分在于skb_shared_info部分,alloc_skb()在為數(shù)據(jù)分配空間的時(shí)候,會(huì)在這個(gè)數(shù)據(jù)的末尾加上一個(gè)skb_shared_info結(jié)構(gòu),這個(gè)結(jié)構(gòu)就是用于scatter/gather IO的實(shí)現(xiàn)的。它主要用于提高性能,避免數(shù)據(jù)的多次拷貝。例如,當(dāng)用戶用sendmsg分送一個(gè)數(shù)組結(jié)構(gòu)的數(shù)據(jù)時(shí),這些數(shù)據(jù)在物理可能是不連續(xù)的(大多數(shù)情況),在不支持scatter/gather IO的網(wǎng)卡上,它只能通過(guò)重新拷貝,將它重裝成連續(xù)的skb(skb_linearize),才可以進(jìn)行DMA操作。而在支持S/G IO上,它就省去了這次拷貝。
![]()
4),數(shù)據(jù)包的接收
* Incoming packets are placed on per-cpu queues so that
* no locking is needed.
*/
struct softnet_data
{
struct net_device *output_queue;
struct sk_buff_head input_pkt_queue;
struct list_head poll_list;
struct sk_buff *completion_queue;
struct net_device backlog_dev; /* Sorry. */
#ifdef CONFIG_NET_DMA
struct dma_chan *net_dma;
#endif
};
這個(gè)數(shù)據(jù)結(jié)構(gòu)同時(shí)用于接收與發(fā)送數(shù)據(jù)包,它為per_CPU結(jié)構(gòu),這樣每個(gè)CPU有自己獨(dú)立的信息,這樣在SMP之間就避免了加鎖操作,從而大大提高了信息處理的并行性。
struct net_device *output_queue;
struct sk_buff *completion_queue;
這兩個(gè)域用于發(fā)送數(shù)據(jù),將在下一節(jié)中描述。
struct sk_buff_head input_pkt_queue;
struct list_head poll_list;
struct net_device backlog_dev;
這三個(gè)域用于接收數(shù)據(jù),其中input_pkt_queue與backlog_dev僅用于non-NAPI的NIC,input_pkt_queue是接收到的數(shù)據(jù)隊(duì)列頭,它用于netif_rx()中,并最終由虛擬的poll函數(shù)process_backlog()處理這個(gè)SKB隊(duì)列。
poll_list則是有數(shù)據(jù)包等待處理的NIC設(shè)備隊(duì)列。對(duì)于non-NAPI驅(qū)動(dòng)來(lái)說(shuō),它始終是backlog_dev。
接收過(guò)程:
當(dāng)一個(gè)數(shù)據(jù)包到來(lái)時(shí),NIC會(huì)產(chǎn)生一個(gè)中斷,這時(shí),它會(huì)執(zhí)行中斷處理全程。
(1), NON-NAPI方式:
如3c59x中的vortex_interrupt(),它會(huì)判斷寄存器的值作出相應(yīng)的動(dòng)作:
if (status & RxComplete)
vortex_rx(dev);
如上,當(dāng)中斷指示,有數(shù)據(jù)包在等待接收,這時(shí),中斷例程會(huì)調(diào)用接收函數(shù)vortex_rx(dev)接收新到來(lái)的包(如下,只保留核心部分):
int pkt_len = rx_status & 0x1fff;
struct sk_buff *skb;
skb = dev_alloc_skb(pkt_len + 5);
if (skb != NULL) {
skb->dev = dev;
skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */
/* 'skb_put()' points to the start of sk_buff data area. */
if (vp->bus_master &&
! (ioread16(ioaddr + Wn7_MasterStatus) & 0x8000)) {
dma_addr_t dma = pci_map_single(VORTEX_PCI(vp), skb_put(skb, pkt_len),
pkt_len, PCI_DMA_FROMDEVICE);
iowrite32(dma, ioaddr + Wn7_MasterAddr);
iowrite16((skb->len + 3) & ~3, ioaddr + Wn7_MasterLen);
iowrite16(StartDMAUp, ioaddr + EL3_CMD);
while (ioread16(ioaddr + Wn7_MasterStatus) & 0x8000)
;
pci_unmap_single(VORTEX_PCI(vp), dma, pkt_len, PCI_DMA_FROMDEVICE);
}
iowrite16(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);
它首先為新到來(lái)的數(shù)據(jù)包分配一個(gè)skb結(jié)構(gòu)及pkt_len+5大小的數(shù)據(jù)長(zhǎng)度,然后便將接收到的數(shù)據(jù)從網(wǎng)卡復(fù)制到(DMA)這個(gè)SKB的數(shù)據(jù)部分中。最后,調(diào)用netif_rx(skb)進(jìn)一步處理數(shù)據(jù):
int netif_rx(struct sk_buff *skb)
{
struct softnet_data *queue;
unsigned long flags;
/*
* The code is rearranged so that the path is the most
* short when CPU is congested, but is still operating.
*/
local_irq_save(flags);
queue = &__get_cpu_var(softnet_data);
if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
if (queue->input_pkt_queue.qlen) {
enqueue:
dev_hold(skb->dev);
__skb_queue_tail(&queue->input_pkt_queue, skb);
local_irq_restore(flags);
return NET_RX_SUCCESS;
}
netif_rx_schedule(&queue->backlog_dev);
goto enqueue;
}
}
這段代碼關(guān)鍵是,將這個(gè)SKB加入到相應(yīng)的input_pkt_queue隊(duì)列中,并調(diào)用netif_rx_schedule(),而對(duì)于NAPI方式,它沒(méi)有使用input_pkt_queue隊(duì)列,而是使用私有的隊(duì)列,所以它沒(méi)有這一個(gè)步驟。至此,中斷的上半部已經(jīng)完成,以下的工作則交由中斷的下半部來(lái)實(shí)現(xiàn)。
void __netif_rx_schedule(struct net_device *dev)
{
unsigned long flags;
local_irq_save(flags);
dev_hold(dev);
list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
if (dev->quota < 0)
dev->quota += dev->weight;
else
dev->quota = dev->weight;
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
local_irq_restore(flags);
}
netif_rx_schedule()就是將有等待接收數(shù)據(jù)包的NIC鏈入softnet_data的poll_list隊(duì)列,然后觸發(fā)軟中斷,讓后半部去完成數(shù)據(jù)的處理工作。
注意:這里是否調(diào)用netif_rx_schedule()是有條件的,即當(dāng)queue->input_pkt_queue.qlen==0時(shí)才會(huì)調(diào)用,否則由于這個(gè)隊(duì)列的長(zhǎng)度不為0,這個(gè)中斷下半部的執(zhí)行已由先前的中斷觸發(fā),它會(huì)斷續(xù)處理余下來(lái)的數(shù)據(jù)包的接收,所以,這里就不必要再次觸發(fā)它的執(zhí)行了。
總之,NON-NAPI的中斷上半部接收過(guò)程可以簡(jiǎn)單的描述為,它首先為新到來(lái)的數(shù)據(jù)幀分配合適長(zhǎng)度的SKB,再將接收到的數(shù)據(jù)從NIC中拷貝過(guò)來(lái),然后將這個(gè)SKB鏈入當(dāng)前CPU的softnet_data中的鏈表中,最后進(jìn)一步觸發(fā)中斷下半部發(fā)繼續(xù)處理。
(2), NAPI方式:
static irqreturn_t e100_intr(int irq, void *dev_id)
{
if(likely(netif_rx_schedule_prep(netdev))) {
e100_disable_irq(nic);
__netif_rx_schedule(netdev);
}
return IRQ_HANDLED;
}
可以看到,兩種方式的不同之處在于,NAPI方式直接調(diào)用__netif_rx_schedule(),而非NAPI方式則要通過(guò)輔助函數(shù)netif_rx()設(shè)置好接收隊(duì)列再調(diào)用netif_rx_schedule(),再者,在非NAPI方式中,提交的是netif_rx_schedule(&queue->backlog_dev),而NAPI中,提交的是__netif_rx_schedule(netdev),即是設(shè)備驅(qū)動(dòng)的net_device結(jié)構(gòu),而不是queue中的backlog_dev。
(3),net_rx_action()
netif_rx_schedule()觸發(fā)中斷下半部的執(zhí)行,這個(gè)下半部將執(zhí)行net_rx_action():
static void net_rx_action(struct softirq_action *h)
{
struct softnet_data *queue = &__get_cpu_var(softnet_data);
unsigned long start_time = jiffies;
local_irq_disable();
while (!list_empty(&queue->poll_list)) {
struct net_device *dev;
local_irq_enable();
dev = list_entry(queue->poll_list.next,
struct net_device, poll_list);
if (dev->quota <= 0 || dev->poll(dev, &budget)) {
… //出錯(cuò)處理
} else {
netpoll_poll_unlock(have);
dev_put(dev);
local_irq_disable();
}
}
由上可以看到,下半部的主要工作是遍歷有數(shù)據(jù)幀等待接收的設(shè)備鏈表,對(duì)于每個(gè)設(shè)備,執(zhí)行它相應(yīng)的poll函數(shù)。
(4),poll函數(shù)
NON—NAPI方式:
這種方式對(duì)應(yīng)該的poll函數(shù)為process_backlog:
struct softnet_data *queue = &__get_cpu_var(softnet_data);
for (; {
local_irq_disable();
skb = __skb_dequeue(&queue->input_pkt_queue);
local_irq_enable();
netif_receive_skb(skb);
}
它首先找到當(dāng)前CPU的softnet_data結(jié)構(gòu),然后遍歷其數(shù)據(jù)隊(duì)SKB,并將數(shù)據(jù)上交netif_receive_skb(skb)處理。
NAPI方式:
這種方式下,NIC驅(qū)動(dòng)程序會(huì)提供自己的poll函數(shù)和私有接收隊(duì)列。
如intel 8255x系列網(wǎng)卡程序e100,它有在初始化的時(shí)候首先分配一個(gè)接收隊(duì)列,而不像以上那種方式在接收到數(shù)據(jù)幀的時(shí)候再為其分配數(shù)據(jù)空間。這樣,NAPI的poll函數(shù)在處理接收的時(shí)候,它遍歷的是自己的私有隊(duì)列:
static int e100_poll(struct net_device *netdev, int *budget)
{
e100_rx_clean(nic, &work_done, work_to_do);
……
}
static void e100_rx_clean(struct nic *nic, unsigned int *work_done,
unsigned int work_to_do)
{
…….
/* Indicate newly arrived packets */
for(rx = nic->rx_to_clean; rx->skb; rx = nic->rx_to_clean = rx->next) {
int err = e100_rx_indicate(nic, rx, work_done, work_to_do);
if(-EAGAIN == err) {
……
}
……
}
static int e100_rx_indicate(struct nic *nic, struct rx *rx,
unsigned int *work_done, unsigned int work_to_do)
{
struct sk_buff *skb = rx->skb;
struct rfd *rfd = (struct rfd *)skb->data;
rfd_status = le16_to_cpu(rfd->status);
/* Get actual data size */
actual_size = le16_to_cpu(rfd->actual_size) & 0x3FFF;
/* Pull off the RFD and put the actual data (minus eth hdr) */
skb_reserve(skb, sizeof(struct rfd));
skb_put(skb, actual_size);
skb->protocol = eth_type_trans(skb, nic->netdev);
netif_receive_skb(skb);
return 0;
}
主要工作在e100_rx_indicate()中完成,這主要重設(shè)SKB的一些參數(shù),然后跟process_backlog(),一樣,最終調(diào)用netif_receive_skb(skb)。
07.01.30
[ 本帖最后由 rwen2012 于 2007-1-30 19:37 編輯 ] |
|