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 = ð_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_SOFTIRQ
和NET_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_action
和net_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函数.
发包过程
- 网卡驱动创建tx descriptor ring(一致性DMA内存),将tx descriptor ring的地址写入网卡寄存器
- 协议栈通过dev_queue_xmit()将sk_buff下送网卡驱动
- 网卡驱动将sk_buff放入tx descriptor ring,更新queue tail pointer (TDT)
- DMA感知到TDT的改变后,找到tx descriptor ring中下一个将要使用的descriptor
- DMA通过PCI总线将descriptor的数据缓存区复制到Tx FIFO
- 复制完后,网卡将数据包发送出去
- 发送完后,启动一个硬中断通知driver释放数据缓存区中的数据包
收包过程
- (准备工作) 网卡驱动创建rx descriptor ring(一致性DMA内存),将rx descriptor ring的地址写入网卡寄存器
- (准备工作) 网卡驱动为每个descriptor分配sk_buff和数据缓存区
- 网卡接收数据包,将数据包写入Rx FIFO
- DMA找到rx descriptor ring中下一个将要使用的descriptor
- 整个数据包写入Rx FIFO后,DMA通过PCI总线将Rx FIFO中的数据包复制到descriptor的数据缓存区
- 复制完后,网卡启动硬中断通知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)
- 执行软中断函数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()
- 网卡驱动通过netif_receive_skb()将sk_buff上送协议栈
网友评论