/*--------------------------------------
欢迎转载:http://kylinux.cublog.cn
---------------------------------------*/
接着上章,转入time_init_hook():
void __init time_init_hook(void)
{
//注册中断处理函数
setup_irq(0, &irq0);
}
Irq0定义如下:
static struct irqaction irq0 = {
timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer", NULL, NULL};
对应的中断处理函数为:timer_interrupt():
/*
* This is the same as the above, except we _also_ save the current
* Time Stamp Counter value at the time of the timer interrupt, so that
* we later on can estimate the time of day more exactly.
*/
irqreturn_t timer_interrupt(int irq, void *dev_id)
{
#ifdef CONFIG_X86_IO_APIC
if (timer_ack) {
/*
* Subtle, when I/O APICs are used we have to ack timer
IRQ
* manually to reset the IRR bit for
do_slow_gettimeoffset().
* This will also deassert NMI lines for the watchdog
if run
* on an 82489DX-based system.
*/
spin_lock(&i8259A_lock);
outb(0x0c, PIC_MASTER_OCW3);
/* Ack the IRQ; AEOI will end it automatically. */
inb(PIC_MASTER_POLL);
spin_unlock(&i8259A_lock);
}
#endif
do_timer_interrupt_hook();
if (MCA_bus) {
/* The PS/2 uses level-triggered interrupts. You can't
turn them off, nor would you want to (any attempt to
enable edge-triggered interrupts usually gets
intercepted by a
special hardware circuit). Hence we have to acknowledge
the timer interrupt. Through some incredibly stupid
design idea, the reset for IRQ 0 is done by setting the
high bit of the PPI port B (0x61). Note that some
PS/2s,
notably the 55SX, work fine if this is removed. */
u8 irq_v = inb_p( 0x61 ); /* read the current
state */
outb_p( irq_v|0x80, 0x61 ); /* reset the IRQ */
}
return IRQ_HANDLED;
} |
核心处理函数为 do_timer_interrupt_hook():
static inline void
do_timer_interrupt_hook(void)
{
global_clock_event->event_handler(global_clock_event);
}
而global_clock_event在前面提过了,中断处理函数初始化通过如
下调用:
static inline void
tick_set_periodic_handler(struct clock_event_device *dev,
int broadcast)
{
dev->event_handler =
tick_handle_periodic;
}
初始化dev->event_handler函数的过程:
clockevents_register_device() >
clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, dev)
>
raw_notifier_call_chain(&clockevents_chain, reason, dev) >
__raw_notifier_call_chain(nh,
val, v, -1, NULL) >
notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls) :
...
ret =
nb->notifier_call(nb, val, v);
/* 注意这个参数v,指向clockevents_register_device
* 的参数clock_event_device *global_clock_event;
* 而参数nb,就是&clockevents_chain
*/
然而,这个notifier_call函数指针赋值在start_kernel() > tick_init() :
clockevents_register_notifier(&tick_notifier);
而tick_notifier的定义:
static struct notifier_block tick_notifier = {
.notifier_call = tick_notify,
};
还没有完呢,
tick_notify>
tick_check_new_device()>
tick_setup-device()>
tick_setup_periodic()>
tick_set_periodic_handler()
这个dev->event_handler()处理函数终于初始化了
|
直接转到tick_handle_periodic():
void tick_handle_periodic(struct clock_event_device *dev)
{
int cpu = smp_processor_id();
ktime_t next;
tick_periodic(cpu);
if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
return;
/*
* Setup the next period for devices, which do not have
* periodic mode:
*/
next = ktime_add(dev->next_event, tick_period);
for (;;) {
if (!clockevents_program_event(dev, next, ktime_get()))
return;
tick_periodic(cpu);
next = ktime_add(next, tick_period);
}
}
其中tick_periodic调用就是以前的一系列更新操作,包括更新进程时间片等等.
static void tick_periodic(int cpu)
{
if (tick_do_timer_cpu == cpu) {
write_seqlock(&xtime_lock);
/* Keep track of the next tick event */
tick_next_period = ktime_add(tick_next_period,
tick_period);
do_timer(1);
write_sequnlock(&xtime_lock);
}
//更新当前运行进程的与时钟相关的信息
update_process_times(user_mode(get_irq_regs()));
profile_tick(CPU_PROFILING);
} |
我们忽略选择编译部份,转到do_timer()
void do_timer(unsigned long
ticks)
{
//
更新jiffies计数.jiffies_64与jiffies在链接的时候,实际是指向同一个区域
jiffies_64 += ticks;
////更新当前时间.xtime的更新
update_times(ticks);
}
Update_process_times()代码如下:
void update_process_times(int
user_tick)
{
struct task_struct *p =
current;
int cpu = smp_processor_id();
//这里判断时钟中断发生用户空间,还是发生在内核模式,然后计数值加1
/* Note: this timer irq
context must be accounted for as well. */
if (user_tick)
account_user_time(p,
jiffies_to_cputime(1));
else
account_system_time(p, HARDIRQ_OFFSET, jiffies_to_cputime(1));
//激活时间软中断
run_local_timers();
if (rcu_pending(cpu))
rcu_check_callbacks(cpu, user_tick);
//减少时间片。
scheduler_tick();
run_posix_cpu_timers(p);
}
run_local_timers()
void run_local_timers(void)
{
raise_softirq(TIMER_SOFTIRQ);
}
而该中断的处理函数__run_timers():
static inline void __run_timers(tvec_base_t *base)
{
struct timer_list *timer;
spin_lock_irq(&base->lock);
/*这里进入定时器处理循环,利用系统全局jiffies与定时器基准jiffies进行对比,如果前者大,则表明某些定时器进行处理了,否则表示所有的
定时器都没有超时.因为CPU可能关闭中断,引起时钟中断信号丢失.可能jiffies要大base->timer_jiffies
while (time_after_eq(jiffies, base->timer_jiffies)) {
//定义并初始化一个链表
struct list_head work_list;
struct list_head *head = &work_list;
int index = base->timer_jiffies & TVR_MASK;
/*
* Cascade timers:
*/
//当index == 0时,说明已经循环了一个周期
//则将tv2填充tv1.如果tv2为空,则用tv3填充tv2.依次类推......
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
++base->timer_jiffies;
//将base->tv1.vec项移至work_list.并将base->tv1.vec置空
list_replace_init(base->tv1.vec + index,
&work_list);
/*如果当前找到的时间数组对应的列表不为空,则表明该列表上串连的所有定时器都已经超时,循环调用每个定时器的处理
函数,并将其从列表中删除,直到列表为空为止。*/
while (!list_empty(head)) {
void (*fn)(unsigned long);
unsigned long data;
//遍历链表中的每一项.运行它所对应的函数,并将定时器从链表上脱落
timer = list_first_entry(head, struct
timer_list,entry);
fn = timer->function;
data = "">
timer_stats_account_timer(timer);
set_running_timer(base, timer);
detach_timer(timer, 1);
spin_unlock_irq(&base->lock);
{
int preempt_count = preempt_count();
fn(data);
if (preempt_count != preempt_count()) {
printk(KERN_WARNING "huh,
entered %p "
"with preempt_count
%08x, exited"
" with %08x?\n",
fn, preempt_count,
preempt_count());
BUG();
}
}
spin_lock_irq(&base->lock);
}
}
set_running_timer(base, NULL);
spin_unlock_irq(&base->lock);
}
|
硬
件定时器计完一个jiffies之后,会引起硬件中断,在硬件中断服务程序中会触发软中断,在定时器软中断服务程序中会调用
__run_timers()完成定时器多级hash
table的处理,并且处理定时时间到的所有timer。__run_timers算法实现描述如下:
1、根据当前jiffes和base->timer_jiffies循
环判断多级hash table扫描条件,如果满足条件,那么继续(2),否则退出循环。
2、通过base->timer_jiffies计算得到V1
table中需要处理的索引项。并且将索引高层hash table中的具体项,将该项中的timer分散到低层table中去。
3、增加base->timer_jiffies值,提取出V1中索引
得到的定时器链表。
4、如果该定时器链表不为空,那么依次处理链表中的定时器,处理过程为调用定
时器的处理函数timer->function。
下面对scheduler_tick()函数做解析吧.
void scheduler_tick(void)
{
int cpu = smp_processor_id();
struct rq *rq = cpu_rq(cpu);
struct task_struct *curr = rq->curr;
u64 next_tick = rq->tick_timestamp + TICK_NSEC;
spin_lock(&rq->lock);
__update_rq_clock(rq);
/*
* Let rq->clock advance by at least TICK_NSEC:
*/
if (unlikely(rq->clock < next_tick))
rq->clock = next_tick;
rq->tick_timestamp = rq->clock;
update_cpu_load(rq);
if (curr != rq->idle) /* FIXME: needed? */
curr->sched_class->task_tick(rq, curr);
spin_unlock(&rq->lock);
#ifdef CONFIG_SMP
rq->idle_at_tick = idle_cpu(cpu);
trigger_load_balance(rq, cpu);
#endif
} |
scheduler_tick()函数负责
减少运行进程的时间片计数值并且在需要时设置need_resched标志.该函数还负责平衡每个处理器的运行队列.
我们在do_timer()还漏掉了一个函
数:
static inline void
update_times(unsigned long ticks)
{
//更新xtime
update_wall_time();
//统计TASK_RUNNING TASK_UNINTERRUPTIBLE进程数量
calc_load(ticks);
}
**下张主要介绍软时钟中断,
摘自网上的资料,请待更新,呵呵
参考资料:
linux内核设计与实现
http://blog.ccidnet.com/blog-htm-do-showone-uid-84561-type-blog-itemid-258809.html
http://blog.chinaunix.net/u2/61477/showart_481379.html
http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=driver&Number=385219&page=0&view=collapsed&sb=5&o=all&fpart=1&vc=1
http://hi.baidu.com/80695073/blog/item/9c170f55be0ae5c1b745ae5b.html
http://blog.chinaunix.net/u1/51562/showart_509400.html
http://article.ednchina.com/Other/20080728070707.htm
|
|