深入理解linux内核网络收包过程—硬中断与软中断
硬中断处理
1. 数据帧首先到达网卡的接收队列,分配RingBuffer 2. DMA把数据搬运到网卡关联的内存 3. 网卡向CPU发起硬中断,通知CPU有数据 4. 调用驱动注册的硬中断处理函数 5. 启动NAPI,触发软中断
上一分析说到网卡硬中断注册的函数igb_msix_ring
static irqreturn_t igb_msix_ring(int irq, void *data)
{
struct igb_q_vector *q_vector = data;
/* Write the ITR value calculated from the previous interrupt. */
igb_write_itr(q_vector);
napi_schedule(&q_vector->napi);
return IRQ_HANDLED;
}
static inline void ____napi_schedule(struct softnet_data *sd,
struct napi_struct *napi)
{
list_add_tail(&napi->poll_list, &sd->poll_list);
//触发一个软中断NET_RX_SOFTIRQ
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}
list_add_tail修改了napi的poll_list(双向链表,数据帧等着被处理),
触发一个软中断NET_RX_SOFTIRQ
网络包硬中断的工作到此结束。
软中断处理
判断softirq_pending标志
static int ksoftirqd_should_run(unsigned int cpu)
{
return local_softirq_pending();
}
执行run_ksoftirqd->__do_softirq
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
while ((softirq_bit = ffs(pending))) {
trace_softirq_entry(vec_nr);
h->action(h);
trace_softirq_exit(vec_nr);
wakeup_softirqd();
}
}
调用action中断函数
static __latent_entropy void net_rx_action(struct softirq_action *h)
{
struct softnet_data *sd = this_cpu_ptr(&softnet_data);
unsigned long time_limit = jiffies +
usecs_to_jiffies(netdev_budget_usecs);
int budget = netdev_budget;
for (;;) {
struct napi_struct *n;
n = list_first_entry(&list, struct napi_struct, poll_list);
//变量sd,调用poll函数
budget -= napi_poll(n, &repoll);
//budget 与 time_limit控制退出
if (unlikely(budget <= 0 ||
time_after_eq(jiffies, time_limit))) {
sd->time_squeeze++;
break;
}
}
核⼼逻辑是获取到当前CPU变量 softnet_data,对其poll_list进⾏遍历,然后执⾏到⽹卡驱动注册到的poll函数。
static int igb_poll(struct napi_struct *napi, int budget)
{
if (q_vector->tx.ring)
clean_complete = igb_clean_tx_irq(q_vector, budget);
if (q_vector->rx.ring) {
int cleaned = igb_clean_rx_irq(q_vector, budget);
}
}
static int igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget)
{
while (likely(total_packets < budget)) {
union e1000_adv_rx_desc *rx_desc;
struct igb_rx_buffer *rx_buffer;
unsigned int size;
rx_buffer = igb_get_rx_buffer(rx_ring, size);
igb_put_rx_buffer(rx_ring, rx_buffer);
cleaned_count++;
/* fetch next buffer in frame if non-eop */
if (igb_is_non_eop(rx_ring, rx_desc))
continue;
/* verify the packet layout is correct */
if (igb_cleanup_headers(rx_ring, rx_desc, skb)) {
skb = NULL;
continue;
}
/* probably a little skewed due to removing CRC */
total_bytes += skb->len;
/* populate checksum, timestamp, VLAN, and protocol */
igb_process_skb_fields(rx_ring, rx_desc, skb);
napi_gro_receive(&q_vector->napi, skb);
/* update budget accounting */
total_packets++;
}
return total_packets;
}
从ringbuf中取出数据skb;
收取完数据以后,对其进⾏⼀些校验
设置 sbk 变量的 timestamp, VLAN id, protocol
gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
{
skb_gro_reset_offset(skb);
ret = napi_skb_finish(napi, skb, dev_gro_receive(napi, skb));
trace_napi_gro_receive_exit(ret);
}
napi_gro_receive函数代表的是⽹卡GRO特性,可以简单理解成把相关的⼩包合并成⼀个⼤包。
/* Pass the currently batched GRO_NORMAL SKBs up to the stack. */
static void gro_normal_list(struct napi_struct *napi)
{
if (!napi->rx_count)
return;
netif_receive_skb_list_internal(&napi->rx_list);
INIT_LIST_HEAD(&napi->rx_list);
napi->rx_count = 0;
}
/* Queue one GRO_NORMAL SKB up for list processing. If batch size exceeded,
* pass the whole batch up to the stack.
*/
static void gro_normal_one(struct napi_struct *napi, struct sk_buff *skb)
{
list_add_tail(&skb->list, &napi->rx_list);
if (++napi->rx_count >= gro_normal_batch)
gro_normal_list(napi);
}
static gro_result_t napi_skb_finish(struct napi_struct *napi,
struct sk_buff *skb,
gro_result_t ret)
{
switch (ret) {
case GRO_NORMAL:
gro_normal_one(napi, skb);
break;
...
}
最终调用 gro_normal_list将数据发送到网络协议栈。