Linux Kernel Net pkt processing procedure

原文

1. 网卡驱动

在内核中,一个PCI设备,使用struct pci_driver结构来描述。因为在系统引导的时候,PCI设备已经被识别,当内核发现一个已经检测到的设备同驱动注册的id_table中的信息相匹配时,它就会触发驱动的probe函数。
如ixgbe驱动

static struct pci_driver ixgb_driver = {
    .name     = ixgb_driver_name,
    .id_table = ixgb_pci_tbl,
    // 系统探测到ixgbe网卡后调用ixgbe_probe()
    .probe    = ixgb_probe,
    .remove   = ixgb_remove,
    .err_handler = &ixgb_err_handler
};

static int __init ixgbe_init_module(void)
{
    ...
    ret = pci_register_driver(&ixgbe_driver); // 注册ixgbe_driver
    ...
}

probe函数被调用,证明已经发现了我们所支持的网卡,这样,就可以调用register_netdev函数向内核注册网络设备了,注册之前,一般会调用alloc_etherdev(或者alloc_etherdev_mq)分配一个net_device,然后初始化它的重要成员。

/* ixgbe_probe   */
struct net_device *netdev;
struct pci_dev *pdev;
pci_enable_device_mem(pdev);
pci_request_mem_regions(pdev, ixgbe_driver_name);
pci_set_master(pdev);
pci_save_state(pdev);
// 这里分配struct net_device
netdev = alloc_etherdev_mq(sizeof(struct ixgbe_adapter), indices);
SET_NETDEV_DEV(netdev, &pdev->dev);
adapter = netdev_priv(netdev);
// alloc_etherdev_mq() -> ether_setup()
void ether_setup(struct net_device *dev)
{
    dev->header_ops     = &eth_header_ops;
    dev->type       = ARPHRD_ETHER;
    dev->hard_header_len    = ETH_HLEN;
    dev->min_header_len = ETH_HLEN;
    dev->mtu        = ETH_DATA_LEN;
    dev->addr_len       = ETH_ALEN;
    dev->tx_queue_len   = 1000; /* Ethernet wants good queues */
    dev->flags      = IFF_BROADCAST|IFF_MULTICAST;
    dev->priv_flags     |= IFF_TX_SKB_SHARING;

    eth_broadcast_addr(dev->broadcast);
}
EXPORT_SYMBOL(ether_setup);

2. 中断注册

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

内核初始化期间,softirq_init会注册TASKLET_SOFTIRQ以及HI_SOFTIRQ相关联的处理函数。

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

网络子系统分两种soft IRQ。NET_TX_SOFTIRQNET_RX_SOFTIRQ,分别处理发送数据包和接收数据包。这两个soft IRQ在net_dev_init函数(net/core/dev.c)中注册:

open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);

收发数据包的软中断处理函数被注册为net_tx_actionnet_rx_action
其中open_softirq实现为

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

3. 重要数据结构体初始化

每个cpu都有队列来处理接收到的帧,都有其数据结构来处理入口和出口流量。
此队列数据结构为softnet_data(定义在include/linux/netdevice.h中):

/*
 * Incoming packets are placed on per-cpu queues so that
 * no locking is needed.
 */
struct softnet_data
{
    struct Qdisc *output_queue; 

    //有数据要传输的设备列表
    struct sk_buff_head input_pkt_queue;
    //双向链表,其中的设备有输入帧等着被处理
    struct list_head poll_list; 
    //缓冲区列表,其中缓冲区已成功传输,可以释放掉
    struct sk_buff *completion_queue;

    struct napi_struct backlog;
}

softnet_data 是在start_kernel 中创建的, 并且每个cpu一个 softnet_data 变量。
这个变量中,最重要的是poll_list , 每当收到数据包时,网络设备驱动会把自己的napi_struct挂到CPU私有变量softnet_data->poll_list上,这样在软中断时,net_rx_action会遍历cpu私有变量的softnet_data->poll_list,执行上面所挂的napi_struct结构的poll钩子函数,将数据包从驱动传到网络协议栈。

4. 收发包过程

ixgbe_adapter包含ixgbe_q_vector数组,ixgbe_q_vector (一个ixgbe_q_vector对应一个中断) 包含napi_struct:

  • 硬中断函数把napi_struct加入CPU的poll_list,软中断函数net_rx_action()遍历poll_list,执行poll函数.

发包过程

  1. 网卡驱动创建tx descriptor ring(一致性DMA内存),将tx descriptor ring的地址写入网卡寄存器
  2. 协议栈通过dev_queue_xmit()将sk_buff下送网卡驱动
  3. 网卡驱动将sk_buff放入tx descriptor ring,更新queue tail pointer (TDT)
  4. DMA感知到TDT的改变后,找到tx descriptor ring中下一个将要使用的descriptor
  5. DMA通过PCI总线将descriptor的数据缓存区复制到Tx FIFO
  6. 复制完后,网卡将数据包发送出去
  7. 发送完后,启动一个硬中断通知driver释放数据缓存区中的数据包

收包过程

  1. (准备工作) 网卡驱动创建rx descriptor ring(一致性DMA内存),将rx descriptor ring的地址写入网卡寄存器
  2. (准备工作) 网卡驱动为每个descriptor分配sk_buff和数据缓存区
  3. 网卡接收数据包,将数据包写入Rx FIFO
  4. DMA找到rx descriptor ring中下一个将要使用的descriptor
  5. 整个数据包写入Rx FIFO后,DMA通过PCI总线将Rx FIFO中的数据包复制到descriptor的数据缓存区
  6. 复制完后,网卡启动硬中断通知CPU数据缓存区中已经有新的数据包了,CPU执行硬中断函数
    • NAPI(以e1000网卡为例):e1000_intr() -> __napi_schedule() -> __raise_softirq_irqoff(NET_RX_SOFTIRQ)
    • 非NAPI(以dm9000网卡为例):dm9000_interrupt() -> dm9000_rx() -> netif_rx() -> napi_schedule() -> __napi_schedule() -> __raise_softirq_irqoff(NET_RX_SOFTIRQ)
  7. 执行软中断函数net_rx_action():
    • NAPI(以e1000网卡为例):net_rx_action() -> e1000_clean() -> e1000_clean_rx_irq() -> e1000_receive_skb() -> netif_receive_skb()
    • 非NAPI(以dm9000网卡为例):net_rx_action() -> process_backlog() -> netif_receive_skb()
  8. 网卡驱动通过netif_receive_skb()将sk_buff上送协议栈
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容