open_softirq结束后,init_timers就结束了,整个内核就可以享受时钟服务了,接下来start_kernel的609行调用hrtimers_init。由于我们没有配置CONFIG_HIGH_RES_TIMERS,所以这个函数仅仅是把全局notifier_block变量hrtimer_cpu_notify加入通知链,供将来的内核各模块使用。
然后,start_kernel的610行调用softirq_init来初始化整个软中断系统:
674void __init softirq_init(void) 675{ 676 int cpu; 677 678 for_each_possible_cpu(cpu) { 679 int i; 680 681 per_cpu(tasklet_vec, cpu).tail = 682 &per_cpu(tasklet_vec, cpu).head; 683 per_cpu(tasklet_hi_vec, cpu).tail = 684 &per_cpu(tasklet_hi_vec, cpu).head; 685 for (i = 0; i < NR_SOFTIRQS; i++) 686 INIT_LIST_HEAD(&per_cpu(softirq_work_list[i], cpu)); 687 } 688 689 register_hotcpu_notifier(&remote_softirq_cpu_notifier); 690 691 open_softirq(TASKLET_SOFTIRQ, tasklet_action); 692 open_softirq(HI_SOFTIRQ, tasklet_hi_action); 693} |
我们看到681~686行,初始化每个CPU的tasklet_vec、tasklet_hi_vec和softirq_work_list[]结构。689行,如果定义了CONFIG_HOTPLUG_CPU配置选项,就将全局notifier_block类型变量remote_softirq_cpu_notifier注册到通知链中。
随后691、692行将TASKLET_SOFTIRQ和HI_SOFTIRQ对应的软中断打开,这样I/O驱动程序中实现可延迟函数的首选方法tasklet就可以使用了,对这方面感兴趣的同学仍然请访问博客“下半部分”
http://blog.csdn.net/yunsongice/archive/2010/03/07/5354011.aspx
start_kernel的611行timekeeping_init函数:
534void __init timekeeping_init(void) 535{ 536 struct clocksource *clock; 537 unsigned long flags; 538 struct timespec now, boot; 539 540 read_persistent_clock(&now); 541 read_boot_clock(&boot); 542 543 write_seqlock_irqsave(&xtime_lock, flags); 544 545 ntp_init(); 546 547 clock = clocksource_default_clock(); 548 if (clock->enable) 549 clock->enable(clock); 550 timekeeper_setup_internals(clock); 551 552 xtime.tv_sec = now.tv_sec; 553 xtime.tv_nsec = now.tv_nsec; 554 raw_time.tv_sec = 0; 555 raw_time.tv_nsec = 0; 556 if (boot.tv_sec == 0 && boot.tv_nsec == 0) { 557 boot.tv_sec = xtime.tv_sec; 558 boot.tv_nsec = xtime.tv_nsec; 559 } 560 set_normalized_timespec(&wall_to_monotonic, 561 -boot.tv_sec, -boot.tv_nsec); 562 update_xtime_cache(0); 563 total_sleep_time.tv_sec = 0; 564 total_sleep_time.tv_nsec = 0; 565 write_sequnlock_irqrestore(&xtime_lock, flags); 566} |
这个函数主要是通过读取CMOS上的时钟来初始化全局时间变量xtime、raw_time以及total_sleep_time。具体的就不去分析了,主要讲讲读取CMOS数据的方法,来看函数read_persistent_clock:
void read_persistent_clock(struct timespec *ts)
{
unsigned long retval, flags;
spin_lock_irqsave(&rtc_lock, flags);
retval = x86_platform.get_wallclock();
spin_unlock_irqrestore(&rtc_lock, flags);
ts->tv_sec = retval;
ts->tv_nsec = 0;
}
而全局变量x86_platform在编译时候被初始化如下:
struct x86_platform_ops x86_platform = {
.calibrate_tsc = native_calibrate_tsc,
.get_wallclock = mach_get_cmos_time,
.set_wallclock = mach_set_rtc_mmss,
.iommu_shutdown = iommu_shutdown_noop,
.is_untracked_pat_range = is_ISA_range,
.nmi_init = default_nmi_init
};
所以通过的是mach_get_cmos_time函数来读取CMOS中的时间数据。这个函数本质上是通过CMOS_READ宏来进行,比如CMOS_READ(RTC_SECONDS)
Linux只用RTC来获取时间和日期,内核通过0x70和Ox71 I/O端口访问RTC。系统管理员通过执行Unix系统时钟程序(直接作用于这两个I/O端口)可以设置时钟。MC146818 RTC芯片(或其他兼容芯片,如DS12887)可以在IRQ8上产生周期性的中断,中断的频率在2HZ~8192HZ之间。与MC146818 RTC对应的设备驱动程序实现在include/linux/mc146818rtc.h和drivers/char/mc146818rtc.c文件中,而对应的设备文件是/dev/mc146818rtc(major=10,minor=135,只读字符设备)。因此用户进程可以通过对她进行编程以使得当RTC到达某个特定的时间值时激活IRQ8线,从而将RTC当作一个闹钟来用。
所以,宏RTC_SECONDS来自文件include/linux/mc146818rtc.h
#define RTC_SECONDS 0
#define CMOS_READ(addr) rtc_cmos_read(addr)
#define RTC_PORT(x) (0x70 + (x))
unsigned char rtc_cmos_read(unsigned char addr)
{
unsigned char val;
lock_cmos_prefix(addr);
outb(addr, RTC_PORT(0));
val = inb(RTC_PORT(1));
lock_cmos_suffix(addr);
return val;
}