- 中断接收
DM9000网卡注册的中断函数为dm9000_interrupt(),该中断函数调用dm9000_rx()从网卡的buffer中读取接收到的数据,将数据接收到skb中之后,调用netif_rx()处理该数据包,最后的结果是将数据包传递给上层处理。
我们看看netif_rx()函数代码主要部分:
int netif_rx(struct sk_buff *skb)
{
int ret;
......
#ifdef CONFIG_RPS
......
#else
{
unsigned int qtail;
ret = enqueue_to_backlog(skb, get_cpu(), &qtail);
put_cpu();
}
#endif
return ret;
}
在enqueue_to_backlog()中,通过__skb_queue_tail(&sd->input_pkt_queue, skb)将skb接入per cpu 变量softnet_data的链中。接着在enqueue_to_backlog()中调用____napi_schedule()。
if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
if (!rps_ipi_queued(sd)) {
printk(KERN_INFO "skedu napi\r\n");
____napi_schedule(sd, &sd->backlog);
}
}
我们进入____napi_schedule()函数看看做了什么
static inline void ____napi_schedule(struct softnet_data *sd,
struct napi_struct *napi)
{
list_add_tail(&napi->poll_list, &sd->poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}
该函数将napi->poll_list加入sd->poll_list中,并置位了系统注册的软中断的第NET_RX_SOFTIRQ位。最后函数从DM9000的中断函数中返回退出。Linux中负责将skb传递给上层的函数是__netif_receive_skb(),那么该函数是怎么被调用的呢?
- 软中断处理函数net_rx_action()
当软中断被调度执行后,net_rx_action()函数会被调用。
static void net_rx_action(struct softirq_action *h)
{
......
while (!list_empty(&sd->poll_list)) {
......
n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);
......
if (test_bit(NAPI_STATE_SCHED, &n->state)) {
work = n->poll(n, weight);
trace_napi_poll(n);
}
......
if (unlikely(work == weight)) {
if (unlikely(napi_disable_pending(n))) {
local_irq_enable();
napi_complete(n);
local_irq_disable();
} else
list_move_tail(&n->poll_list, &sd->poll_list);
}
......
}
简化之后,该函数首先从poll_list中取出一个napi_struct ,即____napi_schedule()函数中加入到poll_list中的napi_struct,然后调用该napi_struct的poll函数。查找代码,找出poll函数指针指向的函数原型process_backlog(),在process_backlog()函数中执行__netif_receive_skb(skb)将skb传递给上层。
- 软中断什么时候被调用?
这个问题,我在网上搜了好一会儿工夫,最后发现,每次cpu执行完中断函数后,例如dm9000_interrupt()函数,在cpu退出中断asm_do_IRQ()前调用irq_exit(),在irq_exit()中执行软中断。在软中断中唤醒软中断处理任务,然后,cpu中断彻底退出,等到系统调度任务的时候,软终端所唤醒的任务得以被调度执行。具体来说,就是net_rx_action()函数。