Linux内核中断及定时器实现

时间:2022-05-16 08:53:57
内核中断及定时器实现分析


定时器是Linux提供的一种定时服务的机制。它在某个特定的时间唤醒某个进程,来做一些工作。Linux初始化时,init_IRQ()函数设定8253的定时周期为10ms(一个tick值)。同样,在初始化时,time_init()用setup_irq()设置时间中断向量irq0,中断服务程序为timer_interrupt

2.4版内核及较早的版本当中,定时器的中断处理采用底半机制,底半处理函数的注册在start_kernel()函数中调用sechd_init(),在这个函数中又调用init_bh(TIMER_BH, timer_bh)注册了定时器的底半处理函数。然后系统才调用time_init()来注册定时器的中断向量和中断处理函数。

在中断处理函数timer_interrupt()中,主要是调用do_timer()函数完成工作。do_timer()函数的主要功能就是调用mark_bh()产生软中断,随后处理器会在合适的时候调用定时器底半处理函数timer_bh()。在timer_bh()中,实现了更新定时器的功能。2.4.23版的do_timer()函数代码如下(经过简略):

void do_timer(struct pt_regs *regs)

{

       (*(unsigned long *)&jiffies)++;

       update_process_times(user_mode(regs));

       mark_bh(TIMER_BH);

}

 

而在内核2.6版本以后,定时器中断处理采用了软中断机制而不是底半机制。时钟中断处理函数仍然为timer_interrup()-> do_timer_interrupt()-> do_timer_interrupt_hook()-> do_timer()。不过do_timer()函数的实现有所不同:

void do_timer(struct pt_regs *regs)

{

       jiffies_64++;

       update_process_times(user_mode(regs));

       update_times();

}

两者所调用的函数基本相同,但是2.4.23版内核与2.6.6内核中,对于update_process_times()函数的实现不同:

void update_process_times(int user_tick)

{

       struct task_struct *p = current;

       int cpu = smp_processor_id(), system = user_tick ^ 1;

 

       update_one_process(p, user_tick, system, cpu);

       run_local_timers();

       scheduler_tick(user_tick, system);

}

update_process_times()调用run_local_timers()引起TIMER_SOFTIRQ定时器软中断,处理器在随后的合适的时机运行软中断处理函数run_timer_softirq,这个函数是在init_timers()函数中注册的:

void __init init_timers(void)

{

       timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,

                            (void *)(long)smp_processor_id());

       register_cpu_notifier(&timers_nb);

       open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);

}

事实上软中断处理函数run_timer_softirq()并没有做什么工作,主要的任务还是通过调用__run_timers()函数完成的,这个函数相当于2.4.23内核当中的run_timer_list()函数的功能。

static inline void __run_timers(tvec_base_t *base)

{

       struct timer_list *timer;

 

       spin_lock_irq(&base->lock);

       /*这里进入定时器处理循环,利用系统全局jiffies与定时器基准jiffies进行对比,如果前者大,则表明有某些定时器需要进行处理了,否则表示所有的定时器都没有超时*/

       while (time_after_eq(jiffies, base->timer_jiffies)) {

              struct list_head work_list = LIST_HEAD_INIT(work_list);

              struct list_head *head = &work_list;

             int index = base->timer_jiffies & TVR_MASK;

 

              /*

              在时间列表数据结构当中查找是否存在需要进行超时处理的定时器,时间列表的数据结构定义如下:

              typedef struct tvec_s {

                     struct list_head vec[TVN_SIZE];

} tvec_t;

 

typedef struct tvec_root_s {

                     struct list_head vec[TVR_SIZE];

} tvec_root_t;

             

struct tvec_t_base_s {

                     spinlock_t lock;

                     unsigned long timer_jiffies;

                     struct timer_list *running_timer;

                     tvec_root_t tv1;

                     tvec_t tv2;

                     tvec_t tv3;

                     tvec_t tv4;

                     tvec_t tv5;

} ____cacheline_aligned_in_smp;


 

*/

              if (!index &&

                     (!cascade(base, &base->tv2, INDEX(0))) &&

                            (!cascade(base, &base->tv3, INDEX(1))) &&

                                   !cascade(base, &base->tv4, INDEX(2)))

                     cascade(base, &base->tv5, INDEX(3));

              ++base->timer_jiffies;

              list_splice_init(base->tv1.vec + index, &work_list);

repeat:

/*如果当前找到的时间数组对应的列表不为空,则表明该列表上串连的所有定时器都已经超时,循环调用每个定时器的处理函数,并将其从列表中删除,直到列表为空为止。*/

              if (!list_empty(head)) {

                     void (*fn)(unsigned long);

                     unsigned long data;

 

                     timer = list_entry(head->next,struct timer_list,entry);

                    fn = timer->function;

                    data = timer->data;

 

                     list_del(&timer->entry);

                     set_running_timer(base, timer);

                     smp_wmb();

                     timer->base = NULL;

                     spin_unlock_irq(&base->lock);

                     fn(data);

                     spin_lock_irq(&base->lock);

                     goto repeat;

              }

       }

       set_running_timer(base, NULL);

       spin_unlock_irq(&base->lock);

}