Linux 2.4.35 时钟中断分析
写在前面
这篇文章的主要目的是自己做笔记,发到博客中是为了和大家分享。本人水平有限,环境大家抓bug,哈哈。另外,本人用的代码浏览器是source insight,用过的人都知道它很好很强大,特此推荐给没有用过的朋友。
时钟中断是在time_init()函数里面初始化的,而time_init()由start_kernel()调用。正如start_kernel()位于main.c中,它做的工作就是初始化内核的各个部分。
代码:
...
trap_init();
init_IRQ();
sched_init();
softirq_init();
time_init();
...
|
time_init()位于time.c,它主要用户初始化系统时间(从BIOS中取),和时钟中断。在这里我们关系的语句是初始化时钟中断:
...
setup_irq(0, &irq0);
...
|
irq0也位于time.c,是一个irqaction结构:
...
static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL};
...
|
irqaction结构的第一个成员就是特定中断的处理函数,在这里,我们看到时钟中断的处理函数是timer_interrput,继续往下追踪。
timer_interrput()位于time.c,在它的倒数第二行调用了do_timer_interrput(),这是处理时钟中断的主体:
...
do_timer_interrupt(irq, NULL, regs);
...
|
继续跟踪至do_timer_interrput(),在此处调用了do_timer():
进入do_timer,函数体很短:
void do_timer(struct pt_regs *regs)
{
(*(unsigned long *)&jiffies)++;
#ifndef CONFIG_SMP
/* SMP process accounting uses the local APIC timer */
update_process_times(user_mode(regs));
#endif
mark_bh(TIMER_BH);
if (TQ_ACTIVE(tq_timer))
mark_bh(TQUEUE_BH);
}
|
update_process_times函数跟新当前进程的时间,比如内核态时间、用户态时间等,另外,将当前进程的时间片减1,如果时间片counter为0,且调度策略不是SCHED_FIFO就把当前进程的need_resched置为1,表示可以重新调度:
...
struct task_struct *p = current;
int cpu = smp_processor_id(), system = user_tick ^ 1;
update_one_process(p, user_tick, system, cpu);
if (p->pid) {
if (--p->counter <= 0) {
p->counter = 0;
/*
* SCHED_FIFO is priority preemption, so this is
* not the place to decide whether to reschedule a
* SCHED_FIFO task or not - Bhavesh Davda
*/
if (p->policy != SCHED_FIFO) {
p->need_resched = 1;
}
}
...
|
回到,do_timer,紧接着update_process_times,执行了代码:
mark_bh(TIMER_BH);
if (TQ_ACTIVE(tq_timer))
mark_bh(TQUEUE_BH);
|
激活钟中断的下半部,表示时钟下半部可以执行;另外,如果定时任务队列不为空,则将TQUEUE_BH也激活。。自此,时钟中断的主要部分处理完毕。
走到这里是不是表示时钟中断就完成了呢?答案是否定的。前面只完成了上半部,还有下半部,也就是TIMER_BH没做呢。要知道接下来做什么,我们要看执行完硬件中断以后会干些什么事。处理中断是在do_IRQ()中进行的,它位于irq.c。在do_IRQ的末尾,有那么两句:
...
if (softirq_pending(cpu))
do_softirq();
...
|
这两句的意思是,如果当前有软件中断被置位,则执行他们的处理函数。那么,这有和TIMER_BH有什么关系呢?这就要看看do_timer中的mark_bh(TIMER_BH)了:
static inline void mark_bh(int nr)
{
tasklet_hi_schedule(bh_task_vec+nr);
}
|
函数很简单,可以知道TIMER_BH对应bh_task_vec[0]。继续跟踪下去:
static inline void tasklet_hi_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
__tasklet_hi_schedule(t);
}
|
还是很简单,嘿嘿。条件判断先不管,直接进入__tasklet_hi_schedule:
void fastcall __tasklet_hi_schedule(struct tasklet_struct *t)
{
int cpu = smp_processor_id();
unsigned long flags;
local_irq_save(flags);
t->next = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = t;
cpu_raise_softirq(cpu, HI_SOFTIRQ);
local_irq_restore(flags);
}
|
这里可以知道,mark_bh(TIMER_BH)把bh_task_vec[0]挂到tasklet_hi_vec[cpu].list上。另外,这里还有我们寻找的软中断信息:
cpu_raise_softirq(cpu, HI_SOFTIRQ)
|
这句的意思是将当前cpu的HI_SOFTIRQ软中断置位,也就是说它是在do_IRQ末尾要执行的东东。
接下来看看HI_SOFTIRQ对应的处理函数是什么。软件中断是在softirq_init()中初始化的,在start_kernel()中time_init()之前调用:
void __init softirq_init()
{
int i;
for (i=0; i<32; i++)
tasklet_init(bh_task_vec+i, bh_action, i);
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
|
这里,我们很清楚的看到,HI_SOFTIRQ对应的处理函数的tasklet_hi_action,继续跟踪下去:
static void tasklet_hi_action(struct softirq_action *a)
{
int cpu = smp_processor_id();
struct tasklet_struct *list;
local_irq_disable();
list = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = NULL;
local_irq_enable();
while (list) {
struct tasklet_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_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = t;
__cpu_raise_softirq(cpu, HI_SOFTIRQ);
local_irq_enable();
}
}
|
函数的主体是一个while循环,它遍历当前cpu的tasklet_struct链表,一一执行每个tasklet_struct注册的函数func。
问题又来了:这和TIMER_BH有什么关系?要解决这个疑问,回到tasklet_hi_schedule()中的__tasklet_hi_schedule(),此函数接受一个tasklet_struct,并把它挂到tasklet_hi_vec[cpu].list上。也就是说,mark_bh(TIMER_BH)将TIMER_BH对应的tasklet_struct挂到tasklet_hi_vec[cpu].list上,这个tasklet_struct对应的func成员就是TIMER_BH的处理函数!至于这些tasklet_struct怎么来的,由tasklet_hi_schedule(bh_task _vec+nr)可知他们来自bh_task_vec数组。
回忆softirq_init,里面有一个循环体:
...
for (i=0; i<32; i++)
tasklet_init(bh_task_vec+i, bh_action, i);
...
|
Bingoo!原来bh_task_vec在这里初始化,从这里我们还知道bh_task_vec的长度为32。
找到TIMER_BH的定义,在interrupt.h中:
enum {
TIMER_BH = 0,
TQUEUE_BH,
DIGI_BH,
SERIAL_BH,
RISCOM8_BH,
SPECIALIX_BH,
AURORA_BH,
ESP_BH,
SCSI_BH,
IMMEDIATE_BH,
CYCLADES_BH,
CM206_BH,
JS_BH,
MACSERIAL_BH,
ISICOM_BH
};
|
嘿嘿,不仅知道了TIMER_BH的值为0,还知道了其它的BH的值,意外的收获哦!走到了这里,依然不知道TIMER_BH对应的处理函数是什么?浏览softirq.c,会发现有那么一个函数:
void init_bh(int nr, void (*routine)(void))
{
bh_base[nr] = routine;
mb();
}
|
看名字就能猜到它是用来初始化下半部的,查找init_bh的caller,发现一个可以的名称:sched_init。没错,就是它,在start_kernel中和time_init出现在一起,看名字也是初始化某个东西,至于它初始化的是什么,不是这里讨论的范畴。跟踪到里面,可以看到那么几句:
init_bh(TIMER_BH, timer_bh);
init_bh(TQUEUE_BH, tqueue_bh);
init_bh(IMMEDIATE_BH, immediate_bh);
|
YES!找到我们需要的东西了,而且还有意外收获,不仅知道了TIMER_BH对应的处理函数是timer_bh,还知道了TQUEUE_BH和IMMEDIATE_BH的处理函数。
结合init_bh可以知道,timer_bh挂在了bh_base[0]上。要彻底搞清楚timer_bh是如何与bh_task_vec联系起来的,还需回到soft_init()中的循环体和task_init()
... for (i=0; i<32; i++) tasklet_init(bh_task_vec+i, bh_action, i); ...
|
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}
|
可以看出,bh_task_vec中每个tasklet_struct的func都与bh_action挂钩,并且将data成员设为对应的BH号。这样,tasklet_hi_action中的t->func(t->data)对应到TIMER_BH,就是bh_action(0)。继续深入bh_action():
static void bh_action(unsigned long nr)
{
int cpu = smp_processor_id();
if (!spin_trylock(&global_bh_lock))
goto resched;
if (!hardirq_trylock(cpu))
goto resched_unlock;
if (bh_base[nr])
bh_base[nr]();
hardirq_endlock(cpu);
spin_unlock(&global_bh_lock);
return;
resched_unlock:
spin_unlock(&global_bh_lock);
resched:
mark_bh(nr);
}
|
至此,终于真相大白了!什么?还没看到?bh_base[nr]()!调用bh_action(0)就是调用bh_base[0](),也即timer_bh()。
到这里,第一部分就结束了。回顾一下,这一部分主要分析了时钟中断的初始化以及处理函数的工作流程。要知道时钟中断的下半部,也就是timer_bh()做了什么,且听下回分解~(嘿嘿,其实现在我也还不是很清楚,边学边写~)
阅读(652) | 评论(0) | 转发(0) |