2014年(84)
分类: LINUX
2014-05-05 15:00:25
原文地址:Linux时钟中断(2.6.23)(三) 作者:niutao.linux
上一节主要讲了时钟中断的注册过程,在这一节我们主要时钟中断的处理程序。
由上一节我们知道注册的时钟中断处理程序是timer_interrupt。我们使用cscope在内核里可以搜索到如下调用关系:
timer_interrupt()
|-->do_timer_interrupt_hook();
|-->global_clock_event->event_handler(global_clock_event);
到此我们又遇到了新的问题:
1、global_clock_event是什么?
2、global_clock_event->event_handler指针最终指向的那个函数?
首先global_clock_event是一个struct clock_event_device类型的全局指针变量,保存着系统中当前正在使用的时钟事件设备(保存了系统当前使用的硬件时钟中断发生时,要执行的中断处理函数的指针)。接下来我们使用cscope工具搜索内核就可以知道在被赋值为:
global_clock_event = &pit_clockevent;或者global_clock_event = &hpet_clockevent;现在的硬件大部分都支持HPET,所以在这里我们主要看hpet_clockevent。
static struct clock_event_device hpet_clockevent = {
.name = "hpet",
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
.set_mode = hpet_set_mode,
.set_next_event = hpet_next_event,
.shift = 32,
.irq = 0,
};
可以看到在内核里时钟时间设备为hpet_clockevent。到此我们依然没有找到。前面提到的将global_clock_event赋值为hpet_clockevent是在hpet_enable()函数中。上一节我们提到时钟中断的注册中调用了late_time_init()函数(也即hpet_time_init函数),在这个函数中就会首先调用hpet_enable()函数,追踪之后函数的调用流程如下:
start_kernel()
|-->time_init()
|-->tsc_init();late_time_init=choose_time_init();/*choose_time_init()=hpet_time_init*/
|-->late_time_init()
|-->hpet_enable()/*如果硬件不支持hpet,则调用setup_pit_timer*/
|-->clockevents_register_device(&hpet_clockevent);
|-->clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, &hpet_clockevent);
|-->raw_notifier_call_chain(&clockevents_chain,
CLOCK_EVT_NOTIFY_ADD, &hpet_clockevent);
|--> __raw_notifier_call_chain(nh, val,&hpet_clockevent, -1,
NULL);
|-->notifier_call_chain(&nh->head, CLOCK_EVT_NOTIFY_ADD,
&hpet_clockevent, nr_to_call, nr_calls);
|-->nb->notifier_call(nb, CLOCK_EVT_NOTIFY_ADD,
&hpet_clockevent);
到此我们又遇到了一个问题,那就是clockevents_chain。使用cscope工具可以找到如下信息:
static struct notifier_block tick_notifier = {
.notifier_call = tick_notify,
};
start_kernel() /*early than time_init*/
|-->tick_init()
|-->clockevents_register_notifier(&tick_notifier);
|-->raw_notifier_chain_register(&clockevents_chain, nb);/*nb=&tick_notifier*/
|-->notifier_chain_register(&nh->head, n);/*nh=&clockevents_chain,n=nb*/
|-->rcu_assign_pointer(*nl, n);
#define rcu_assign_pointer(p, v) ({ \
smp_wmb(); \
(p) = (v); \
})
由此可见clockevents_chain->head->notifier_call最终被注册为tick_notify.结合前面提到的对hept_clockevent的操作的最后一个函数nb->notifier_call(nb, CLOCK_EVT_NOTIFY_ADD, &hpet_clockevent);,我们就可以将该句写为:
tick_notify(clockevents_chain->head, CLOCK_EVT_NOTIFY_ADD, &hpet_clockevent)。
下面我们来看看tick_notify函数:
tick_notify(clockevents_chain->head,CLOCK_EVT_NOTIFY_ADD,&hpet_clockevent)
|-->tick_check_new_device(&hpet_clockevent);
|-->tick_setup_device(td, newdev, cpu, cpumask);/*newdev=&hpet_clockevent*/
{
if (td->mode == TICKDEV_MODE_PERIODIC)
tick_setup_periodic(newdev, 0);
else
tick_setup_oneshot(newdev, handler, next_event);
}
enum tick_device_mode {
TICKDEV_MODE_PERIODIC,
TICKDEV_MODE_ONESHOT,
};
我们这里只看tick_setup_periodic(&hpet_clockevent,0)这种情况,也就是时钟中断的类型为
TICKDEV_MODE_PERIODIC 这种情况。
tick_setup_periodic(&clockevents_chain,0)
|-->tick_set_periodic_handler(dev,0);
|-->void tick_set_periodic_handler(struct clock_event_device *dev, int broadcast)
{
if (!broadcast)
dev->event_handler = tick_handle_periodic;
else
dev->event_handler = tick_handle_periodic_broadcast;
}
到此我们终于知道了hpet_clockevent->event_handler=tick_handle_periodic。结合前面讲的所有,也就是说 在时钟中断处理函数timer_interrupt()函数里调用的global_clock_event->event_handler= tick_handle_periodic。
到此我们就彻底搞清了中断的注册和调用过程,不过也都是之从代码的角度去讲解的,却不知道为何要使用这么复杂的过程?目的何在?下一次我们具体研究在没一次时钟中断到来的时候,系统都做了那些工作,也就是tick_handle_periodic()函数。