linux内核网络收包过程—硬中断与软中断
Posted 为了维护世界和平_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内核网络收包过程—硬中断与软中断相关的知识,希望对你有一定的参考价值。
目录
数据通过网络发送过来
硬中断处理
- 数据帧首先到达网卡的接收队列,分配RingBuffer
- DMA把数据搬运到网卡关联的内存
- 网卡向CPU发起硬中断,通知CPU有数据
- 调用驱动注册的硬中断处理函数
- 启动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;
igb_write_itr仅记录硬件中断频率
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将数据发送到网络协议栈。
参考
以上是关于linux内核网络收包过程—硬中断与软中断的主要内容,如果未能解决你的问题,请参考以下文章