Linux内核定时器--原版PPT优秀课件
- 格式:pptx
- 大小:368.26 KB
- 文档页数:91
Linux内核定时器详解80X86体系结构上,常用的定时器电路实时时钟(RTC)RTC内核通过IRQ8上发出周期性的中断,频率在2-8192HZ之间,掉电后依然工作,内核通过访问0x70和0x71 I/O端口访问RTC。
时间戳计时器(TSC)利用CLK输入引线,接收外部振荡器的时钟信号,该计算器是利用64位的时间戳计时器寄存器来实现额,与可编程间隔定时器传递来的时间测量相比,更为精确。
可编程间隔定时器(PIT)PIT的作用类似于微波炉的闹钟,PIT永远以内核确定的固定频率发出中断,但频率不算高。
CPU本地定时器利用PIC或者APIC总线的时钟计算。
高精度时间定时器(HPET)功能比较强大,家机很少用,也不去记了。
ACPI电源管理定时器它的时钟信号拥有大约为3.58MHZ的固定频率,该设备实际上是一个简单的计数器,为了读取计算器的值,内核需要访问某个I/O端口,需要初始化定时器的数据结构利用timer_opts描述定时器Timer_opts的数据结构Name :标志定时器员的一个字符串Mark_offset :记录上一个节拍开始所经过的时间,由时钟中断处理程序调用Get_offset 返回自上一个节拍开始所经过的时间Monotonic_clock :返回自内核初始化开始所经过的纳秒数Delay:等待制定数目的“循环”定时插补就好像我们要为1小时35分34秒进行定时,我们不可能用秒表去统计,肯定先使用计算时的表,再用计算分的,最后才用秒表,在80x86架构的定时器也会使用各种定时器去进行定时插补,我们可以通过cur_timer指针来实现。
单处理器系统上的计时体系结构所有与定时有关的活动都是由IRQ线0上的可编程间隔定时器的中断触发。
初始化阶段1. 初始化间,time_init()函数被调用来建立计时体系结构2. 初始化xtime变量(xtime变量存放当前时间和日期,它是一个timespec 类型的数据结构)3. 初始化wall_to_monotonic变量,它跟xtime是同一类型的,但它存放将加在xtime上的描述和纳秒数,这样即使突发改变xtime也不会受到影响。
第15章内核时钟与定时器实验目的●学习Linux系统中的时钟和定时器原理●分析理解Linux内核时间的实现机制●分析比较ITIMER_REAL、ITIMER_VIRTUAL、ITIMER_PROF●学习理解内核中各种定时器的实现机制●掌握操作定时器的命令,掌握定时器的使用实验内容针对一个计算fibonacci数的进程,设定三个定时器,获取该进程在用户模式的运行时间,在内核模式的运行时间,以及总的运行时间。
提示:setitimer()/getitimer()系统调用的使用。
ITIMER_REAL实时计数;ITIMER_VIRTUAL统计进程在用户模式(进程本身执行)执行的时间;ITIMER_PROF统计进程在用户模式(进程本身执行)和内核模式(系统代表进程执行)下的执行时间,与ITIMER_VIRTUAL比较,这个计时器记录的时间多了该进程内核模式执行过程中消耗的时间。
实验原理15.1 关于时钟和定时器一台装有操作系统的计算机里一般有两个时钟:硬件时钟和软件时钟。
硬件时钟从根本上讲是CMOS时钟,是由小型电池供电的时钟,这种电池一般可持续供电三年左右。
因为有自己的电池,所以当计算机断电的时候CMOS时钟可以继续运行,这就是为什么你的计算机总是知道正确的日期和时间的原因。
而软件时钟则是由操作系统本身维护的,所以又称系统时钟。
这个时钟是在系统启动时,读取硬件时钟获得的,而不是靠记忆计时。
在得到硬件时钟之后,就完全由系统本身维护。
之所以使用两套时钟的原因是因为硬件时钟的读取太麻烦,所消耗的时间太长。
硬件时钟的主要作用就是提供计时和产生精确时钟中断。
而软件时钟的作用则可以归纳为下面的几条:●保存正确时间。
●防止进程超额使用CPU。
●记录CPU的使用情况。
●处理用户进程发出的系统调用。
二、作用:一个timer_list结构体的实例对应一个定时器,在linux设备驱动编程中,可以使用timer_list和基于它的一些操作(函数)来完成定时出发工作或者完成某周期性的事物。
三、个字段详解:1、struct list_head entry--定时器链表,用于存放软定时器,该链表根据定时器expirex 字段的值将它们分组存放。
2、unsigned long expires--定时器的到期时间,到达expires时间后,定时器将调用其成员函数function,其中将data字段作为function的参数。
该字段表示的时间是以时间节拍为单位。
例如如果你想定时一秒,则expires=jiffies+HZ*1。
A、关于jiffies的详解见我们知道,操作系统应该能够在将来某个时刻准时调度某个任务,所以需要一种能保证任务准时调度运行的机制。
希望支持每种操作系统的微处理器必须包含一个可周期性中断它的可编程间隔定时器。
这个周期性中断被称为系统时钟滴答(或system timer),它象节拍器一样来组织系统任务,也称为时钟中断。
时钟中断的发生频率设定为HZ,HZ是一个与体系结构无关的常数,在文件<linux/param.h>中定义,时钟中断对操作系统是非常重要的,当时钟中断发生时,将周期性地执行一些功能,例如:. 更新系统的uptime. 更新time of day. 检查当前任务的时间片是否用光,如果是则需要重新调度任务. 执行到期的dynamic timer. 更新系统资源使用统计和任务所用的时间统计3、void (*function)(unsigned long)--定时器处理函数,也就是说到达expires时间时,function函数将被调用执行。
其参数来自定时器的data字段。
4、unsigned long data--在调用function函数时,该字段作为其参数被使用。
四、操作:1、定义及初始化:struct timer_list timer;(1)-- void init_timer(struct timer_list *timer){debug_timer_init(timer);__init_timer(timer);}(4)--setup_timer(&timer);等同于定义方式(2)和(3),不过对base字段的赋值是调用了init_timer()函数。
定时器timer说明:/******************* linux内核的时间管理******************/(1)内核中的时间概念时间管理在linux内核中占有非常重要的作用。
相对于事件驱动而言,内核中有大量函数是基于时间驱动的。
有些函数是周期执行的,比如每10毫秒刷新一次屏幕;有些函数是推后一定时间执行的,比如内核在500毫秒后执行某项任务。
要区分:*绝对时间和相对时间*周期性产生的事件和推迟执行的事件周期性事件是由系统系统定时器驱动的(2)HZ值内核必须在硬件定时器的帮助下才能计算和管理时间。
定时器产生中断的频率称为节拍率(tick rate)。
在内核中指定了一个变量HZ,内核初始化的时候会根据这个值确定定时器的节拍率。
HZ定义在<asm/param.h>,在i386平台上,目前采用的HZ值是1000。
也就是时钟中断每秒发生1000次,周期为1毫秒。
即:#define HZ 1000注意!HZ不是个固定不变的值,它是可以更改的,可以在内核源代码配置的时候输入。
不同的体系结构其HZ值是不一样的,比如arm就采用100。
如果在驱动中要使用系统的中断频率,直接使用HZ,而不要用100或1000。
a.理想的HZ值i386的HZ值一直采用100,直到2.5版后才改为1000。
提高节拍率意味着时钟中断产生的更加频繁,中断处理程序也会更频繁地执行。
带来的好处有:*内核定时器能够以更高的频率和更高的准确度运行*依赖定时器执行的系统调用,比如poll()和select(),运行的精度更高*提高进程抢占的准确度(缩短了调度延时,如果进程还剩2ms时间片,在10ms的调度周期下,进程会多运行8ms。
由于耽误了抢占,对于一些对时间要求严格的任务会产生影响)坏处有:*节拍率越高,系统负担越重。
中断处理程序将占用更多的处理器时间。
(3)jiffies全局变量jiffies用于记录系统启动以来产生的节拍的总数。
linux内核定时器:
时钟中断: 有系统定时的硬件以周期性的时间间隔产生
比如HZ=1000,即1s中产生1000次中断,一次1/1000 s
每当时钟中断产生一次,jiffise (ulong)就加一,驱动程序根据jiffise值计算时间及间隔关机之后jiffise清零
一个延时程序:
ulong j=jiffise+jit_delay *HZ; //延时jit_delay秒
while(jiffse<j)
内核定时器用于控制某个函数在未来的某个特定的时刻执行, 特点,函数执行一次,就是一个单闹钟
内核定时器:内核定时器被组织成双向链表,使用一个结构体描述
struct timer_list {
struct list_head entry; //内核使用
ulong expires; //超时的jiffise值void (*funct)(ulong); //超时处理函数
ulong data; //超时处理函数的参数struct tvec_base *base; //内核使用
}
内核定时器:
void init_timer(struct timer_list *timer) //初始化定时器队列结构启动定时器:
void add_timer(struct timer_list *timer)
删除定时器, 如果超时之后,系统会自动删除定时器
int del_timer(struct timer_list *timer)
实例:。
内核定时器#include <linux/module.h>#include <linux/version.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/sched.h>//这两个头文件与内核定时器的使用有关;#include <linux/timer.h>#include <linux/jiffies.h>MODULE_LICENSE("GPL");MODULE_AUTHOR("**********");MODULE_VERSION("2.6.35.000");//STEP1:定义一个内核定时器对象;static struct timer_list my_timer;/*STEP6:实现定时器函数;该定时器函数是由内核调用进程swapper 来执行的,即:内核调用进程swapper,然后由进程swapper来调用并执行该定时器函数;*/static void timer_process(unsigned long param){printk("HZ = %d, jiffies = %lu, timer parameter = %lu, file:%s, line:%d, caller:[%s];\n", HZ, jiffies, param, __FILE__, __LINE__, current->comm);/*STEP7:如果需要继续定时,那就需要把定时器对象重新加入到内核动态定时器链表中;因为定时器激活并到期之后,内核只会执行一次定时器函数,执行完之后,就把当前定时器对象从内核动态定时器链表中移除了;所以,如果要继续定时,那就要重新设定超时时间(旧的超时时间已经过期)并把当前定时器对象加入到内核动态定时器链表中并重新激活;*/#if 0my_timer.expires = jiffies + HZ; //重新设置定时器到期时间(100次时钟滴答:1秒);add_timer(&my_timer); //重新激活定时器;#endifmod_timer(&my_timer, (jiffies + HZ)); //重新设置超时时间并再次激活定时器;};static int __init study_init(void){int ret = 0;printk("%s\n", __PRETTY_FUNCTION__);//STEP2:初始化定时器对象my_timer;init_timer(&my_timer);//STEP3:设置定时器对象的各个属性值;my_timer.function = timer_process; //定时器函数;my_timer.data = (unsigned long)789; //定时器函数的参数;my_timer.expires = jiffies + HZ; //定时器到期时间(100次时钟滴答:1秒);//STEP4:注册内核定时器my_timer,把定时器对象my_timer注册到内核动态定时器链表中;add_timer()返回之后,定时器对象就由内核来接管了;add_timer(&my_timer);printk("kernel timer start; file:%s, line:%d, caller[%s]\n", __FILE__, __LINE__, current->comm);return ret;}static void __exit study_exit(void){printk("%s\n",__PRETTY_FUNCTION__);//STEP5:当定时器对象不需要时,就把定时器对象从内核动态定时器链表中删除;del_timer(&my_timer);printk("kernel timer stop;file:%s;line:%d\n", __FILE__, __LINE__);}module_init(study_init);module_exit(study_exit);定时器对象的定义:struct timer_list{struct list_head entry;unsigned long expires;struct tvec_base* base;void (*function)(unsigned long);unsigned long data;int slack;#ifdef CONFIG_TIMER_STATSvoid *start_site;char start_comm[16];int start_pid;#endif#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;#endif};其中:entry:双向链表元素,用于把多个定时器对象链接成一个双向循环队列;expires:定时器到期时间,单位是时钟滴答次数;这个时间被表示成自系统启动以来所走过的时钟滴答数(也即:时钟节拍或时钟中断数);当一个定时器的值expires值小于或等于jiffies的当前值时,我们就说这个定时器已经超时或到期了;在初始化一个定时器对象之后,通常把它的expires值设置成当前jiffies值加上某一个时间间隔值(以时钟滴答次数计算);内核会把该值与系统中的jiffies值进行比较;function:定时器处理函数;当定时器到期时,内核就会自动执行function所指定的函数;data:被传递给定时器函数function()的参数;通常,内核定时器对象被链接到一个双向循环队列中等待运行,此时我们就说该定时器处于pending状态,因此,函数timer_pending()就可以通过判断entry成员是否为空来判断一个定时器是否处于pending 状态;注意:1、定时器对象被注册到内核动态定时器链表中并激活之后,在定时时间到期时,定时器函数只会被内核调度执行一次,定时器函数执行结束之后,这个到期的定时器对象就会被内核从内核动态定时器链表中移除;因此,如果要实现间隔指定时间并重复定时的话,那就要重新设置超时时间(旧的超时时间已经过期)并把这个定时器对象重新注册到内核动态定时器链表中;2、当执行定时器函数时,当前进程是swapper,而不是加载模块时的insmod,也就是说,当调用add_timer()之后,系统就把定时器对象交由内核来管理了,当超时时间一到,内核就会调用进程swapper来执行定时器函数;也就是说,当超时时间到达之后,内核调用进程swapper,然后由进程swapper来调用并执行定时器函数;。