最近我又开始看这个《深入理解linux网络技术内幕》了。以前一直觉得这本书是一个巨无霸,昨天和前天各花了一点时间。大概是一直在搞内核的缘故吧!现在看起来倒不是很吃力了。大概看了1/3吧,虽然的确有点跑马观花了意味,但是自我感觉还是明白了主干的东西。《linux内核情景分析》里面对软中断讲得比较简略,而《深入》那书后边还图书馆了,所以导致我对软中断的理解很不够透彻。现在大致终结一下吧! 首先,软中断的执行是由ksoftirqd完成的,这是多个内核进程,每个CPU都有一个这样的进程,申明如下:
//linux-3.0.8/include/linux/interrupt.h DECLARE_PER_CPU(struct list_head [NR_SOFTIRQS], softirq_work_list); DECLARE_PER_CPU(struct task_struct *, ksoftirqd); static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp; struct softirq_action { void (*action)(struct softirq_action *); };
softirq_action就是一个函数指针,NR_SOFTIRQS就是软中断的个数,不信你可以看看下边。
enum { HI_SOFTIRQ=0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ NR_SOFTIRQS };
在每个ksoftirqd线程中,它所干的是就去执行softirq_vec函数指针数组中的函数,相当于按优先级执行。每个软中断的函数都是在内核里定义好的。比如tasklet_hi_action(), tasklet_action(),net_rx_action()。当然,这还不算完。对于不同的软中断函数,执行的方法也不一样。就tasklet_hi_action()和tasklet_action()而言,它们各自有一个链表分别叫tasklet_vec,tasklet_hi_vec。这些链表单元结构都是tasklet_struct类型的。定义如下:
//linux-3.0.8/include/linux/interrupt.h struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); unsigned long data; };
//linux-3.0.8/kernel/softirq.c static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec); static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
看到那个func函数指针没有,这个就是链表最重要的元素。软中断函数tasklet_hi_action()和tasklet_action()的作用就是调用这个链表中的func函数。可以根据需要练添加tasklet_struct元素进入链表。对于net_rx_action(),这函数就是真正衔接中断的函数,算是所谓的“bottom half”了。它的工作机理其实和前面两个差不多。依然是一个链表,链表的表头在softnet_data。这是一个全局的数据结构。每一个CPU都有一个对应的soft_data,它包含一个headlist polllist的指针。polllist实际连接了napi_struct类型。而这个napi_struct类型是net_device息息相关。napi_struct中的poll指针就指向了具体的驱动模块中的poll()函数。到这里,我们终于找到了和上半部衔接的地方了。下面这是e100驱动的poll函数。
static int e100_poll(struct napi_struct *napi, int budget) { struct nic *nic = container_of(napi, struct nic, napi); unsigned int work_done = 0; e100_rx_clean(nic, &work_done, budget); e100_tx_clean(nic); /* If budget not fully consumed, exit the polling mode */ if (work_done < budget) { napi_complete(napi); e100_enable_irq(nic); } return work_done; }
看似清爽的函数其实并没有这么简单。主要的工作都是e100_rx_clean()在干。然后就是接收了,值得一提的是e100使用了DMA,然后调用的netif_receive_skb(skb)。这的确是历史性的一刻了。这标志着我们的数据经过千辛万苦终于是走出了驱动层,向L3迈进。
发表评论