1.时钟中断的产生
时钟中断是特别重要的一个中断,因为整个操作系统的活动都受到他的鼓励。系统利用时钟中断维持系统时间,促使环境的切换,以保证所有进程共享CPU;利用时钟中断并进行记账,监督系统工作以及确定未来的调度优先级等工作。可以说“时钟中断”是整个操作系统的脉搏。
从本质上说,时钟中断只是一个周期性的信号,完全是硬件行为,该信号触发CPU去执行一个中断服务程序。
2.时钟中断处理程序
每一次时钟中断的产生都会进行一系列的操作,其中调用的主要函数为do_timer(),操作过程如下:
(1)给jiffies变量增加1.
(2)更新资源消耗系统值,比如当前进程所消耗的系统时间和用户时间。
(3)执行已经到期的定时期。
(4)执行scheduler_tick()函数。
(5)更新墙上时间,该事件存放在xtime变量中。
(6)计算平均负载值。
因为上述工作分别由单独的函数负责完成,所以实际do_timer()执行的代码看起来非常简单:
void do_timer(struct pt_regs *regs)
{
jiffies ++;
update_process_times(user_mode(regs));
update_times();
}
update_process_times()函数根据时钟中断产生的位置,对相关时间进行了更新。
void update_process_times(int user_tick)
{
struct task_struct *p=current;
int cpu = smp_processor_id();
int system = user_tick ^ 1;
update_one_process(p, user_tick, system, cpu);
/*更新进程时间:
p->utime += user; //进程执行用户代码的时间
p->stime += system; //进程执行内核代码的时间
*/
run_local_timers(); //处理到期的定时期(软中断)
schedluer_tick(user_tick, system); //负责减少当前运行进程的时间片计数值并且在需要的时候设置need_resched标志。(need_resched表示CPU以系统空间返回到用户空间前要进行一次调度)
}
update_times()函数更新墙上时钟。
void update_times(void)
{
unsigned long ticks;
ticks = jiffies - wall_jiffies;
if(ticks) {
wall_jiffies - wall_jiffies;
update_wall_time(ticks);
}
last_time_offset = 0;
calc_load(ticks);
}
do_time()函数执行完毕后返回具体的时钟处理程序,继续执行后面的工作,释放xtime_lock锁,然后退出。
3.定时器及其应用
定时器是管理内核锁化时间的基础,有时也被称为动态定时器或内核定时器。内核经常需要退后某些执行的代码,但是,退后这个概念很模糊,下半部的本意并非是放到某个时间去执行任务,而仅仅是不在此时刻执行就行了。我们所需要的是能够使工作在指定时间上执行,内核定时器正是解决这个问题的理想工具。
定时器的使用很简单,只需要执行一些初始化工作,设置一个到期时间,注意到时候执行的函数,然后激活定时器就可以了。指定的函数将在定时器到期时自动执行。注意定时器并不周期运行,他在到期后就自行销毁,这也正是这种定时器被称为动态定时器的一个原因。动态定时器不断的创建和销毁,而且他的运行次数页不受限制。定时器在内核中用得非常普遍。
(1)时用定时器
定时器由timer_list结构表示,定义如下:
struct timer_list {
struct list_head entry; //包含定时器的链表
unsinged long expires; //以节拍为单位的定时值
spinlock_t lock; //保护定时器的锁
void (*function)(unsigned long); //定时器到时要执行的函数
unsigned long data; //传递给处理函数的长整型参数
};
内核提供了一组与定时器相关的接口简化了对定时器的操作。
创建定时器首先需要定义它:
struct timer_list my_timer;
接下来需要通过一个辅助函数初始化定时器数据结构的内部值,初始化必须在对定时器操作前完成。
init _timer(&my_timer);
现在就可以填充结构中需要的值。
my_timer.expires = jiffies +delay ; //定时器到期节拍数
my_timer.data = 0; //给定时器处理函数传入0值
my_timer.function = my_function; //定时器到期调用的函数
如果当前jiffies计数等与或大与my_timer.expires,由my_timer.function指向的处理函数就会开始执行,另外该函数还要使用长整型参数ma_timer.data。我们从timer_list结构就可以看得到,处理函数必须符合下面的函数原型:
void my_timer_function(unsigned long data);
data参数不同,则可以对应不同的定时器。如果不需要这个参数,可以简单传递0(或其他值);
最后必须激活定时器:
add_timer(&my_timer);
虽然内核不会在定时时间到达之前运行定时器处理函数,但是却有可能推迟定时器处理函数。一般来说,定时器都会在到期后马上就会执行,但是也可能回被推迟到下一次时钟节拍才能执行,所以不能用定时器实现任何硬实时任务。
如果需要在定时器到期之前停止定时器,可以使用del_timer()函数:
del_timer(&my_timer);
被激活或未被激活的定时器都可以使用该函数,如果定时器还位被激活,则该函数返回0;否则返回1。注意,不需要为已经到期的定时器定用该函数,因为它们会自动被删除。
(2).执行定时器
内核在时钟中断发生后执行定时器,定时器作为软中断在下半部中执行。具体来说,时钟中断处理程序会执行update_process_timers()函数,该函数随即调用run_local_timers()函数:
void run _local_timers(void)
{
raise_softirq(TIMER_SOFTIRQ);
}
run_timer_softirq()函数处理软中断TIMER_SOFTIRQ,其处理函数为run_timer_softirq,该函数用来处理所有的软件时钟,从而在当前处理器上运行所有的超时定时器。
(3)定时器的应用
假定内核决定把当前进程挂起2s,可以通过执行下面的代码来做到这一点:
timeout = 2*Hz;
set_current_state(TASK_INTERRUPTIBLE); //当前是设置使进程进入睡眠状态,并收到信号将被唤醒。若该为TASK_UNINTERRUPTIBLE则不可被信号唤醒
remaining = schedule_timeout(timeout);
调用schedule_timeout()函数必须处于进程上下文中,并且不能持有锁。
unsigned schedule_timeout(unsigned long timeout)
{
struct timer_list timer;
unsigned long expire;
expire = timeout + jiffies;
init_time (&timer);
timer.expires = expire;
timer.data = (unsigned long) current;
timer.function = process_timeout; //该函数设置为weak_up_process()函数唤醒当前进程
add_timer(&timer);
schedule (); //进程挂起直到定时器到期
del_timer(&timer);
timeout = expire - jiffies;
return (timeout <0?0:timeout);
}