linux定时器和Jiffies

合集下载

linux 内核定时器timer_list详解

linux 内核定时器timer_list详解

在模块的编写过程中,我们经常使用定时器来等待一段时间之后再来执行某一个操作。

为方便分析,写了下列一段测试程序:#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定时器编程
Linux定时器编程
Linux内核中定义了一个timer_list结构,我们在驱动程序中可以利用之:
struct timer_list {
struct list_head list;
unsigned long expires; //定时器到期时间
unsigned long data; //作为参数被传入定时器处理函数
(4)在定时器到期时,function被执行;
(5)在程序中涉及timer控制的地方适当地调用del_timer、mod_timer删除timer或修改timer的expires。
我们可以参考drivers\char\keyboard.c中键盘的驱动中关于timer的部分:

#include <linux/timer.h>
rep = test_and_set_bit(keycode, key_down);
/* If the keyboard autorepeated for us, ignore it.
* We do our own autorepeat processing.
*/
if (rep && !autorepeat)
kbd_repeatkeycode = keycode;
if (vc_kbd_mode(kbd, VC_REPEAT)) {
if (rep)
key_autorepeat_timer.expires = jiffies + kbd_repeatinterval;
else
key_autorepeat_timer.expires = jiffies + kbd_repeattimeout;
return;

你需要了解Linux设备驱动之定时与延时的区别

你需要了解Linux设备驱动之定时与延时的区别

你需要了解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下高精度定时器的实现

-1

struct itimerval { struct timeval it _ interval; /* timer interval * / struct timeval it _ value; }; /* current value * /
itimerval 结构中的 it_value 是减少时间,当这个值为 0 的时候就发出相应的信号,然后再将 it_value 设置为 it_interval 值。如果 it_interval 不为 0,则该定时器将持续有效,即达到周期 定时的效果。 应用 setitimer 时需要注意 Linux 信号机制基本上是从 UNIX 系统中继承过来的。 早期 UNIX 系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那 些建立在早期机制上的信号叫做“不可靠信号”,信号值小于 SIGRTMIN(SIGRTMIN=32, SIGRTMAX=63)的信号都是不可靠信号。对于不可靠信号来说,进程每次处理信号后就将 信号的响应设置为默认动作。在某些情况下,将导致对信号的处理错误;因此,用户如果不 希望这样的操作,就要在信号处理函数结尾再一次调用 signal(),重新安装函数,或者采用 sigaction()来代替 signal()。利用 setitimer()实现 ms 级定时的示例如下:
2. Linux 系统提供的几种定时方式
2.1 利用系统调用
Linux 向用户提供了实现简单定时功能的系统调用 alarm()函数。alarm()函数提供精度为 s 级的定时器,到时之后,向进程发送信号,这种利用信号实现的方法效率低,且精度也不 满足要求[3]。 Linux 还向用户提供了功能更强大的系统调用 setitimer()。setitimer()支持 3 种类型的定 时器,分别是:ITIMER_REAL,以系统真实的时间来计算,定时器到时后发送 SIGALRM 信号; ITIMER_VIRTUAL,以该进程在用户态下花费的时间来计算,定时器到时后发送 SIGVTALRM 信号。ITIMER_PROF,以该进程在用户态下和内核态下所费的时间来计算, 定 时 器 到 时 后 发 送 SIGPROF 信 号 。 setitimer() 的 函 数 原 型 为 :

linux内核定时器详解及实例

linux内核定时器详解及实例

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中定时器的使用方法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)-时间、延迟及延缓操作

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内核定时器--原版PPT优秀课件

Linux内核定时器--原版PPT优秀课件
变的值,大多数体系结构的HZ值是可调的
7
2.1.1 理想的HZ值
自Linux问世以来,i386体系结构中时 钟中断频率就设定为100HZ,但是在2.5开发 版本内核中,中断频率被提高到1000HZ。
提高节拍率意味着时钟中断产生得更 加频繁,所以中断处理程序也会更频繁的 执行,带来以下几点好处: • 更高的时钟中断解析度可提高时间驱动事 件的解析度。 • 提高了时间驱动事件的准确度。
13
2.2.1 jiffies的回绕
• Jiffies变量总是无符号长整数,因此,在32 位体系结构上时32位,在64位体系结构上 时64位。32位的jiffies变量,如果HZ = 100, 497天后会溢出,如果HZ = 1000,49.7天后 会溢出。而64位则别指望能看到溢出。
• 访问jiffies的代码仅会读取jiffies_64的低32位, 通过get_jiffies_64()函数,可以读取整个64 位,多数代码只需要访问低32位就够了。
/*还没有超时,继续执行任务*/ }
/*超时了,发生错误*/
15
• 正常的情况下,上面的代码没有问题。但是当 jiffies接近最大值的时候,就会出现回绕问题, 如下图所示:
• 1. 循环中第一次比较时,jiffies = J1,没有超时 • 2. 循环中第二次比较时,jiffies = J2,实际已经
执行中断处理程序,占用CPU时间过多。 • 减少了处理器对其他工作的时间,更频繁
的打乱处理器高速缓存并增加耗电。
11
2.2 jiffies
• 全局变量jiffies用来记录自系统启动以来产 生得节拍的总数。例如:系统启动了N秒, 那么jiffies就为N x HZ。
• Jiffies的类型为无符号长整型(unsigned long),用其它任何类型存放都不正确。
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

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时间。

相关文档
最新文档