软中断
软中断由
struct softirq_action{
Void(*action)(struct softirq_action *)
}
表示。并且当前内核中的软中断总数固定为32个,由数组Static struct softirq_actionsoftirq_vec[NR_SOFTIRQS]来表示。
目前只用到了其中的9个。包括定时器、网络、tasklet等。
软中断的执行是通过do_softirq来查询数组softirq_vec[NR_SOFTIRQS]对应的位图,查看是否有软中断挂起,
有则将已挂起的软中断通过:
h=softirq_vec
h->action(h)
来执行对应的软中断的action函数。所以,只要调用do_softirq都会查询软中断,并处理挂起的软中断。
一般do_softirq会在下列地方会执行:
1. 硬件中断返回函数,在硬件中断的返回函数irq_exit()会调用do_softirq(通过函数do_softirq的包装函数
invoke_softirq()间接访问)
2. ksoftirqd内核线程
3. 显式调用do_softirq处,例如网络驱动中等
软中断的使用
首先,需使用新的软中断,则需用户在内核中分配新的索引
然后,通过open_softirq()函数注册一个软中断的处理函数,
例如open_softirq(TASKLET_SOFTIRQ,tasklet_action),其中tasklet_action函数即为软中断处理函数。
最后,需要在别的地方通过raise_softirq(TASKLET_SOFTIRQ)来触发软中断。
以使处理器择机尽早通过do_softirq来执行tasklet_action函数。假如在中断中使用了tasklet,则在硬件中断
退出前会有机会执行。
此处说明下ksoftirqd线程,该线程通过是early_initcall(spawn_ksoftirqd)
spawn_ksoftirqd-> cpu_callback –>kthread_create创建。
假如raise_softirq的步骤不是在中断处理函数中完成的(通过tasklet_schedule会执行raise_softirq操作),
或者raise_softirq之后一直未产生硬件中断,则软中断最后的执行是由调用了do_softirq函数的此线程来完成的,
或者如网卡驱动中直接显示调用do_softirq函数。值得说明的是,正是由于在硬件中断退出前会执行do_softirq,
使得采用tasklet的中断下半部性能很好。因为加入系统负荷不重,则tasklet会被立即执行。但其执行绝对不会晚于
下个内核时间片。因为中断退出后就会执行do_softirq,内核时间片采用的是定时器中断,在其中断处理函数退出前
会调用do_softirq,所以tasklet的执行时间比较有保障,其性能比同样用于中断下半部的工作队列高很多。
但是,假如是由ksoftirqd来处理的其他类型软中断性能可能就会差一点。
tasklet软中断
tasklet是在软中断TASKLET_SOFTIRQ的基础上实现的重要步骤如下:
首先,tasklet可通过DECLARE_TASKLET(my_tasklet,my_tasklet_handler,dev)
宏来初始化一个tasklet_struct。此处的task_handler为tasklet的处理函数。
其次,通过tasklet_schedule()将tasklet调度,以便有机会尽早的运行。
其中应该将第二步放在中断处理函数中,这样执行完中断处理函数后就有立即有
机会执行tasklet了。
tasklet和软中断的关系
首先系统在初始化的时候在start_kernel函数中通过调用softirq_init()函数,
在softirq_init()函数中通过调用open_softirq(TASKLET_SOFTIRQ,tasklet_action);
来注册一个软中断,其处理函数为tasklet_action。注册之后还需调用raise_softirq函数
来告诉内核TASKLET_SOFTIRQ软中断已挂起,以使处理器可以尽早处理该型软中断。
挂起的操作通过tasklet_schedule()函数间接调用raise_softirq()函数来实现
void __tasklet_schedule(structtasklet_struct *t)
{
unsignedlong flags;
local_irq_save(flags);
t->next= NULL;
*__get_cpu_var(tasklet_vec).tail= t;
__get_cpu_var(tasklet_vec).tail= &(t->next);
raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
此后则等待中断处理函数退出后调用do_softirq来执行TASKLET_SOFTIRQ软中断的处理函数asklet_action。
static void tasklet_action(structsoftirq_action *a)
{
structtasklet_struct *list;
local_irq_disable();
list= __get_cpu_var(tasklet_vec).head;
……
while(list) {
structtasklet_struct *t = list;
list= list->next;
if(tasklet_trylock(t)) {
if(!atomic_read(&t->count)) {
if(!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
BUG();
t->func(t->data);
…...
}
可以发现在tasklet_action函数中,会获取tasklet链表,然后依次
轮训已注册的tasklet处理函数。例如之前通过DECLAR_TASKLET宏注册的
my_tasklet_handler函数。
所以最终在驱动中使用tasklet需要的工作是:
1.初始化一个tasklet_struct结构,并注册对应的tasklet处理函数。
2.tasklet处理函数的实现
内核定时器
内核的定时器是通过定时器软中断实现的。
内核部分:
1.注册软中断:在内核启动时,在start_kernel函数中通过调用init_timers->open_softirq(TIMER_SOFTIRQ, run_timer_softirq)
实现注册TIMER_SOFTIRQ软中断,其中run_timer_softirq函数为软中断处理函数。
2.中断挂起:timer_interrupt->update_process_times->run_local_timers->raise_softirq(TIMER_SOFTIRQ);
其中timer_interrupt函数为定时器中断处理函数
驱动部分:
1.初始化一个timer_list,特别是初始化其中的func函数指针
2.func函数的实现,此为内核定时器中断处理函数。即定时时间到后,会调用此函数。
具体执行
在内核时间片到,定时器硬件中断退出时,会调用do_softirq函数。在此函数中先调用软中断处理函数run_timer_softirq,在此处理
函数中查看和修改内核定时器链表上的定时器。若定时时间到,则通过timer->func调用定时器处理函数。