linux时钟及定时器

来源:互联网 发布:c#高级编程 豆瓣 编辑:程序博客网 时间:2024/05/11 18:40

1、  introduce

时间管理在内核中占有及其重要的地位,内核中大量的函数都是基于时间驱动的,比如有的函数是周期执行的,有的函数要等待一段时间后才执行。此外系统还要管理系统的运行时间以及当前的日期和时间。

周期性产生的时间都是由系统定时器驱动的,系统定时器能以固定频率产生中断----定时器中断,其对应的中断处理程序负责更新系统时间和执行周期任务。

系统时钟是定时器硬件和系统软件的结合,系统时钟硬件在通过编程配置后产生一定频率的中断,与该中断相关的中断向量号是0.系统软件通过累计从开机到现在产生该中断的次数来维护系统时间形成系统时间。

常见的硬件定时器有:

·8254可编程定时器,该芯片有一个119318Hz的振荡器驱动,含有3个独立的通道,每个通道有一个16位计数器。对于每一个到达的时钟脉冲,计数器值减1,当计数器减到0时,相应通道产生一次输出。其中0通道链接到中断控制器,产生系统时钟所需滴答。

·高精度事件定时器(HPET)用于取代8254RTC,能产生更高精度的时钟中断。

·时间戳计数器TSC:所有intel处理器都包含一个64为寄存器,在cpu的每个时钟信号到来时加1,是一个不断增加的计数器。其计数间隔为1/主频。利用TSC可以得到更为精准的时间度量

jiffies用于记录系统自启动到当前时刻为止系统时钟产生的滴答数,在系统响应时钟中断、进行中断处理时,timer_interrupt()jiffies1.

2、  内核定时器

2.1内核定时器的使用

2.1.1内核定时器由数据结构timer_list表示,该结构表示了一个待处理的延迟任务称为内核定时器节点。<src/include/linux/timer.h>

struct timer_list{

    struct list_head entry;

    unsigned long expires;

    void (*function)(unsigned long);

    unsigned long data;

    struct timer_base_s *base;

    }

·entry将内核定时器节点链接到系统中的定时器链表中

·expires定时器的超时时间

·function定时器超时处理函数

·base该定时器节点属于那个处理器,在使用init_timer()初始化时指向一个每处理器变量tvec_bases的成员变量t_base

2.1.2使用内核定时器节点

·声明一个内核定时器数据结构:struct timer_list my_timer;

·初始化:init_timer(&my_timer);

·设置定时器参数:my_timer.expire=jiffies+delay;

                  my_timer.data=0;

                  my_timer.function=my_function;

·激活定时器:add_timer(&my_timer);

2.2内核定时器结构

每一个内核定时器节点与系统中的处理器通过一个每处理器变量联系起来,内核在<src/kernel/timer.c>中使用static DEFINE_PER_CPU(tvec_base_t,tvec_bases);分配一个名为tvec_bases类型为tvec_t_base_s的每处理器变量。

DEFINE_PER_CPU 这个宏就是为每个CPU分配一个数据结构, 比如在runqueue这种情况, 为每个CPU建立一个运行队列,这样就可以更好地管理每个CPU上面的进程.宏的第一个参数是一个数据类型, 第二个参数是自己定义的一个名字.

·typedef tvec_t_base_s tvec_base_t

·tvec_t_base_s组织当前处理器上所有待处理内核定时器节点。

·timer_jiffies记录了该数据结构锁包含定时器最早超时时间,根据该变量可以计算出超时定时器所在链表的表头

·running_timer记录了正在本地处理器上进行超时处理的内核定时器

·vec[i]list_head结构,将超时时刻相同的定时器链接成双向链表

Linux 的内核把定时器分为 5 组,每组的粒度分别表示为:1 jiffies256 jiffies256*64 jiffies256*64*64 jiffies256*64*64*64 jiffies,每组中表头数量分别为:25664646464,这样,在 256+64+64+64+64 = 512 个表头中,表示的范围达到 2^32

3、  初始化

3.1内核定时器基础结构的初始化

内核定时器的基础结构的初始化过程由init_timer()函数处理。该函数在系统初始化时被系统初始化主函数start_kernel()<src/init/main.c>调用。

·init_timers()<src/kernel/timer.c>负责完成内核定时器所需数据结构的初始化工作,并完成对应的softirq处理函数的设置。首先调用timer_cpu_notify()初始化每处理器变量的本地处理器部分;随后调用open_softirq()向系统注册时钟中断处理程序基于softirq的下半部。

·timer_cpu_notify:<src/kernel/timer.c>函数调用init_timer_cpu()初始化内核定时器架构的核心变量----tvec_base的本地拷贝,初始化待处理内核定时器节点为NULL.

·init_timers_cpu():<src/kernel/timer.c>初始化每处理器变量tvec_bases的第cpu个处理器对应的拷贝并初始化其上待处理定时器为NULL

3.2系统时钟初始化

系统时钟由time_init()初始化,同样他也是在系统初始化时被start_kernel()调用。

·time_init():<src/i386/kernel/time.c>初始化系统墙上时间ximte、为系统选择合适的时钟中断源并为其设置时钟中断处理函数。函数 调用get_cmos_time()读取RTC记录的时间,将该时间赋值到成员变量tv_sec中,然后调用set_normalized_timespec()对变量wall_to_monotonic初始化。随后调用函数select_timer()为系统时钟选择合适的时钟源,将代表该时钟源的结构赋值到指针变量cur_timer中。最后time_init_hook()函数完成设置系统时钟中断处理策划那个徐。

·select_timer():<src/srch/i386/kernel/timers/timer.c>首先从时钟源数组timer[]中选择一个合适的时钟源,然后返回该时钟源控制结构。

·time_init_hook()<src/arch/i386/mach-default/setup.c>直接调用setup_irq(0,&irq0)为时钟中断设置中断处理函数,其中断向量号为0,中断处理的数据结构为static irqaction irq0={timer_interrupt,SA_INTERRUPT,CPU_MASK_NONE, “timer”,NULL,NULL}

3.3初始化时钟中断源由函数setup_pit_timer()处理,该函数在系统中断描述符表初始化过程中被函数init_IRQ()调用。

·setup_pit_timer()<src/arch/i386/kernel/timers/timer_pit.c>负责对8254可编程定时器进行初始化,设置定时器工作模式、中断频率等。

4、  时钟中断处理过程

4.1时钟中断是分时操作系统的核心和基础,当系统时钟中断源产生中断请求、系统响应中断请求并进行中断处理时,会对系统时钟进行维护及其他处理。

·timer_interrupt():<src/arch/i386/kernel/time.c>是时钟中断处理的入口函数。该函数首先调用当前系统时钟源cur_timer的成员函数mark_offset_tsc()获得高精度时钟源在上次与此次时钟中断锁跨国的时间,然后计算该段时间内是否有时钟中断丢失,并计算丢失的时钟中断次数补到jiffies_64.然后调用do_timer_interrupt()进行处理。

·do_timer_interrupt_hook()<src/include/asm-i386/mach-default/do_timer.h>,函数do_timer_interrupt()调用do_timer_interrupt_hook()进行具体的处理。本函数先调用do_timer()进行具体的时钟中断处理。然后updata_process_times()对当前被中断进程进行记账。

·do_timer()<src/kernel/timer.c>更新jiffies_64将其值加1,然后调用updata_times对墙上时间进行更新。

·updata_process_times():<src/kernel/timer.c>根据被中断进程是处于用户态还是系统态分别调用account_user_time()account_system_time()对被中断进程进行记账。然后调用函数run_local_timer()设置时钟中断处理的下半部标记、激活时钟中断的下半部,该下半部负责维护、更新内核定时器链表,对超时定时器执行超时处理函数,并将超时定时器移除链表。最后调用scheduler_tick()对进程的时间片进行维护,并根据情况设置调度标记,在中断处理完毕、中断返回时系统会检查该标记,当标记被设置时调用scheduler().

5、  内核定时器的使用

5.1内核定时器的使用从内核定时器节点的出示化开始

·init_timer()<src/kernel/timer.c>先将定时器节点成员变量entry.next=NULL,然后将base指向每处理器变量tvec_bases的本地拷贝,从而将次内核定时器timer与当前处理器关联起来。

·add_timer ():<src/include/linux/timer.h>调用_mod_timer()将定时器节点插入到当前处理器上待处理定时器节点链表。

·_mod_timer()<src/kernel/yimer.c>在内核定时器没有被激活的情况下将内核定时器添加到当前处理器待处理定时器链表中,在已激活的情况下将其移动到当前处理器待处理定时器链表上。其中调用函数timer_pending()判断定时器是否以激活;在已激活的情况下调用detach_timer()将该定时器从内核定时器从待处理链表上移除,并设置ret=1标志将定时器以新参数重新激活。最后internal_add_timer()将内核定时器添加到new_base对应的处理器的内核定时器连表上。

·inter_add_timer()<src/kernel/timer.c>将定时器插入到每处理器变量tvec_base的本地拷贝base中,更新并维护该数据结构。

5.2内核定时器的处理过程

在系统时钟中断处理过程中会设置下半部标记,该下半部负责对内核定时器链表中的定时器进行处理

·run_timer_softirq()<src/kernel/timer.c>:该函数是时钟中断下半部的入口函数,负责判断当前处理器上是否有超时定时器,并对超时定时器进行处理。函数使用宏time_after_eq判断是否有超时的定时器,如果有则调用_run_timers()

·_run_timers()<src/kernel/timer.c>:函数使用宏timer_afte_eq作为循环条件,在当前处理器还有未处理的超时定时器时,首先根据base->timer_jiffies计算出内核定时器超时链表在数组vec[TV_SIZE]中的下标,并用该下标初始化index,若tv1所包含256个内核定时器链表都已经超时且已经进行了处理,则将tv2~tv5内核定时器依次移动到tv1中来,这个过程由cascade()函数完成。随后调用list_splice_init()将超时内核定时器链表从超时链表表头摘下来,依次调用相关的处理函数进行超时处理。处理完毕后,调用函数set_running_timer()设置当前处理器上正在处理的内核定时器为NULL

·cascade()<src/kernel/timer.c>该函数将粗粒度的内核定时器链表细化插入到更细粒度的内核处理器链表中(tv5->tv4->tv3->tv2->tv1)。

原创粉丝点击