linux定时器和Jiffies
- 格式:doc
- 大小:29.00 KB
- 文档页数:9
在模块的编写过程中,我们经常使用定时器来等待一段时间之后再来执行某一个操作。
为方便分析,写了下列一段测试程序:#include <linux/config.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/interrupt.h>#include <linux/delay.h>#include <linux/timer.h>MODULE_LICENSE("GPL");void test_timerfuc(unsigned long x){printk("Eric xiao test ......\n");}//声明一个定个器struct timer_list test_timer = TIMER_INITIALIZER(test_timerfuc, 0, 0);int kernel_test_init(){printk("test_init\n");//修改定时器到期时间。
为3个HZ。
一个HZ产生一个时钟中断mod_timer(&test_timer,jiffies+3*HZ);//把定时器加入时钟软中断处理链表add_timer(&test_timer);}int kernel_test_exit(){printk("test_exit\n");return 0;}module_init(kernel_test_init);module_exit(kernel_test_exit);上面的例子程序比较简单,我们从这个例子开始研究linux下的定时器实现。
TIMER_INITIALIZER():1):TIMER_INITIALIZER()用来声明一个定时器,它的定义如下:#define TIMER_INITIALIZER(_function, _expires, _data) { \.function = (_function), \.expires = (_expires), \.data = (_data), \.base = NULL, \.magic = TIMER_MAGIC, \.lock = SPIN_LOCK_UNLOCKED, \}Struct timer_list定义如下:struct timer_list {//用来形成链表struct list_head entry;//定始器到达时间unsigned long expires;spinlock_t lock;unsigned long magic;//定时器时间到达后,所要运行的函数void (*function)(unsigned long);//定时器函数对应的参数unsigned long data;//挂载这个定时器的tvec_t_base_s.这个结构我们等会会看到,当该次中断顺利执行后,该值也将清空为NULLstruct tvec_t_base_s *base;};从上面的过程中我们可以看到TIMER_INITIALIZER()只是根据传入的参数初始化了struct timer_list结构.并把magic 成员初始化成TIMER_MAGIC2): mod_timer():修改定时器的到时时间int mod_timer(struct timer_list *timer, unsigned long expires){//如果该定时器没有定义fuctionBUG_ON(!timer->function);//判断timer的magic是否为TIMER_MAGIC.如果不是,则将其修正为TIMER_MAGIC check_timer(timer);//如果要调整的时间就是定时器的定时时间而且已经被激活,则直接返回if (timer->expires == expires && timer_pending(timer))return 1;//调用_mod_timer().呆会再给出分析return __mod_timer(timer, expires);}3): add_timer()用来将定时器挂载到定时软中断队列,激活该定时器static inline void add_timer(struct timer_list * timer){__mod_timer(timer, timer->expires);}可以看到mod_timer与add_timer 最后都会调用__mod_timer().为了分析这个函数,我们先来了解一下定时系统相关的数据结构.tvec_bases: per cpu变量,它的定义如下:static DEFINE_PER_CPU(tvec_base_t, tvec_bases) = { SPIN_LOCK_UNLOCKED };由此可以看到tves_bases的数型数据为teves_base_t.数据结构的定义如下:typedef struct tvec_t_base_s tvec_base_t;struct tvec_t_base_s的定义:struct tvec_t_base_s {spinlock_t lock;//上一次运行计时器的jiffies 值这个值很关键,正是这个值保证了不会遗漏定时器中断,timer中断中每次循环查找后,该值加一unsigned long timer_jiffies;struct timer_list *running_timer;//tv1 tv2 tv3 tv4 tv5是五个链表数组tvec_root_t tv1;tvec_t tv2;tvec_t tv3;tvec_t tv4;tvec_t tv5;} ____cacheline_aligned_in_smp;Tves_root_t与tvec_t的定义如下:#define TVN_BITS 6#define TVR_BITS 8#define TVN_SIZE (1 << TVN_BITS)#define TVR_SIZE (1 << TVR_BITS)#define TVN_MASK (TVN_SIZE - 1)#define TVR_MASK (TVR_SIZE - 1)typedef struct tvec_s {struct list_head vec[TVN_SIZE];} tvec_t;typedef struct tvec_root_s {struct list_head vec[TVR_SIZE];} tvec_root_t;系统规定定时器最大超时时间间隔为0xFFFFFFFF.即为一个32位数.即使在64位系统上.如果超过此值也会将其强制设这oxFFFFFFFF(这在后面的代码分析中可以看到).内核最关心的就是间隔在0~255个HZ之间的定时器.次重要的是间隔在255~1<<(8+6)之间的定时器.第三重要的是间隔在1<<(8+6) ~ 1<<(8+6+6)之间的定器.依次往下推.也就是把32位的定时间隔为份了五个部份.1个8位.4个6位.所以内核定义了五个链表数组.第一个链表数组大小为8位大小,也即上面定义的 #define TVR_SIZE (1 << TVR_BITS).其它的四个数组大小为6位大小.即上面定义的#define TVN_SIZE (1 << TVN_BITS)在加入定时器的时候,按照时间间隔把定时器加入到相应的数组即可.了解这点之后,就可以来看__mod_timer()的代码了://修改timer或者新增一个timer都会调用此接口int __mod_timer(struct timer_list *timer, unsigned long expires){tvec_base_t *old_base, *new_base;unsigned long flags;int ret = 0;//入口参数检测BUG_ON(!timer->function);check_timer(timer);spin_lock_irqsave(&timer->lock, flags);//取得当前CPU对应的tvec_basesnew_base = &__get_cpu_var(tvec_bases);repeat://该定时器所在的tvec_bases.对于新增的timer.它的base字段为NULLold_base = timer->base;/** Prevent deadlocks via ordering by old_base < new_base.*///在把timer从当前tvec_bases摘下来之前,要充分考虑好竞争的情况if (old_base && (new_base != old_base)) {//按次序获得锁if (old_base < new_base) {spin_lock(&new_base->lock);spin_lock(&old_base->lock);} else {spin_lock(&old_base->lock);spin_lock(&new_base->lock);}/** The timer base might have been cancelled while we were* trying to take the lock(s):*///如果timer->base != old_base.那就是说在Lock的时候.其它CPU更改它的值 //那就解锁.重新判断if (timer->base != old_base) {spin_unlock(&new_base->lock);spin_unlock(&old_base->lock);goto repeat;}} else {//old_base == NULl 或者是 new_base==old_base的情况//获得锁spin_lock(&new_base->lock);//同理,在Lock的时候timer会生了改变if (timer->base != old_base) {spin_unlock(&new_base->lock);goto repeat;}}/** Delete the previous timeout (if there was any), and install* the new one:*///将其从其它的tvec_bases上删除.注意运行到这里的话,说话已经被Lock了 if (old_base) {list_del(&timer->entry);ret = 1;}//修改它的定时器到达时间timer->expires = expires;//将其添加到new_base中internal_add_timer(new_base, timer);//修改base字段timer->base = new_base;//操作完了,解锁if (old_base && (new_base != old_base))spin_unlock(&old_base->lock);spin_unlock(&new_base->lock);spin_unlock_irqrestore(&timer->lock, flags);return ret;}internal_add_timer()的代码如下:static void internal_add_timer(tvec_base_t *base, struct timer_list *timer){//定时器到达的时间unsigned long expires = timer->expires;//计算时间间间隔unsigned long idx = expires - base->timer_jiffies;struct list_head *vec;//根据时间间隔,将timer放入相应数组的相应位置if (idx < TVR_SIZE) {int i = expires & TVR_MASK;vec = base->tv1.vec + i;} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {int i = (expires >> TVR_BITS) & TVN_MASK;vec = base->tv2.vec + i;} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;vec = base->tv3.vec + i;} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;vec = base->tv4.vec + i;} else if ((signed long) idx < 0) {/** Can happen if you add a timer with expires == jiffies,* or you set a timer to go off in the past*///如果间隔小于0vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);} else {int i;/* If the timeout is larger than 0xffffffff on 64-bit* architectures then we use the maximum timeout:*///时间间隔超长,将其设为oxFFFFFFFFif (idx > 0xffffffffUL) {idx = 0xffffffffUL;expires = idx + base->timer_jiffies;}i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;vec = base->tv5.vec + i;}/** Timers are FIFO:*///加入到链表末尾list_add_tail(&timer->entry, vec);}计算时间间隔即可知道要加入到哪一个数组.哪又怎么计算加入到该数组的那一项呢?对于间隔时间在0~255的定时器: 它的计算方式是将定时器到达时间的低八位与低八位为1的数相与而成对于第1个六位,它是先将到达时间右移8位.然后与低六位全为1的数相与而成对于第2个六位, 它是先将到达时间右移8+6位.然后与低六位全为1的数相与而成依次为下推…在后面结合超时时间到达的情况再来分析相关部份4):定时器更新每过一个HZ,就会检查当前是否有定时器的定时器时间到达.如果有,运行它所注册的函数,再将其删除.为了分析这一过程,我们先从定时器系统的初始化看起.asmlinkage void __init start_kernel(void){……init_timers();……}Init_timers()的定义如下:void __init init_timers(void){timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,(void *)(long)smp_processor_id());register_cpu_notifier(&timers_nb);//注册TIMER_SOFTIRQ软中断open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL);}timer_cpu_notify()àinit_timers_cpu():代码如下:static void /* __devinit */ init_timers_cpu(int cpu){int j;tvec_base_t *base;//初始化各个数组中的链表base = &per_cpu(tvec_bases, cpu);spin_lock_init(&base->lock);for (j = 0; j < TVN_SIZE; j++) {INIT_LIST_HEAD(base->tv5.vec + j);INIT_LIST_HEAD(base->tv4.vec + j);INIT_LIST_HEAD(base->tv3.vec + j);INIT_LIST_HEAD(base->tv2.vec + j);}for (j = 0; j < TVR_SIZE; j++)INIT_LIST_HEAD(base->tv1.vec + j);//将最近到达时间设为当前jiffiesbase->timer_jiffies = jiffies;}我们在前面分析过,每当时钟当断函数到来的时候,就会打开定时器的软中断.运行其软中断函数.run_timer_softirq()代码如下:static void run_timer_softirq(struct softirq_action *h){//取得当于CPU的tvec_base_t结构tvec_base_t *base = &__get_cpu_var(tvec_bases);//如果jiffies > base->timer_jiffiesif (time_after_eq(jiffies, base->timer_jiffies))__run_timers(base);}__run_timers()代码如下:static inline void __run_timers(tvec_base_t *base){struct timer_list *timer;unsigned long flags;spin_lock_irqsave(&base->lock, flags);//因为CPU可能关闭中断,引起时钟中断信号丢失.可能jiffies要大base->timer_jiffies 好几个//HZwhile (time_after_eq(jiffies, base->timer_jiffies)) {//定义并初始化一个链表struct list_head work_list = LIST_HEAD_INIT(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_splice_init(base->tv1.vec + index, &work_list);repeat://work_List中的定时器是已经到时的定时器if (!list_empty(head)) {void (*fn)(unsigned long);unsigned long data;//遍历链表中的每一项.运行它所对应的函数,并将定时器从链表上脱落timer = list_entry(head->next,struct timer_list,entry);fn = timer->function;data = timer->data;list_del(&timer->entry);set_running_timer(base, timer);smp_wmb();timer->base = NULL;spin_unlock_irqrestore(&base->lock, flags);fn(data);spin_lock_irq(&base->lock);goto repeat;}}set_running_timer(base, NULL);spin_unlock_irqrestore(&base->lock, flags);}如果base->timer_jiffies低八位为零.说明它向第九位有进位.所以把第九位到十五位对应的定时器搬到前八位对应的数组.如果第九位到十五位为空的话.就到它的上个六位去搬数据.上面的代码也说明.要经过1<<8个HZ才会更新全部数组中的定时器.这样做的效率是很高的. 分析下里面的两个重要的子函数:static int cascade(tvec_base_t *base, tvec_t *tv, int index){/* cascade all the timers from tv up one level */struct list_head *head, *curr;//取数组中序号对应的链表head = tv->vec + index;curr = head->next;/** We are removing _all_ timers from the list, so we don't have to* detach them individually, just clear the list afterwards.*///遍历这个链表,将定时器重新插入到base中while (curr != head) {struct timer_list *tmp;tmp = list_entry(curr, struct timer_list, entry);BUG_ON(tmp->base != base);curr = curr->next;internal_add_timer(base, tmp);}//将链表设为初始化状态INIT_LIST_HEAD(head);return index;}//将list中的数据放入head中,并将list置为空static inline void list_splice_init(struct list_head *list, struct list_head *head) {if (!list_empty(list)) {__list_splice(list, head);INIT_LIST_HEAD(list);}}//将list中的数据放入headstatic inline void __list_splice(struct list_head *list, struct list_head *head){//list的第一个元素struct list_head *first = list->next;//list的最后一个元素struct list_head *last = list->prev;//head的第一个元素struct list_head *at = head->next;将first对应的链表链接至headfirst->prev = head;head->next = first;//将head 原有的数据加入到链表末尾last->next = at;at->prev = last;}5):del_timer()删除定时器//删除一个timerint del_timer(struct timer_list *timer){unsigned long flags;tvec_base_t *base;check_timer(timer);repeat:base = timer->base;//该定时器没有被激活if (!base)return 0;//加锁spin_lock_irqsave(&base->lock, flags);//如果在加锁的过程中,有其它操作改变了timerif (base != timer->base) {spin_unlock_irqrestore(&base->lock, flags);goto repeat;}//将timer从链表中删除list_del(&timer->entry);timer->base = NULL;spin_unlock_irqrestore(&base->lock, flags);return 1;}6): del_timer_sync()有竞争情况下的定时器删除在SMP系统中,可能要删除的定时器正在某一个CPU上运行.为了防止这种在情况.在删除定时器的时候,应该优先使用del_timer_synsc().它会一直等待所有CPU上的定时器执行完成. int del_timer_sync(struct timer_list *timer){tvec_base_t *base;int i, ret = 0;check_timer(timer);del_again://删除些定时器ret += del_timer(timer);//遍历CPUfor_each_online_cpu(i) {base = &per_cpu(tvec_bases, i);//如果此CPU正在运行这个timerif (base->running_timer == timer) {//一直等待,直到这个CPU执行完while (base->running_timer == timer) {cpu_relax();preempt_check_resched();}break;}}smp_rmb();//如果这个timer又被调用.再删除if (timer_pending(timer))goto del_again;return ret;}定时器部份到这里就介绍完了.为了管理定时器.内核用了一个很巧妙的数据结构.值得好好的体会.。
你需要了解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 内核定时器详解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线O上的可编程间隔定时器的中断触发。
初始化阶段1. 初始化间,time_init() 函数被调用来建立计时体系结构2. 初始化Xtime变量(Xtime变量存放当前时间和日期,它是一个timespec 类型的数据结构)3. 初始化wall_to_monotonic 变量,它跟Xtime 是同一类型的,但它存放将加在Xtime 上的描述和纳秒数,这样即使突发改变Xtime 也不会受到影响。
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设备驱动程序学习(10)-时间、延迟及延缓操作Linux设备驱动程序学习(10)-时间、延迟及延缓操作度量时间差时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据HZ 值来设定,HZ 是一个体系依赖的值,在<linux/param.h>中定义或该文件包含的某个子平台相关文件中。
作为通用的规则,即便如果知道HZ 的值,在编程时应当不依赖这个特定值,而始终使用HZ。
对于当前版本,我们应完全信任内核开发者,他们已经选择了最适合的HZ值,最好保持HZ 的默认值。
对用户空间,内核HZ几乎完全隐藏,用户HZ 始终扩展为100。
当用户空间程序包含param.h,且每个报告给用户空间的计数器都做了相应转换。
对用户来说确切的HZ 值只能通过/proc/interrupts 获得:/proc/interrup ts 的计数值除以/proc/uptime 中报告的系统运行时间。
对于ARM体系结构:在<linux/param.h>文件中的定义如下:也就是说:HZ 由__KERNEL__和CONFIG_HZ决定。
若未定义__KERNEL__,H Z为100;否则为CONFIG_H Z。
而CONFIG_HZ是在内核的根目录的.config文件中定义,并没有在make menuconfig的配置选项中出现。
Linux的\arch\arm\configs\s3c2410_defconfig文件中的定义为:所以正常情况下s3c24x0的HZ为200。
这一数值在后面的实验中可以证实。
每次发生一个时钟中断,内核内部计数器的值就加一。
这个计数器在系统启动时初始化为0,因此它代表本次系统启动以来的时钟嘀哒数。
这个计数器是一个64-位变量( 即便在32-位的体系上)并且称为“jiffies_64”。
但是驱动通常访问jiffies 变量(unsigned long)(根据体系结构的不同:可能是jiffies_64 ,可能是jiffies_64 的低32位)。
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 字段。
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。
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函数的实现,函数首先处理两种特殊情况,一种是传入的。
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 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)
实例:。
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)的代码片段。
1.linux HZLinux核心几个重要跟时间有关的名词或变数,以下将介绍HZ、tick与jiffies。
HZLinux核心每隔固定周期会发出timer interrupt (IRQ 0),HZ是用来定义每一秒有几次timer interrupts。
举例来说,HZ为1000,代表每秒有1000次timer interrupts。
HZ可在编译核心时设定,如下所示(以核心版本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=.95秒,约合497天或1.38年),即当取值到达最大值时继续加1,就变为了0。
3.4 Linux内核如何来防止jiffies溢出Linux内核中提供了以下四个宏,可有效解决由于jiffies溢出而造成程序逻辑出错的情况。
下面是从Linux Kernel/** 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时间。