linux定时器和Jiffies
- 格式:doc
- 大小:45.50 KB
- 文档页数:9
HZ和JiffiesHZ和Jiffies2011-06-04 15:17:49分类: C/C++2.4 内核定时器内核中许多部分的工作都高度依赖于时间信息。
Linux内核利用硬件提供的不同的定时器以支持忙等待或睡眠等待等时间相关的服务。
忙等待时,CPU 会不断运转。
但是睡眠等待时,进程将放弃CPU。
因此,只有在后者不可行的情况下,才考虑使用前者。
内核也提供了某些便利,可以在特定的时间之后调度某函数运行。
我们首先来讨论一些重要的内核定时器变量(jiffies、HZ和xtime)的含义。
接下来,我们会使用Pentium时间戳计数器(TSC)测量基于Pentium的系统的运行次数。
之后,我们也分析一下Linux 怎么使用实时钟(RTC)。
2.4.1 HZ和Jiffies系统定时器能以可编程的频率中断处理器。
此频率即为每秒的定时器节拍数,对应着内核变量HZ。
选择合适的HZ值需要权衡。
HZ 值大,定时器间隔时间就小,因此进程调度的准确性会更高。
但是,HZ值越大也会导致开销和电源消耗更多,因为更多的处理器周期将被耗费在定时器中断上下文中。
HZ的值取决于体系架构。
在x86系统上,在2.4内核中,该值默认设置为100;在2.6内核中,该值变为1000;而在2.6.13中,它又被降低到了250。
在基于ARM的平台上,2.6内核将HZ设置为100。
在目前的内核中,可以在编译内核时通过配置菜单选择一个HZ值。
该选项的默认值取决于体系架构的版本。
2.6.21内核支持无节拍的内核(CONFIG_NO_HZ),它会根据系统的负载动态触发定时器中断。
无节拍系统的实现超出了本章的讨论范围,不再详述。
jiffies变量记录了系统启动以来,系统定时器已经触发的次数。
内核每秒钟将jiffies变量增加HZ次。
因此,对于HZ值为100的系统,1个jiffy等于10ms,而对于HZ为1000的系统,1个jiffy仅为1ms。
为了更好地理解HZ和jiffies变量,请看下面的取自IDE驱动程序(drivers/ide/ide.c)的代码片段。
你需要了解Linux设备驱动之定时与延时的区别Linux通过系统硬件定时器以规律的间隔(由HZ度量)产生定时器中断,每次中断使得一个内核计数器的值jiffies累加,因此这个jiffies就记录了系统启动开始的时间流逝,然后内核据此实现软件定时器和延时。
Demo for jiffies and HZ#include unsigned long j, stamp_1, stamp_half, stamp_n; j = jiffies; /* read the current value */ stamp_1 = j + HZ; /* 1 second in the future */ stamp_half = j + HZ/2; /* half a second */ stamp_n = j + n * HZ / 1000; /* n milliseconds */内核定时器硬件时钟中断处理程序会唤起TIMER_SOFTIRQ 软中断,运行当前处理器上到期的所有内核定时器。
定时器定义/初始化在Linux内核中,TImer_list结构体的一个实例对应一个定时器:/* 当expires指定的定时器到期时间期满后,将执行funcTIon(data) */ struct TImer_list { unsigned long expires; /*定时器到期时间*/ void (*function)(unsigned long); /* 定时器处理函数*/ unsigned long data; /* function的参数*/ ... }; /* 定义*/ struct timer_list my_timer; /* 初始化函数*/ void init_timer(struct timer_list * timer); /* 初始化宏*/ TIMER_INITIALIZER(_function, _expires, _data) /* 定义并初始化宏*/ DEFINE_TIMER(_name, _function, _expires, _data)定时器添加/移除/* 注册内核定时器,将定时器加入到内核动态定时器链表中*/ void add_timer(struct timer_list * timer); /* del_timer_sync()是del_timer()的同步版,在删除一个定时器时需等待其被处理完,因此该函数的调用不能发生在中断上下文*/ void del_timer(struct timer_list * timer); void del_timer_sync(struct timer_list * timer);定时时间修改int mod_timer(struct timer_list *timer, unsigned long expires);。
linux中定时器的使用方法Linux是一个功能强大的操作系统,其中提供了许多工具来帮助用户管理和计划任务。
其中一个重要的工具是定时器,它可以在指定的时间间隔内执行某些操作。
本文将介绍Linux中定时器的使用方法。
1. 了解定时器的基本概念在Linux中,定时器是一种可重复执行的指令。
它们被设置在特定的时间段内,并在该时间段内自动执行。
定时器可以执行任何命令,如运行程序、创建文件、编辑文件、重启服务等。
2. 创建定时器要创建定时器,可以使用定时器脚本。
定时器脚本是一个简单的文件,包含定时器的指令和设置。
例如,可以使用以下命令来创建一个名为“crontab”的定时器脚本:```crontab -e```这将打开一个新的编辑器窗口,其中包含一个名为“crontab”的选项。
在这个窗口中,可以添加、编辑和删除定时器。
3. 编辑定时器要编辑定时器,需要使用“crontab”命令。
例如,可以使用以下命令来编辑一个已经存在的定时器:```crontab -e```在编辑定时器时,可以选择要使用的定时器、设置时间和日期,以及要自动执行的指令。
例如,要创建一个在每天下午3点定时执行“ls -l”命令的定时器,可以使用以下命令:```*/3 * * * * ls -l```这将在每天的下午3点自动执行“ls -l”命令。
请注意,“*/3 * * * *”是一个固定的指令,将在每个下午3点自动执行。
4. 删除定时器要删除定时器,可以使用“crontab”命令。
例如,可以使用以下命令来删除一个已经存在的定时器:```crontab -r```这将删除当前文件中的所有定时器。
5. 了解定时器的优点和限制定时器是一种非常有用的工具,可以帮助用户在特定时间执行某些操作。
虽然定时器可以提高效率,但也存在一些限制。
首先,定时器的设置是固定的,无法更改。
这意味着,如果希望在特定时间执行不同的操作,需要使用多个定时器。
其次,定时器不会在周末或节假日期间运行。
Linux驱动技术关键之一:内核定时器与延迟工作内核定时器软件上的定时器最终要依靠硬件时钟来实现,简单的说,内核会在时钟中断发生后检测各个注册到内核的定时器是否到期,如果到期,就回调相应的注册函数,将其作为中断底半部来执行。
实际上,时钟中断处理程序会触发TIMER_SOFTIRQ软中断,运行当前处理器上到期的所有定时器。
设备驱动程序如要获得时间信息以及需要定时服务,都可以使用内核定时器。
jiffies要说内核定时器,首先就得说说内核中关于时间的一个重要的概念:jiffies变量,作为内核时钟的基础,jiffies每隔一个固定的时间就会增加1,称为增加一个节拍,这个固定间隔由定时器中断来实现,每秒中产生多少个定时器中断,由在中定义的HZ宏来确定,如此,可以通过jiffies获取一段时间,比如jiffies/HZ表示自系统启动的秒数。
下两秒就是(jiffies/HZ+2),内核中用jiffies来计时,秒转换成的jiffies:seconds*HZ,所以以jiffiy为单位,以当前时刻为基准计时2秒:(jiffies/HZ+2)*HZ=jiffies+2*HZ如果要获取当前时间,可以使用do_getTImeofday(),该函数填充一个struct TImeval结构,有着接近微妙的分辨率。
//kernel/TIme/timekeeping.c 473 /** 474 * do_gettimeofday - Returns the time of day in a timeval 475 * @tv: pointer to the timeval to be set 476 * 477 * NOTE: Users should be converted to using getnstimeofday() 478 */ 479 void do_gettimeofday(struct timeval *tv)驱动程序为了让硬件有足够的时间完成一些任务,常常需要将特定的代码延后一段时间来执行,根据延时的长短,内核开发中使用长延时和短延时两个概念。
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也不会受到影响。
Linux定时器的使用内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于<linux/timer.h> 和kernel/timer.c 文件中。
被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则:1) 没有current 指针、不允许访问用户空间。
因为没有进程上下文,相关代码和被中断的进程没有任何联系。
2) 不能执行休眠(或可能引起休眠的函数)和调度。
3) 任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。
内核定时器的调度函数运行过一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。
在SMP系统中,调度函数总是在注册它的同一CPU上运行,以尽可能获得缓存的局域性。
内核定时器的数据结构struct timer_list {struct list_head entry;unsigned long expires;void (*function)(unsigned long);unsigned long data;struct tvec_base *base;/* ... */};其中expires 字段表示期望定时器执行的jiffies 值,到达该jiffies 值时,将调用function 函数,并传递data 作为参数。
当一个定时器被注册到内核之后,entry 字段用来连接该定时器到一个内核链表中。
base 字段是内核内部实现所用的。
需要注意的是expires 的值是32位的,因为内核定时器并不适用于长的未来时间点。
初始化在使用struct timer_list 之前,需要初始化该数据结构,确保所有的字段都被正确地设置。
初始化有两种方法。
方法一:DEFINE_TIMER(timer_name, function_name, expires_value, data);该宏会定义一个名叫timer_name 内核定时器,并初始化其function, expires, name 和base 字段。
Linux时间子系统之一:定时器的应用我们知道低分辨率定时器和高精度定时器的实现原理,内核为了方便其它子系统,在时间子系统中提供了一些用于延时或调度的API,例如msleep,hrtimer_nanosleep 等等,这些API基于低分辨率定时器或高精度定时器来实现,本章的内容就是讨论这些方便、好用的API是如何利用定时器系统来完成所需的功能的。
1. msleepmsleep相信大家都用过,它可能是内核用使用最广泛的延时函数之一,它会使当前进程被调度并让出cpu一段时间,因为这一特性,它不能用于中断上下文,只能用于进程上下文中。
要想在中断上下文中使用延时函数,请使用会阻塞cpu的无调度版本mdelay。
msleep 的函数原型如下:[cpp] view plain copyvoid msleep(unsigned int msecs)延时的时间由参数msecs指定,单位是毫秒,事实上,msleep的实现基于低分辨率定时器,所以msleep的实际精度只能也是1/HZ级别。
内核还提供了另一个比较类似的延时函数msleep_interrupTIble:[cpp] view plain copyunsigned long msleep_interrupTIble(unsigned int msecs)延时的单位同样毫秒数,它们的区别如下:函数延时单位返回值是否可被信号中断msleep毫秒无否msleep_interrupTIble毫秒未完成的毫秒数是最主要的区别就是msleep会保证所需的延时一定会被执行完,而msleep_interrupTIble则可以在延时进行到一半时被信号打断而退出延时,剩余的延时数则通过返回值返回。
两个函数最终的代码都会到达schedule_timeout函数,它们的调用序列如下图所示:图1.1 两个延时函数的调用序列下面我们看看schedule_timeout函数的实现,函数首先处理两种特殊情况,一种是传入的。
定时测量timing measurement内核显式实时时钟RTC 时间标记计数器TStampC 内核跟踪当前时间可编程间隔定时器(PIntervalT)内核编程可发固定频率中断周期性中断RTC:独立于CPU和所有芯片CMOS RAM RTC集成在一个芯片。
在IRQ8发周期性中断,2hz-8192hz,可编程达特定值激活IRQ8总线(闹钟)、dev/rtc内核0x70 0x71I/O端口存取RTC /sbin/clockTSC 寄存器,汇编指令rdtsc读,时钟节拍频率400MHZ 2.5ns+1 b*s=1比PIT精确,系统初始化确定时钟信号频率,calibrate_tsc()算出PIT 发timer interrupt通知内核。
内核检查正在运行的进程是否该被抢占。
短节拍好,但内核态耗时大定时中断处理程序:更新启动后时间(PIT)TIMER.BH TQUEUE_BH: 更新时间日期确定当前进程CPU运行时间,超分配则抢占,更新资源使用统计数检查每个软定时器时间间隔已到?调用函数时间保持函数timekeeping:保持当前最新时间2计算当前秒内的微妙数。
有TSC,变量指向使用TSC的函数。
do_gettimeofday()计算,do_fast_gettimeoffset()微秒数else do_normal_gettime()do_get_fast_time变量存放的指针指向合适函数do_slow_gettimeoffset()time_init()将变量指向正确函数,设置IRQ0对应中断门CPU有TSC:1 执行rdtsc,存在last_tsc_low2读8254芯片内部振荡器状态,delay_at_last_interrupt=计算定时中断发生和中断服务例程执行间延迟调用do_timer_interrupt() 1 调用do_timer() 关中断运行更新jiffies:启动以来的节拍数。
内核初始化=0,中断+1lost_ticks:xtime(当前时间近似值)最后更新以来的节拍数lost_ticks_system:。
1. Linux下有两类时钟:1.1 实时钟RTC它由板上电池驱动的“Real Time Clock”也叫做RTC或者叫CMOS时钟,硬件时钟。
当操作系统关机的时候,用这个来记录时间,但是对于运行的系统是不用这个时间的。
1.2 系统时钟“System clock”也叫内核时钟或者软件时钟,是由软件根据时间中断来进行计数的,内核时钟在系统关机的情况下是不存在的,所以,当操作系统启动的时候,内核时钟是要读取RTC时间来进行时间同步.2. 标准计时器2.1 时钟滴答计时(jiffies)的几个基本参数2.1.1 时钟周期(clock cycle)的频率-晶振频率计时器Timer晶体振荡器在1秒内产生的时钟脉冲个数就是时钟周期的频率, 要注意把这个Timer的时钟周期频率与时钟中断的频率区别开来, Linux用宏CLOCK_TICK_RATE来表示计时器的输入时钟脉冲的频率(比如我的为#define CLOCK_TICK_RATE 1193180),该宏定义在arm/mach-xxx/include/mach/timex.h头文件中。
2.1.2 时钟中断(clock tick)我们知道当计数器减到0值时,它就在IRQ0上产生一次时钟中断,也即一次时钟中断, 计数器的初始值决定了要过多少时钟周期才产生一次时钟中断,因此也就决定了一次时钟滴答的时间间隔长度.2.1.3 时钟中断的频率(HZ)即1秒时间内Timer所产生的时钟中断次数。
确定了时钟中断的频率值后也就可以确定Timer的计数器初值。
Linux内核用宏HZ来表示时钟中断的频率,而且在不同的平台上HZ有不同的定义值。
对于SPARC、MIPS、ARM和i386等平台HZ的值都是100。
该宏在ARM平台上的定义如下(/arch/arm/include/asm/param.h)2.1.4 计数器的初始值计数器的初始值由宏LATCH定义在文件:include/linux/jiffies.h#define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ) /* For divider */ 2.1.5 jiffies在Linux 内核中,时间由一个名为jiffies 的全局变量衡量,该变量标识系统启动以来经过的滴答数。
jiffies函数jiffies函数是Linux内核中用于时间计量的函数,它以系统时钟节拍计数器的值作为基准,计算出经过的时间。
该函数在Linux内核源代码中广泛使用,是内核开发者进行系统性能分析和调试的重要工具之一。
一、函数定义在Linux内核源代码中,jiffies函数的定义通常在kernel/time.c文件中。
其函数原型如下:```cunsigned long jiffies_to_timeval(unsigned long jiffies)```该函数将系统时钟节拍计数器的值转换为对应的timeval结构体,用于描述经过的时间。
二、函数使用jiffies函数的使用方法非常简单,只需要将经过的时间(以毫秒为单位)传递给jiffies_to_ms()函数,即可获取对应的jiffies值。
例如:```cunsigned long jiffies_passed = jiffies_to_ms(current->timer_jiffies);```其中,current->timer_jiffies表示当前进程已经经过的时钟节拍数。
三、函数原理jiffies函数的原理是基于系统时钟节拍计数器(jiffies)的值来进行时间计量的。
在Linux内核中,时钟节拍计数器是一个全局变量,用于记录系统时钟的节拍数。
每当系统时钟更新时,时钟节拍计数器就会自动递增。
因此,jiffies函数可以用来计算进程、系统调用、中断等事件之间的时间间隔。
四、函数优缺点优点:jiffies函数简单易用,是Linux内核中广泛使用的计时函数之一。
它能够准确地测量时间间隔,适用于系统性能分析和调试。
缺点:由于jiffies函数的计数是基于系统时钟节拍计数器的值,因此受到系统时钟的影响较大。
如果系统时钟不稳定或出现异常,jiffies函数的精度和准确性就会受到影响。
此外,由于jiffies函数是以全局变量的形式存储在内核代码中,因此需要小心使用,避免与其他变量产生冲突或破坏内核数据结构。
linux驱动之内核定时器驱动设计我的环境:Fedora 14 内核版本为2.6.38.1开发板:ARM9 TQ2440移植内核版本:linux-2.6.30.4定时器在linux内核中主要是采用一个结构体实现的。
但是需要注意定时器是一个只运行一次的对象,也就是当一个定时器结束以后,还需要重现添加定时器。
但是可以采用mod_timer()函数动态的改变定时器到达时间。
这个驱动主要实现内核定时器的基本操作。
内核定时器主要是是通过下面的结构体struct timer_list实现。
需要的头文件包括#include;,但是在实际开发过程中不需要包含该头文件,因为在sched.h中包含了该头文件。
struct timer_list {struct list_head entry;unsigned long expires;void (*function)(unsigned long);unsigned long data;struct tvec_base *base;#ifdef CONFIG_TIMER_STATSvoid *start_site;char start_comm[16];int start_pid;#endif#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;#endif};定时器的实现主要是该结构体的填充和部分函数的配合即可完成。
其中红色的部分是最主要的几个元素,1、expires主要是用来定义定时器到期的时间,通常采用jiffies这个全局变量和HZ这个全局变量配合设置该元素的值。
比如expires = jiffies + n*HZ,其中jiffies 是自启动以来的滴答数,HZ是一秒种的滴答数。
2、function可以知道是一个函数指针,该函数就是定时器的处理函数,类似我们在中断中的中断函数,其实定时器和中断有很大的相似性。
定时器处理函数是自己定义的函数。
Linux定时器的使用内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于<linux/timer.h> 和kernel/timer.c 文件中。
被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则:1) 没有current 指针、不允许访问用户空间。
因为没有进程上下文,相关代码和被中断的进程没有任何联系。
2) 不能执行休眠(或可能引起休眠的函数)和调度。
3) 任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。
内核定时器的调度函数运行过一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。
在SMP系统中,调度函数总是在注册它的同一CPU上运行,以尽可能获得缓存的局域性。
内核定时器的数据结构struct timer_list {struct list_head entry;unsigned long expires;void (*function)(unsigned long);unsigned long data;struct tvec_base *base;/* ... */};其中expires 字段表示期望定时器执行的jiffies 值,到达该jiffies 值时,将调用function 函数,并传递data 作为参数。
当一个定时器被注册到内核之后,entry 字段用来连接该定时器到一个内核链表中。
base 字段是内核内部实现所用的。
需要注意的是expires 的值是32位的,因为内核定时器并不适用于长的未来时间点。
初始化在使用struct timer_list 之前,需要初始化该数据结构,确保所有的字段都被正确地设置。
初始化有两种方法。
方法一:DEFINE_TIMER(timer_name, function_name, expires_value, data);该宏会定义一个名叫timer_name 内核定时器,并初始化其function, expires, name 和base 字段。
linux jiffies单位
Linux Jiffies单位是什么?
在Linux系统中,Jiffies是一种时间单位,它用于测量操作
系统内核的时间。
Jiffies的长度取决于系统的时钟频率,通常是
以毫秒为单位。
在Linux内核中,Jiffies是一个32位的无符号整数,它会在特定的时间间隔内递增。
它的值会在系统启动时被初始化,然后以系统时钟频率的速度递增。
Jiffies单位在Linux系统中被广泛应用于计算时间间隔和定
时器。
它被用于实现定时器、延迟等待和性能统计等功能。
例如,
内核中的很多定时器都是以Jiffies为单位来计算的。
Jiffies单位也可以用于测量系统的负载和性能。
通过跟踪Jiffies的变化,可以了解系统的运行时间、CPU利用率和系统的响
应速度等信息。
总之,Linux Jiffies单位是Linux系统内核中用于测量时间
的一种单位,它在系统的时间管理和性能统计中扮演着重要的角色。
通过对Jiffies的使用和跟踪,可以更好地了解和优化系统的性能。
内核定时器,分级结构,定时器迁移刷新,DEFINE_TIMER,init_timer,setup_timer,add_timer,mod_timer,del_timer1 内核定时器概述Linux内核2.4版中去掉了老版本内核中的静态定时器机制,而只留下动态定时器。
动态定时器与静态定时器这二个概念是相对于Linux内核定时器机制的可扩展功能而言的,动态定时器是指内核的定时器队列是可以动态变化的,然而就定时器本身而言,二者并无本质的区别。
考虑到静态定时器机制的能力有限,因此Linux内核2.4版中完全去掉了以前的静态定时器机制。
2.6内核为了支持SMP及CPU热插拔,对定时器相关结构又做了改动。
本文所有代码基于2.6.19内核(摘自)Linux11 struct list_head entry;12 unsigned long expires;1314 void (*function)(unsigned long);15 unsigned long data;1617 struct tvec_t_base_s *base;18}; 各数据成员的含义如下:双向链表元素entry:用来将多个定时器连接成一条双向循环队列。
expires:指定定时器到期的时间,这个时间被表示成自系统启动以来的时钟滴答计数(也即时钟节拍数)。
当一个定时器的expires值小于或等于jiffies变量时,我们就说这个定时器已经超时或到期了。
在初始化一个定时器后,通常把它的expires域设置成当前expires变量的当前值加上某个时间间隔值(以时钟滴答次数计)。
函数指针function:指向一个可执行函数。
当定时器到期时,内核就执行function所指定的函数。
data域:被内核用作function函数的调用参数。
base:当前timer所属的base。
由于考虑了SMP的情况,每个CPU都含有一个base。
2 动态内核定时器的组织结构Linux是怎样为其内核定时器机制提供动态扩展能力的呢?其关键就在于“定时器向量”的概念。
Linux内核定时器的使用linux内核中定时器的使用,定时器是很重要的内容,在调试TP或者其他许多程序时都涉及到定时器的使用,因此掌握定时器的运用是必备的。
下面将介绍定时器驱动的常用函数。
对于具体的驱动后面的文档会以蜂鸣器驱动为例,并介绍框架层及应用怎样去控制蜂鸣器。
1.linux系统时间频率定义系统定时器的时钟频率HZ 定义在 arch/arm/include/asm/param.h#define Hz 100 //ARM构架基本都是1002.节拍总数(jiffies)全局变量jiffies用来记录自系统启动以来产生的节拍总数,根据这个节拍总数可以获得系统自启动以来的时间,linux系统启动时,会将jiffies初始化为0,3.访问jiffies变量jiffies总是无符号长整数,该变量定义在linux/jiffies.h文件中内核定时器使用内核定时器的步骤1. 定义内核定时器结构体变量内核定时器需要一个timer_list结构体(#include<linux/timer.h>),该结构体指定的内核定时器处理函数等struct timer_list {struct list_head entry; //定时器链表入口unsigned long expires; //以jifffies为单位的定时值(过期时间)struct tvec_base *base; // 定时器内部值,用户不要使用void (*function)(unsigned long); // 定时器处理函数unsigned long data; //传给处理函数的长整形参数值int slack; //与expires组合成新的expires,在第二部会初始化这个变量#ifdef CONFIG_TIMER_STATSint start_pid;void *start_site;char start_comm[16];#endif#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;#endif};2.初始化内核定时器(实际初始化timer_list 结构体)初始化内核定时器需要使用init_timer宏(#include<linux/timer.h>),该宏原型如下:#define init_timer(timer) init_timer_key((timer), NULL, NULL)其中timer就是timer_list的指针,init_timer主要调用了init_timer_key函数void init_timer_key(struct timer_list *timer, const char *name, struct lock_class_key *key){debug_init(timer);__init_timer(timer, name, key);}static void __init_timer(struct timer_list *timer,const char *name,struct lock_class_key *key){timer->entry.next = NULL;timer->base = __raw_get_cpu_var(tvec_bases);timer->slack = -1;#ifdef CONFIG_TIMER_STATStimer->start_site = NULL;timer->start_pid = -1;memset(timer->start_comm, 0, TASK_COMM_LEN);#endiflockdep_init_map(&timer->lockdep_map, name, key, 0);}3.实现定时器处理函数定时器处理函数原型如下:void timer_handle(unsigned long arg) //arg就是 timer_list .data的值4.对timer_list 成员变量的进一步初始化初始化function函数和expires的值,到达过期时间expires时执行function函数。
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)
实例:。
1.linux HZLinux核心几个重要跟时间有关的名词或变数,以下将介绍HZ、tick与jiffies。
HZLinux核心每隔固定周期会发出timer interrupt (IRQ 0),HZ是用来定义每一秒有几次timer interrupts。
举例来说,HZ为1000,代表每秒有1000次timer interrupts。
HZ可在编译核心时设定,如下所示(以核心版本2.6.20-15为例):adrian@adrian-desktop:~$ cd /usr/src/linuxadrian@adrian-desktop:/usr/src/linux$ make menuconfigProcessor type and features ---> Timer frequency (250 HZ) --->其中HZ可设定100、250、300或1000。
小实验观察/proc/interrupt的timer中断次数,并于一秒后再次观察其值。
理论上,两者应该相差250左右。
adrian@adrian-desktop:~$ cat /proc/interrupts | grep timer && sleep 1 && cat /proc/interrupts | grep timer0: 9309306 IO-APIC-edge timer0: 9309562 IO-APIC-edge timer上面四个栏位分别为中断号码、CPU中断次数、PIC与装置名称。
要检查系统上HZ的值是什么,就执行命令cat kernel/.config | grep '^CONFIG_HZ='2.TickTick是HZ的倒数,意即timer interrupt每发生一次中断的时间。
如HZ为250时,tick为4毫秒(millisecond)。
3.JiffiesJiffies为Linux核心变数(unsigned long),它被用来记录系统自开机以来,已经过了多少tick。
每发生一次timer interrupt,Jiffies变数会被加一。
值得注意的是,Jiffies于系统开机时,并非初始化成零,而是被设为-300*HZ (arch/i386/kernel/time.c),即代表系统于开机五分钟后,jiffies 便会溢位。
那溢位怎么办?事实上,Linux核心定义几个macro(timer_after、time_after_eq、time_before与time_before_eq),即便是溢位,也能借由这几个macro正确地取得jiffies的内容。
另外,80x86架构定义一个与jiffies相关的变数jiffies_64 ,此变数64位元,要等到此变数溢位可能要好几百万年。
因此要等到溢位这刻发生应该很难吧。
3.1 jiffies及其溢出全局变量jiffies取值为自操作系统启动以来的时钟滴答的数目,在头文件<linux/sched.h>中定义,数据类型为unsigned long volatile (32位无符号长整型)。
jiffies转换为秒可采用公式:(jiffies/HZ)计算,将秒转换为jiffies可采用公式:(seconds*HZ)计算。
当时钟中断发生时,jiffies 值就加1。
因此连续累加一年又四个多月后就会溢出(假定HZ=100,1个jiffies等于1/100秒,jiffies可记录的最大秒数为 (2^32 -1)/100=42949672.95秒,约合497天或1.38年),即当取值到达最大值时继续加1,就变为了0。
3.4 Linux内核如何来防止jiffies溢出Linux内核中提供了以下四个宏,可有效解决由于jiffies溢出而造成程序逻辑出错的情况。
下面是从Linux Kernel 2.6.7版本中摘取出来的代码:/** These inlines deal with timer wrapping correctly. You are* strongly encouraged to use them* 1. Because people otherwise forget* 2. Because if the timer wrap changes in future you won't have to * alter your driver code.** time_after(a,b) returns true if the time a is after time b.** Do this with "<0" and ">=0" to only test the sign of the result. A * good compiler would generate better code (and a really good compiler * wouldn't care). Gcc is currently neither.*/#define time_after(a,b) \(typecheck(unsigned long, a) && \typecheck(unsigned long, b) && \((long)(b) - (long)(a) < 0))#define time_before(a,b) time_after(b,a)#define time_after_eq(a,b) \(typecheck(unsigned long, a) && \typecheck(unsigned long, b) && \((long)(a) - (long)(b) >= 0))#define time_before_eq(a,b) time_after_eq(b,a)在宏time_after中,首先确保两个输入参数a和b的数据类型为unsigned long,然后才执行实际的比较。
8. 结论系统中采用jiffies来计算时间,但由于jiffies溢出可能造成时间比较的错误,因而强烈建议在编码中使用 time_after等宏来比较时间先后关系,这些宏可以放心使用。
内核时钟:内核使用硬件提供的不同时钟来提供依赖于时间的服务,如busy-waiting(浪费CPU周期)和sleep-waiting(放弃CPU)5.HZ and Jiffiesjiffies记录了系统启动后的滴答数,常用的函数:time_before()、time_after()、time_after_eq()、time_before_eq()。
因为jiffies随时钟滴答变化,不能用编译器优化它,应取volatile值。
32位jiffies变量会在50天后溢出,太小,因此内核提供变量jiffies_64来hold 64位jiffies。
该64位的低32位即为jiffies,在32位机上需要两天指令来赋值64位数据,不是原子的,因此内核提供函数get_jiffies_64()。
6.Long Delaysbusy-wait:timebefore(),使CPU忙等待;sleep-wait:shedule_timeout(截至时间);无论在内核空间还是用户空间,都没有比HZ 更精确的控制了,因为时间片都是根据滴答更新的,而且即使定义了您的进程在超过指定时间后运行,调度器也可能根据优先级选择其他进程执行。
sleep-wait():wait_event_timeout()用于在满足某个条件或超时后重新执行,msleep()睡眠指定的ms后重新进入就绪队列,这些长延迟仅适用于进程上下文,在中断上下文中不能睡眠也不能长时间busy-waiting。
内核提供了timer API来在一定时间后执行某个函数:#include <linux/timer.h>struct timer_list my_timer;init_timer(&my_timer); /* Also see setup_timer() */my_timer.expire = jiffies + n*HZ; /* n is the timeout in number of seconds */my_timer.function = timer_func; /* Function to executeafter n seconds */my_timer.data = func_parameter; /* Parameter to be passed to timer_func */add_timer(&my_timer); /*Start the timer*/如果您想周期性执行上述代码,那么把它们加入timer_func()函数。
您使用mod_timer()来改变my_timer的超时值,del_timer()来删掉my_timer,用timer_pending()查看是否my_timer处于挂起状态。
用户空间函数clock_settime()和clock_gettime()用于获取内核时钟服务。
用户应用程序使用setitimer()和getitimer()来控制alarm信号的传递当指定超时发生后。
8.Real Time ClockRTC时钟track绝对时间。
RTC电池常超过computer生存期。
可以用RTC完成以下功能:(1)读或设置绝对时钟,并在clock updates时产生中断;(2)以2HZ到8192HZ来产生周期性中断;(3)设置alarms。
jiffies仅是相对于系统启动的相对时间,如果想获取absolute time或wall time,则需要使用RTC,内核用变量xtime来记录,当系统启动时,读取RTC并记录在xtime中,当系统halt时,则将wall time写回RTC,函数do_gettimeofday()来读取wall time。
#include <linux/time.h>static struct timeval curr_time;do_gettimeofday(&curr_time);my_timestamp = cpu_to_le32(curr__sec); /* Record timestamp */ 用户空间获取wall time的函数:time()返回calendar time或从00:00:00 on January 1,1970的秒数;(2)localtime():返回calendar time in broken-down format;(3)mktime():与localtime()相反;(4)gettimeofday()以microsecond 精确度返回calendar时间。