内核定时器
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;}定时器部份到这里就介绍完了.为了管理定时器.内核用了一个很巧妙的数据结构.值得好好的体会.。
at89s51工作原理

at89s51工作原理AT89S51是一种基于8051内核的单片机芯片,它采用了CMOS技术制造,具有低功耗、高速运算等特点。
下面是AT89S51的工作原理:1. 内核结构:AT89S51芯片的内核采用了典型的8051内核结构,包括中央处理器、内部存储器(包括RAM和ROM)、I/O系统、定时器/计数器等核心模块。
2. 程序存储器:AT89S51芯片具有4KB的内部Flash存储器,用于存储程序指令。
这些指令可以被读取和执行。
Flash存储器还具有可擦写和重新编程的能力,可以进行固件更新。
3. 数据存储器:AT89S51芯片具有64字节的RAM(随机访问存储器),用于存储程序中的变量和临时数据。
此外,芯片还具有可选的外部数据存储器接口(如外部RAM或ROM芯片)。
4. I/O系统:AT89S51芯片具有4个I/O口,可以用于与外部设备的通信和数据交换。
这些I/O口可以配置为输入或输出,并且具有电平转换功能。
5. 定时器/计数器:AT89S51芯片具有2个16位定时器/计数器。
这些定时器可以配置为计时、计数或作为PWM(脉宽调制)发生器使用,以满足不同的应用需求。
6. 中断系统:AT89S51芯片具有5个中断源,允许外部设备通过引脚触发中断事件。
中断优先级可以通过配置寄存器进行设置,以确保最高优先级的中断能够及时响应。
在工作过程中,AT89S51芯片根据程序存储器中的指令,通过中央处理器执行相应的操作。
通过与外部设备的通信和数据交换,在完成特定的任务和功能实现。
同时,芯片可以通过定时器、计数器和中断系统,实现定时控制和中断处理的功能。
hrtimer及内核clock

hrtimer及内核clock/timer子系统[嵌入式]发布时间:2010-06-30 09:54:39转过来,研究android过程中发现linux kernel内核定时器这块较以前2.4版本改动非常大,在网上收到这篇,粗看了下,但时间紧张没仔细看,等忙完android的这个分析,回头仔细看。
kernel-2.6.22中的arm arch加入了对dynticks, clocksource/event支持. 找了些kernelclock及timer子系统近来的变化, 总结一下.一般来说Soft-Timer (timer wheel / hrtimer)都是由Hardware-Timer(时钟中断之类)以及相关的clock source(e.g GPT in Soc)驱动,所以我打算先从clock这层开始介绍, 接着是soft-timer, kernel timekeeping,最后来看一些应用.Clock Sourceclock source定义了一个clock device的基本属性及行为, 这些clock device一般都有计数,定时, 产生中断能力, 比如GPT. 结构定义如下:struct clocksource {char *name;struct list_head list;int rating;cycle_t (*read)(void);cycle_t mask;u32 mult; /* cycle -> xtime interval, maybe two clock cy cle trigger oneinterrupt (one xtime interval) */u32 shift;unsigned long flags;cycle_t (*vread)(void);void (*resume)(void);/* timekeeping specific data, ignore */cycle_t cycle_interval; /* just the rate of GPT count p er OS HZ */u64 xtime_interval; /* xtime_interval = cycle_interv al * mult. */cycle_t cycle_last ____cacheline_aligned_in_smp; /* las t cycle in rate count */u64 xtime_nsec; /* cycle count, remain from _ns ec* now nsec rate count offset = xtime_nsec +* _nsec << shift */s64 error;};最重要的成员是read(), cycle_last和cycle_interval. 分别定义了读取clock device count寄存器当前计数值接口, 保存上一次周期计数值和每个tick周期间隔值. 这个结构内的值,无论是cycle_t, 还是u64类型(实际cycle_t就是u64)都是计数值(cycle), 而不是nsec,sec和jiffies. read()是整个kernel读取精确的单调时间计数的接口,kernel会用它来计算其他时间, 比如:jiffies, xtime.clocksource的引入, 解决了之前kernel各个arch都有自己的clock device的管理方式,基本都隐藏在MSL层, kernel core 及driver很难访问的问题. 它导出了以下接口:1) clocksource_register() 注册clocksource2) clocksource_get_next() 获取当前clocksource设备3) clocksource_read() 读取clock, 实际跑到clocksource->read()当driver处理的时间精度比较高的时, 可以通过上面的接口, 直接拿clock device来读.当然目前ticker时钟中断源也会以clocksource的形式存在.Clock EventClock event的主要作用是分发clock事件及设置下一次触发条件. 在没有clock event之前,时钟中断都是周期性地产生, 也就是熟知的jiffies和HZ.Clock Event device主要的结构:struct clock_event_device {const char *name;unsigned int features;unsigned long max_delta_ns;unsigned long min_delta_ns;unsigned long mult;int shift;int rating;int irq;cpumask_t cpumask;int (*set_next_event)(unsigned long evt,struct clock_event_device *);void (*set_mode)(enum clock_event_mode mode,struct clock_event_device *);void (*event_handler)(struct clock_event_devi ce *);void (*broadcast)(cpumask_t mask);struct list_head list;enum clock_event_mode mode;ktime_t next_event;};最重要的是set_next_event(), event_handler(). 前者是设置下一个clock事件的触发条件,一般就是往clock device里重设一下定时器. 后者是event handler, 事件处理函数.该处理函数会在时钟中断ISR里被调用. 如果这个clock用来做为ticker时钟,那么handler的执行和之前kernel的时钟中断ISR基本相同, 类似timer_tick().事件处理函数可以在运行时动态替换, 这就给kernel一个改变整个时钟中断处理方式的机会,也就给highres tick及dynamic tick一个动态挂载的机会.目前kernel内部有periodic/highres/dynamic tick三种时钟中断处理方式. 后面会介绍.hrtimer & timer wheel首先说一下timer wheel. 它就是kernel一直采用的基于jiffies的timer机制,接口包括init_timer(), mod_timer(), del_timer()等, 很熟悉把.hrtimer 的出现, 并没有抛弃老的timer wheel机制(也不太可能抛弃:)).hrtimer做为kernel里的timer定时器, 而timer wheel则主要用来做timeout定时器.分工比较明确. hrtimers采用红黑树来组织timers, 而timer wheel采用链表和桶.hrtimer精度由原来的timer wheel的jiffies提高到nanosecond.主要用于向应用层提供nanosleep, posix-timers和itimer接口,当然驱动和其他子系统也会需要high resolution的timer.kernel 里原先每秒周期性地产生HZ个ticker(中断),被在下一个过期的hrtimer的时间点上产生中断代替. 也就是说时钟中断不再是周期性的,而是由timer来驱动(靠clockevent的set_next_event接口设置下一个事件中断),只要没有hrtimer加载, 就没有中断. 但是为了保证系统时间(进程时间统计,jiffies的维护)更新, 每个tick_period(NSEC_PER_SEC/HZ,再次强调hrtimer精度是nsec)都会有一个叫做tick_sched_timer的hrtimer加载.接下来对比一下, hrtimer引入之前及之后, kernel里时钟中断的处理的不同. (这里都是基于armarch的source去分析)1)no hrtimerkernel 起来, setup_arch()之后的time_init()会去初始化相应machine结构下的timer.初始化timer函数都在各个machine的体系结构代码中, 初始化完硬件时钟, 注册中断服务函数,使能时钟中断. 中断服务程序会清中断, 调用timer_tick(), 它执行:1. profile_tick(); /* kernel profile, 不是很了解*/2. do_timer(1); /* 更新jiffies */3. update_process_times(); /* 计算进程耗时, 唤起TIMER_SOFTIRQ(timer wheel),重新计算调度时间片等等*/最后中断服务程序设置定时器, 使其在下一个tick产生中断.这样的框架, 使得high-res的timer很难加入. 所有中断处理code都在体系结构代码里被写死,并且代码重用率很低, 毕竟大多的arch都会写同样的中断处理函数.2)hrtimerkernel 里有了clockevent/source的引入, 就把clocksource的中断以一种事件的方式被抽象出来.事件本身的处理交给event handler. handler可以在kernel里做替换从而改变时钟中断的行为.时钟中断ISR会看上去象这样:static irqreturn_t timer_interrupt(int irq, void *dev_id) {/* clear timer interrupt flag */...../* call clock event handler */arch_clockevent.event_handler(&arch_clockevent);....return IRQ_HANDLED;}event_handler 在注册clockevent device时, 会被默认设置成tick_handle_periodic().所以kernel刚起来的时候, 时钟处理机制仍然是periodic的, ticker中断周期性的产生.tick_handle_periodic()会做和timer_tick差不多的事情,然后调用clockevents_program_event() =>arch_clockevent.set_next_event()去设置下一个周期的定时器.tick-common.c里把原来kernel时钟的处理方式在clockevent框架下实现了, 这就是periodictick的时钟机制.hres tick机制在第一个TIMER SOFTIRQ里会替换掉periodic tick, 当然要符合一定条件,比如command line里没有把hres(highres=off)禁止掉,clocksource/event支持hres和oneshot的能力. 这里的切换做的比较ugly,作者的comments也提到了, 每次timer softirq被调度,都要调用hrtimer_run_queues()检查一遍hres是否active,如果能在timer_init()里就把clocksource/event的条件check过, 直接切换到hres就最好了,不知道是不是有什么限制条件. TIMER SOFTIRQ代码如下:static void run_timer_softirq(struct softirq_action *h) {tvec_base_t *base = __get_cpu_var(tvec_bases);hrtimer_run_queues(); /* 有机会就切换到hres或者nohz */if (time_after_eq(jiffies, base->timer_jiffies)) __run_timers(base); /* timer wheel */}切换的过程比较简单, 用hrtimer_interrupt()替换当前clockevent hander, 加载一个hrtimer:tick_sched_timer在下一个tick_period过期, retrigger下一次事件.hrtimer_interrupt ()将过期的hrtimers从红黑树上摘下来,放到相应clock_base->cpu_base->cb_pending列表里,这些过期timers会在HRTIMER_SOFTIRQ里执行.然后根据剩余的最早过期的timer来retrigger下一个event, 再调度HRTIMER_SOFTIRQ. hrtimer softirq执行那些再cb_pending上的过期定时器函数.tick_sched_timer这个hrtimer在每个tick_period都会过期, 执行过程和timer_tick()差不多,只是在最后调用hrtimer_forward将自己加载到下一个周期里去,保证每个tick_period都能正确更新kernel内部时间统计.TimekeepingTimekeeping子系统负责更新xtime, 调整误差, 及提供get/settimeofday接口. 为了便于理解,首先介绍一些概念:Times in Kernelkernel的time基本类型:1) system timeA monotonically increasing value that represents the amount of time the system has been running. 单调增长的系统运行时间, 可以通过time source,xtime及wall_to_monotonic计算出来.2) wall timeA value representing the the human time of day, as seen on a wrist-watch.Realtime时间: xtime.3) time sourceA representation of a free running counter running at a known frequency, usually in hardware, e.g GPT. 可以通过clocksource->read()得到counter值4) tickA periodic interrupt generated by a hardware-timer, typically with a fixed interval defined by HZ: jiffies这些time之间互相关联, 互相可以转换.system_time = xtime + cyc2ns(clock->read() - clock->cycle_last) +wall_to_monotonic;real_time = xtime + cyc2ns(clock->read() - clock->cycle_last)也就是说real time是从1970年开始到现在的nanosecond, 而systemtime是系统启动到现在的nanosecond.这两个是最重要的时间, 由此hrtimer可以基于这两个time来设置过期时间. 所以引入两个clock base.Clock BaseCLOCK_REALTIME: base在实际的wall timeCLOCK_MONOTONIC: base在系统运行system timehrtimer可以选择其中之一, 来设置expire time, 可以是实际的时间, 也可以是相对系统的时间.他们提供get_time()接口:CLOCK_REALTIME 调用ktime_get_real()来获得真实时间,该函数用上面提到的等式计算出realtime.CLOCK_MONOTONIC 调用ktime_get(), 用system_time的等式获得monotonic time.timekeeping提供两个接口do_gettimeofday()/do_settimeofday(), 都是针对realtime操作. 用户空间对gettimeofday的syscall也会最终跑到这里来.do_gettimeofday()会调用__get_realtime_clock_ts()获得时间, 然后转成timeval.do_settimeofday(), 将用户设置的时间更新到xtime, 重新计算xtime到monotonic的转换值,最后通知hrtimers子系统时间变更.int do_settimeofday(struct timespec *tv){unsigned long flags;time_t wtm_sec, sec = tv->tv_sec;long wtm_nsec, nsec = tv->tv_nsec;if ((unsigned long)tv->tv_nsec >= NSEC_PER_SEC) return -EINVAL;write_seqlock_irqsave(&xtime_lock, flags);nsec -= __get_nsec_offset();wtm_sec = wall_to__sec + (_sec - se c);wtm_nsec = wall_to__nsec + (_nsec - nsec);set_normalized_timespec(&xtime, sec, nsec); /* 重新计算x time:用户设置的时间减去上一个周期到现在的nsec */set_normalized_timespec(&wall_to_monotonic, wtm_sec, w tm_nsec); /*重新调整wall_to_monotonic */clock->error = 0;ntp_clear();update_vsyscall(&xtime, clock);write_sequnlock_irqrestore(&xtime_lock, flags);/* signal hrtimers about time change */clock_was_set();return 0;}Userspace Applicationhrtimer的引入, 对用户最有用的接口如下:Clock APIclock_gettime(clockid_t, struct timespec *)获取对应clock的时间clock_settime(clockid_t, const struct timespec *)设置对应clock时间clock_nanosleep(clockid_t, int, const struct timespec *, struct timespec *)进程nano sleepclock_getres(clockid_t, struct timespec *)获取时间精度, 一般是nanosecclockid_t 定义了四种clock:CLOCK_REALTIMESystem-wide realtime clock. Setting this clock requires appropriate privileges. CLOCK_MONOTONICClock that cannot be set and represents monotonic time since some unspecified starting point.CLOCK_PROCESS_CPUTIME_IDHigh-resolution per-process timer from the CPU.CLOCK_THREAD_CPUTIME_IDThread-specific CPU-time clock.前两者前面提到了, 后两个是和进程/线程统计时间有关系, 还没有仔细研究过,是utime/stime之类的时间. 应用层可以利用这四种clock, 提高灵活性及精度.Timer APITimer 可以建立进程定时器,单次或者周期性定时。
8051单片机的内核的结构及运行过程解析

8051单片机的内核的结构及运行过程解析1.ALU(算术逻辑单元):8051单片机内置了一个8位ALU,负责执行算术和逻辑运算。
ALU可以进行加法、减法、与、或、非、异或等操作。
2.寄存器组:8051单片机包括4个8位的通用寄存器(R0~R7)和一个16位的程序计数器(PC)。
通用寄存器可用于保存临时数据和中间结果,程序计数器则记录当前执行指令的地址。
3.存储器:8051单片机的存储器包括内部存储器和外部扩展存储器。
内部存储器包括片内RAM和片内ROM两部分。
片内RAM可以分为128字节的数据存储器(IDATA)和256字节的数据存储器(XDATA)。
片内ROM则存储程序代码。
4.定时器/计数器:8051单片机内核包含两个定时器/计数器(T0、T1)。
定时器模式用于产生一定的时间延迟,计数器模式用于计数外部事件的个数。
定时器/计数器具有可编程的工作模式和计数值。
5.中断源:8051单片机支持多组中断源,包括外部中断INT0和INT1、定时器/计数器中断、串口中断等。
中断源的优先级可以通过程序设置,以满足不同应用场景的需求。
1.取指令阶段:程序计数器(PC)保存了当前指令的地址。
8051单片机通过将PC指针输出地址,从存储器中读取指令。
读取的指令存储于指令寄存器(IR)中。
2.译码阶段:指令寄存器(IR)中的指令会被译码器解码,生成相应的控制信号和操作码。
控制信号会对单片机的内部功能模块进行控制,操作码则确定执行的操作类型。
3.执行阶段:根据指令的操作码,单片机执行相应的操作。
例如,如果操作码指示进行加法运算,则ALU会执行加法操作,并将结果保存在指定的寄存器或存储单元中。
4.访存阶段:在执行一些指令时,单片机需要从存储器中读取或写入数据。
在访存阶段,单片机会将需要访问的存储器地址输出,并根据控制信号读取或写入数据。
5.写回阶段:在一些指令执行结束后,单片机会将执行结果写回到寄存器或存储器中。
写回阶段会更新相应的寄存器或存储单元,以保存最新的结果。
SysTick系统定时器(功能框图和优先级配置)

SysTick系统定时器(功能框图和优先级配置)SysTick—系统定时器是属于 CM3 内核中的⼀个外设,内嵌在 NVIC 中。
系统定时器是⼀个 24bit (2^24)的向下递减的计数器,计数器每计数⼀次的时间为 1/SYSCLK,⼀般我们设置系统时钟 SYSCLK 等于 72M。
当重装载数值寄存器的值递减到 0 的时候,系统定时器就产⽣⼀次中断,以此循环往复。
因为 SysTick 是属于 CM3 内核的外设,所以所有基于 CM3 内核的单⽚机都具有这个系统定时器,使得软件在 CM3 单⽚机中可以很容易的移植。
系统定时器⼀般⽤于操作系统,⽤于产⽣时基,维持操作系统的⼼跳.SysTick的执⾏过程:counter在时钟的驱动下,从reload初值开始往下递减计数到0(在递减的过程中值可以在STK_VAL中查看到),产⽣中断和置位COUNTFLAG标志。
然后⼜从reload 值开始重新递减计数,如此循环。
SysTick相关寄存器SysTick—系统定时器有4 个寄存器(CTRL LOAD VAL CALIB),简要介绍如下。
在使⽤ SysTick 产⽣定时的时候,只需要配置前三个寄存器,最后⼀个校准寄存器不需要使⽤。
SysTick寄存器结构体SysTick寄存器(在固件库⽂件:core_cm3.h中定义)typedef struct{_IO uint32_t CTRL; /*控制及状态寄存器*/_IO uint32_t LOAD; /*重装载数值寄存器*/_IO uint32_t VAL; /*当前数值寄存器*/_IO uint32_t CALIB; /*校准寄存器*/}SysTick库函数SysTick配置库函数(在固件库⽂件:core_cm3.h中定义)__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks){// 不可能的重装载值,超出范围if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) {return (1UL);}// 设置重装载寄存器SysTick->LOAD = (uint32_t)(ticks - 1UL);// 设置中断优先级,默认优先级最低 __NVIC_PRIO_BITS 4(1111)系统定时器此时设置的优先级在内核外设中是最低的NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);// 设置当前数值寄存器SysTick->VAL = 0UL;// 设置系统定时器的时钟源为 AHBCLK=72M// 使能系统定时器中断// 使能定时器SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |21 SysTick_CTRL_TICKINT_Msk |22 SysTick_CTRL_ENABLE_Msk;23 return (0UL);24 }⽤固件库编程的时候我们只需要调⽤库函数 SysTick_Config()即可,形参ticks⽤来设置重装载寄存器的值,最⼤不能超过重装载寄存器的值 2^24,当重装载寄存器的值递减到 0的时候产⽣中断,然后重装载寄存器的值⼜重新装载往下递减计数,以此循环往复。
linux核心函数

linux核心函数Linux 内核是操作系统的核心部分,它提供了操作系统的核心功能,包括进程管理、内存管理、文件系统等。
Linux 内核的源代码中包含了大量的函数,用于实现各种操作系统的功能。
以下是一些Linux 内核中常见的核心函数,它们扮演着关键的角色:1.进程管理函数:–fork():创建一个新的进程。
–exec():在当前进程中执行一个新的程序。
–wait():等待子进程结束。
–exit():终止当前进程。
2.调度和任务管理函数:–schedule():进行进程调度。
–yield():主动让出CPU,将当前进程移动到就绪队列的末尾。
–wake_up_process():唤醒一个等待中的进程。
3.内存管理函数:–kmalloc():在内核中分配内存。
–kfree():释放内核中的内存。
–vmalloc():在虚拟地址空间中分配内存。
4.文件系统函数:–open():打开一个文件。
–read():从文件中读取数据。
–write():向文件中写入数据。
–close():关闭文件。
5.设备驱动函数:–register_chrdev():注册字符设备。
–unregister_chrdev():注销字符设备。
–request_irq():注册中断处理函数。
6.网络函数:–socket():创建套接字。
–bind():将套接字与地址绑定。
–listen():侦听传入连接请求。
–accept():接受传入的连接请求。
7.定时器和时钟函数:–timer_create():创建一个定时器。
–timer_settime():设置定时器的时间。
–gettimeofday():获取当前时间。
8.同步和互斥函数:–spin_lock():获取自旋锁。
–spin_unlock():释放自旋锁。
–mutex_lock():获取互斥锁。
–mutex_unlock():释放互斥锁。
这些函数仅仅是Linux 内核中众多函数的一小部分,Linux 内核的源代码非常庞大而复杂,包含了各种各样的功能和模块。
linux c语言定时器

linux c语言定时器linux c语言定时器2010-09-01 20:13linux定时器的使用使用定时器的目的无非是为了周期性的执行某一任务,或者是到了一个指定时间去执行某一个任务。
要达到这一目的,一般有两个常见的比较有效的方法。
一个是用linux内部的三个定时器,另一个是用sleep, usleep函数让进程睡眠一段时间,其实,还有一个方法,那就是用gettimeofday, difftime等自己来计算时间间隔,然后时间到了就执行某一任务,但是这种方法效率低,所以不常用。
首先来看看linux操作系统为每一个进程提供的3个内部计时器。
ITIMER_REAL: 给一个指定的时间间隔,按照实际的时间来减少这个计数,当时间间隔为0的时候发出SIGALRM信号ITIMER_VIRTUAL: 给定一个时间间隔,当进程执行的时候才减少计数,时间间隔为0的时候发出SIGVTALRM信号ITIMER_PROF: 给定一个时间间隔,当进程执行或者是系统为进程调度的时候,减少计数,时间到了,发出SIGPROF信号,这个和ITIMER_VIRTUAL联合,常用来计算系统内核时间和用户时间。
用到的函数有:#include <sys/time.h>int getitimer(int which, struct itimerval *value);int setitimer(int which, struct itimerval*newvalue, struct itimerval* oldvalue); strcut timeval{long tv_sec; /*秒*/long tv_usec; /*微秒*/};struct itimerval{struct timeval it_interval; /*时间间隔*/struct timeval it_value; /*当前时间计数*/};it_interval用来指定每隔多长时间执行任务, it_value用来保存当前时间离执行任务还有多长时间。
开源可编程手环实战篇--RW系统学习

RivieraWaves系统内核目录术语与定义 (3)一、系统概述 (3)1.1 RW简介 (3)1.2 特征 (3)1.3 源文件 (4)1.4 头文件 (4)1.5 内核环境 (4)二、消息机制 (5)2.1 概述 (5)2.2 消息对象 (5)2.3 消息ID (6)2.4 参数管理 (6)2.5 消息队列对象 (6)2.6 消息队列基元 (7)2.6.1 消息空间分配 (7)2.6.2 消息发送 (7)2.6.3 消息基本发送 (8)2.6.4 消息转发 (8)2.6.5 消息释放 (8)三、调度机制 (8)3.1 概述 (8)3.2 优先级管理 (9)3.3 调度算法 (9)3.4 保存服务 (9)四、任务 (9)4.1 任务类型 (9)4.2 任务描述 (9)五、内核定时器 (10)5.1 概述 (10)5.2 时隙定义 (10)5.3 定时器对象 (10)5.4 定时器设置 (10)5.5 定时器基元 (11)5.5.1 定时器设置 (11)5.5.2 定时器清除 (11)5.5.3 定时器活动 (11)5.5.4 定时器期满 (12)六、宏定义 (12)术语与定义RW RivieraWaves系统ID 身份(Identity)开发板系列教程:/s/1bnBJsMr手环DIY开源学习教程:/s/1eQlUGiI阅读软件:/s/1o6ELGCE一、系统概述1.1 RW简介RW是RivieraWaves系统,也是该公司的名称。
RivieraWaves是专业开发无线连接性平台的主要的先进IP供应商,其IP产品集成进大批量系统级芯片(SoC)中,最新的Wi-Fi 802.11ac和Bluetooth Smart 4.1 IP是完整的解决方案,包括了软件,硬件以及系统和RF支持,并且已经获得主要的半导体企业部署使用。
RivieraWaves的Wi-Fi和Bluetooth IP以备有高功效的基于硬件设计和基于DSP设计方式提供,为LTE和Wi-Fi带来了实现差异化的灵活性和统一的平台。
时钟节拍——精选推荐

时钟节拍 时钟节拍可谓是 uC/OS 操作系统的⼼脏,它若不跳动,整个系统都将会瘫痪。
时钟节拍就是操作系统的时基,操作系统要实现时间上的管理,必须依赖于时基。
时钟节拍就是系统以固定的频率产⽣中断(时基中断),并在中断中处理与时间相关的事件,推动所有任务向前运⾏。
时钟节拍需要依赖于硬件定时器,在 STM32 裸机程序中经常使⽤的 SysTick 时钟是 MCU的内核定时器,通常都使⽤该定时器产⽣操作系统的时钟节拍。
⽤户需要先在“os_cfg_app.h”中设定时钟节拍的频率,该频率越⾼,操作系统检测事件就越频繁,可以增强任务的实时性,但太频繁也会增加操作系统内核的负担加重,所以⽤户需要权衡该频率的设置。
秉⽕在这⾥采⽤默认的 1000 Hz(本书之后若⽆特别声明,均采⽤1000 Hz),也就是时钟节拍的周期为 1 ms。
设置时钟节拍的频率:/* ------------------------ TICKS ----------------------- */#define OS_CFG_TICK_RATE_HZ 1000u // 时钟节拍频率 (10 to 1000 Hz)#define OS_CFG_TICK_TASK_PRIO 10u // 时钟节拍任务 OS_TickTask() 的优先级#define OS_CFG_TICK_TASK_STK_SIZE 128u // 时钟节拍任务 OS_TickTask() 的栈空间⼤⼩#define OS_CFG_TICK_WHEEL_SIZE 17u // OSCfg_TickWheel 数组的⼤⼩,推荐使⽤任务总数/4,且为质数View Code 在app.c中的起始任务 AppTaskStart() 中初始化时钟节拍定时器,其实就是初始化 STM32 内核的 SysTick 时钟。
初始化 SysTick 时钟:cpu_clk_freq = BSP_CPU_ClkFreq(); //获取 CPU 内核时钟频率(SysTick ⼯作时钟)cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz; //根据⽤户设定的时钟节拍频率计算 SysTick 定时器的计数值OS_CPU_SysTickInit(cnts); //调⽤ SysTick 初始化函数,设置定时器计数值和启动定时器View Code OS_CPU_SysTickInit() 函数的定义位于“os_cpu_c.c” :void OS_CPU_SysTickInit (CPU_INT32U cnts){CPU_INT32U prio;/* 填写 SysTick 的重载计数值 */CPU_REG_NVIC_ST_RELOAD = cnts - 1u; // SysTick 以该计数值为周期循环计数定时/* 设置 SysTick 中断优先级 */prio = CPU_REG_NVIC_SHPRI3;prio &= DEF_BIT_FIELD(24, 0);prio |= DEF_BIT_MASK(OS_CPU_CFG_SYSTICK_PRIO, 24); //设置为默认的最⾼优先级0,在裸机例程中该优先级默认为最低CPU_REG_NVIC_SHPRI3 = prio;/* 使能 SysTick 的时钟源和启动计数器 */CPU_REG_NVIC_ST_CTRL |= CPU_REG_NVIC_ST_CTRL_CLKSOURCE |CPU_REG_NVIC_ST_CTRL_ENABLE;/* 使能 SysTick 的定时中断 */CPU_REG_NVIC_ST_CTRL |= CPU_REG_NVIC_ST_CTRL_TICKINT;}View Code SysTick 定时中断函数 OS_CPU_SysTickHandler() 的定义也位于“os_cpu_c.c”,就毗邻 OS_CPU_SysTickInit() 函数定义体的上⽅。
linux定时器实现原理

linux定时器实现原理Linux定时器是Linux操作系统中的一种机制,用于在指定的时间间隔内执行特定的任务或程序。
它是实现自动化任务和定时执行的重要工具之一。
本文将介绍Linux定时器的实现原理和使用方法。
一、Linux定时器的实现原理Linux定时器的实现原理主要基于操作系统的时钟中断机制。
当系统启动时,操作系统会初始化一个硬件时钟,并且设置一个固定的时间间隔,通常为几毫秒。
当时钟达到设定的时间间隔时,操作系统会触发一个时钟中断,即产生一个中断信号,通知操作系统进行相应的处理。
在Linux内核中,定时器是通过一个称为“定时器列表”的数据结构来实现的。
定时器列表是一个双向链表,用于存储所有的定时器对象。
每个定时器对象包含了定时器的属性和回调函数等信息。
当一个定时器被创建时,它会被加入到定时器列表中,并根据定时器的触发时间,在列表中找到合适的位置插入。
在每次时钟中断发生时,操作系统会遍历定时器列表,检查是否有定时器已经到达触发时间。
如果有定时器到达触发时间,操作系统将调用相应的回调函数执行任务或程序。
二、Linux定时器的使用方法在Linux中,可以使用多种方式来创建和使用定时器。
以下是使用Linux定时器的常见方法:1. 使用系统调用函数:Linux提供了系统调用函数(如timer_create、timer_settime等)来创建和设置定时器。
通过这些系统调用函数,可以设置定时器的触发时间、定时器的属性以及定时器到达触发时间时要执行的任务或程序。
2. 使用命令行工具:Linux还提供了一些命令行工具(如cron、at 等),可以通过命令行来创建和管理定时器。
通过这些命令行工具,可以设置定时器的触发时间、定时器的属性以及定时器到达触发时间时要执行的任务或程序。
3. 使用编程语言:除了系统调用函数和命令行工具,还可以使用编程语言来创建和使用定时器。
在C语言中,可以使用POSIX定时器库(如timer_create、timer_settime等函数)来实现定时器的功能。
stm32定时器中断配置

stm32 定时器中断配置
stm32 中断
stm32 的Cortex 内核具有强大的异常响应系统,它把能够打断当前代码执行流程的事件分为异常(excepTIon)和中断(terryp),并把它们用二个表管理起来,编号为0~15 的称为内核异常,而16 以上的则称为外部中断(外,相对内核而言),这个表就称为中断向量表。
而STM32 对这个表重新进行了编排,把编号从-3 至6 的中断向量定义为系统异常,编号为负的内核异常不能被设置优先级,如复位(Reset)、不可屏蔽中断(NMI)、硬错误(Hardfault)。
从编号7 开始的为外部中断,这些中断的优先级都是可以自行设置的。
STM32 的中断如此之多,配置起来并不容易,因此,我们需要一个
强大而方便的中断控制器NVIC,NVIC 是属于Cortex 内核的器件。
stm32 中断配置
配置STM32 的中断只需要理解2 个内容,配置4 个变量即可。
linux timer用法

linuxtimer用法Linux操作系统提供了丰富的定时器功能,通过使用Linux定时器,可以轻松地实现定时任务、周期性执行的操作等。
本文将介绍Linux定时器的用法,包括定时器的类型、创建、使用和销毁等。
一、定时器类型Linux定时器可以分为以下几种类型:1.软定时器:软定时器是一种基于时间的定时器,可以通过系统调用实现定时任务。
软定时器的时间单位可以是秒、毫秒、微秒等,可以根据实际需求选择合适的单位。
2.硬定时器:硬定时器是一种基于内核定时器的定时器,可以通过内核提供的定时器接口实现周期性执行的操作。
硬定时器的精度较高,可以根据实际需求选择合适的精度。
二、创建定时器创建定时器可以通过系统调用来实现,具体方法如下:1.软定时器创建:可以使用`timer_create()`函数创建一个软定时器,该函数需要指定定时器的名称、指向定时器回调函数的指针、定时器的超时时间等信息。
创建成功后,会返回一个定时器的标识符,可以使用该标识符来控制定时器的执行。
2.硬定时器创建:可以使用`timer_create()`函数创建一个硬定时器,该函数需要指定定时器的名称、指向定时器回调函数的指针、定时器的起始时间等信息。
创建成功后,内核会根据指定的精度周期性地执行回调函数。
三、使用定时器创建了定时器后,需要使用该标识符来控制定时器的执行。
可以使用`timer_set_state()`函数来设置定时器的状态为运行或停止。
可以使用`timer_start()`函数来启动定时器,使定时器进入运行状态;可以使用`timer_try_stop()`函数来尝试停止当前运行的定时器。
需要注意的是,硬定时器不能被取消或延迟执行,只能被重新设置起始时间。
四、销毁定时器定时器执行完毕或不再需要时,需要销毁该定时器。
可以使用`timer_delete()`函数来销毁软定时器,使用`timer_delete(timerfd)`函数来销毁硬定时器。
Linux内核中的时钟中断tic...

第七章 Linux内核的时钟中断(By 詹荣开,NUDT)Copyright © 2003 by 詹荣开E-mail:***************Linux-2.4.0Version 1.0.0,2003-2-14摘要:本文主要从内核实现的角度分析了Linux 2.4.0内核的时钟中断、内核对时间的表示等。
本文是为那些想要了解Linux I/O子系统的读者和Linux驱动程序开发人员而写的。
关键词:Linux、时钟、定时器申明:这份文档是按照自由软件开放源代码的精神发布的,任何人可以免费获得、使用和重新发布,但是你没有限制别人重新发布你发布内容的权利。
发布本文的目的是希望它能对读者有用,但没有任何担保,甚至没有适合特定目的的隐含的担保。
更详细的情况请参阅GNU通用公共许可证(GPL),以及GNU自由文档协议(GFDL)。
你应该已经和文档一起收到一份GNU通用公共许可证(GPL)的副本。
如果还没有,写信给:The Free Software Foundation, Inc., 675 Mass Ave, Cambridge,MA02139, USA欢迎各位指出文档中的错误与疑问。
前言时间在一个操作系统内核中占据着重要的地位,它是驱动一个OS内核运行的“起博器”。
一般说来,内核主要需要两种类型的时间:1. 在内核运行期间持续记录当前的时间与日期,以便内核对某些对象和事件作时间标记(timestamp,也称为“时间戳”),或供用户通过时间syscall进行检索。
2. 维持一个固定周期的定时器,以提醒内核或用户一段时间已经过去了。
PC机中的时间是有三种时钟硬件提供的,而这些时钟硬件又都基于固定频率的晶体振荡器来提供时钟方波信号输入。
这三种时钟硬件是:(1)实时时钟(Real Time Clock,RTC);(2)可编程间隔定时器(Programmable Interval Timer,PIT);(3)时间戳计数器(Time Stamp Counter,TSC)。
第五章_中断与时钟

16
5-1-3
• 提交任务
中断顶半部和底半部
实际的工作至少会在经过指 定的jiffies(由delay指定)之 后才会被执行
int queue_work(struct workqueue_struct *queue, struct work_struct *work); int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay);
12
5-1-3
– 声明tasklet
中断顶半部和底半部
底半部执行的函数
• tasklet的使用
DECLARE_TASKLET(name, function, data) ;
例如: (unsigned long); void xxx_do_tasklet xxx_do_tasklet(unsigned xxx_tasklet , xxx_do_tasklet , 0); DECLARE_TASKLET( DECLARE_TASKLET(xxx_tasklet xxx_tasklet, xxx_do_tasklet,
– 调度
int schedule_work(struct work_struct *work); int schedule_delayed_work(struct work_struct *work,unsigned long delay);
– 刷新
void flush_scheduled_work(void);
中断处理程序架构
中断顶半部和底半部 中断共享
中断与时钟
计时
时间、延迟及延缓操作
linux 内核定时测量详解timing measurement

定时测量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:。
Linux定时器的使用

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 字段。
可等待定时器是这样一种内核对象

可等待定时器是这样一种内核对象,它们会在某个指定的时间触发,或每隔一段时间触发一次。
它们通常用来在某个时间执行一些操作。
CreateWaitableTimer函数用于创建一个可等待定时器:HANDLE WINAPI CreateWaitableTimer(__in_opt LPSECURITY_ATTRIBUTES lpTimerAttributes, //安全描述符,为NULL时使用默认的//且该句柄不被子进程继承__in BOOL bManualReset, //要创建一个手动重置定时器还是一个自动重置计时器//当手动重置计时器被触发时,正在等待该计时器的所有线程都会变成可调度状态//当自动重置计时器被触发时,只有一个正在等待该计时的线程会变成可调度状态__in_opt LPCTSTR lpTimerName //该可等待计时器的名称);函数OpenWaitableTimer用于得到一个已经存在的可等待计时器的句柄,该句柄与当前进程相关联:HANDLE WINAPI OpenWaitableTimer(__in DWORD dwDesiredAccess, //访问权限__in BOOL bInheritHandle, //是否允许子进程继承该句柄__in LPCTSTR lpTimerName //要打开的对象名称);在创建的时候,可等待的计时器对象总是处于未触发状态。
当我们想要触发计时器时,必须调用SetWaitableTimer函数:BOOL WINAPI SetWaitableTimer(__in HANDLE hTimer, //想要触发的计时器__in const LARGE_INTEGER *pDueTime, //计时器第一次触发的时间__in LONG lPeriod, //第一次触发后,计时器的触发频度__in_opt PTIMERAPCROUTINE pfnCompletionRoutine, //异步过程调用APC函数__in_opt LPVOID lpArgToCompletionRoutine, //APC函数的参数__in BOOL fResume //是否继续执行,一般传FALSE);函数CancelWaitableTimer用来将指定的计时器取消:BOOL WINAPI CancelWaitableTimer(__in HANDLE hTimer);这样计时器就永远不会触发了,除非以后再调用SetWaitableTimer来对它进行重置。
timer定时器介绍

下面的代码引用展示了 jit 关于 jitimer 定时器的部分. 当一个进程试图读取我们的文件,j = jiffies;
/* fill the data for our timer function */
data->prevjiffies = j;
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
这个数据结构包含比曾展示过的更多的字段, 但是这 3 个是打算从定时器代码自身以外被存取的. 这个 expires 字段表示定时器期望运行的 jiffies 值; 在那个时间, 这个 function 函数被调用使用 data 作为一个参数. 如果你需要在参数中传递多项, 你可以捆绑它们作为一个单个数据结构并且传递一个转换为 unsiged long 的指针, 在所有支持的体系上的一个安全做法并且在内存管理中相当普遍( 如同 15 章中讨论的 ). expires 值不是一个 jiffies_64 项因为定时器不被期望在将来很久到时, 并且 64-位操作在 32-位平台上慢.
为能够被执行, 多个动作需要进程上下文. 当你在进程上下文之外(即, 在中断上下文), 你必须遵守下列规则:
没有允许存取用户空间. 因为没有进程上下文, 没有和任何特定进程相关联的到用户空间的途径.
这个 current 指针在原子态没有意义, 并且不能使用因为相关的代码没有和已被中断的进程的联系.
jit 模块包括一个例子文件, /proc/jitimer ( 为 "just in timer"), 它返回一个头文件行以及 6 个数据行. 这些数据行表示当前代码运行的环境; 第一个由读文件操作产生并且其他的由定时器. 下列的输出在编译内核时被记录:
5)高手进阶windows内核定时器之二

标题:【原创】高手进阶windows内核定时器之二作者:combojiang时间: 2008-03-03,15:43链接: /showthread.php?t=60579windows内核定时器,定时最小单位为100ns. 比ring3的定时器要精准得多,并且使用它,稳定性高,系统开销小,无需消息队列,且功能强大,是作为数据采集绝好的定时器。
( ,有点像高钙片的广告词)这个内核定时器,涉及到三个函数,分别是KeInitializeTimerEx,KeCancelTimer和KeSetTimerEx。
下面我们使用windbg这个工具,来进一步看看这三个函数的功能。
注:我用的环境是winxp sp2一、VOID KeInitializeTimerEx(IN PKTIMER Timer,IN TIMER_TYPE Type);typedef enum _TIMER_TYPE {NotificationTimer,SynchronizationTimer} TIMER_TYPE;typedef struct _KTIMER {DISPATCHER_HEADER32 Header;ULARGE_INTEGER DueTime;LIST_ENTRY TimerListEntry;struct _KDPC *Dpc;LONG Period;} KTIMER, *PKTIMER, *PRKTIMER;typedef struct _DISPATCHER_HEADER32 {UCHAR Type;UCHAR Absolute;UCHAR Size;UCHAR Inserted;LONG SignalState;LIST_ENTRY32 WaitListHead;} DISPATCHER_HEADER32;分析KeInitializeTimerEx函数如下:lkd> u KeInitializeTimerEx l 50nt!KeInitializeTimerEx:804f9d00 8bff mov edi,edi804f9d02 55 push ebp804f9d03 8bec mov ebp,esp804f9d05 8b4508 mov eax,dword ptr [ebp+8] ;取参数1,Timer 804f9d08 8a4d0c mov cl,byte ptr [ebp+0Ch] ;取参数2,Type 804f9d0b 80c108 add cl,8804f9d0e 33d2 xor edx,edx;设置Timer->Header.Type = 参数2 Type + 8804f9d10 8808 mov byte ptr [eax],cl;ecx = Timer->Header.WaitListHead804f9d12 8d4808 lea ecx,[eax+8];设置Timer->Header.Inserted = 0804f9d15 885003 mov byte ptr [eax+3],dl;设置Timer->Header.Size = 10804f9d18 c640020a mov byte ptr [eax+2],0Ah;设置Timer->Header.SignalState= 0804f9d1c 895004 mov dword ptr [eax+4],edx;Timer->Header.WaitListHead.Blink = Timer->Header.WaitListHead;804f9d1f 894904 mov dword ptr [ecx+4],ecx;Timer->Header.WaitListHead.Flink = Timer->Header.WaitListHead;804f9d22 8909 mov dword ptr [ecx],ecx;Timer->DueTime.LowPart = 0804f9d24 895010 mov dword ptr [eax+10h],edx;Timer->DueTime.HighPart = 0804f9d27 895014 mov dword ptr [eax+14h],edx;Timer->DueTime.Period = 0804f9d2a 895024 mov dword ptr [eax+24h],edx804f9d2d 5d pop ebp804f9d2e c20800 ret 8逆向还原的代码如下:VOID NTAPI KeInitializeTimerEx(OUT PKTIMER Timer,IN TIMER_TYPE Type){Timer->Header.Type = Type + 8;Timer->Header.Inserted = 0;Timer->Header.Size = 10;Timer->Header.SignalState= 0;Timer->Header.WaitListHead.Blink = Timer->Header.WaitListHead;Timer->Header.WaitListHead.Flink = Timer->Header.WaitListHead;Timer->DueTime.LowPart = 0;Timer->DueTime.HighPart = 0;Timer->Period = 0;}二、BOOLEAN KeCancelTimer(IN PKTIMER Timer);分析KeCancelTimer函数如下:lkd> u KeCancelTimernt!KeCancelTimer:804f9d36 8bff mov edi,edi804f9d38 55 push ebp804f9d39 8bec mov ebp,esp804f9d3b 53 push ebx;提升IRQL = DISPATCH_LEVEL804f9d3c ff1514874d80 call dword ptr [nt!_imp__KeRaiseIrqlToDpcLevel (804d8714)] 804f9d42 8b5508 mov edx,dword ptr [ebp+8] ;取参数,Timer;if(Timer->Header.Inserted == 0)goto 804f9d5d;804f9d45 8a5a03 mov bl,byte ptr [edx+3]804f9d48 84db test bl,bl804f9d4a 7411 je nt!KeCancelTimer+0x27 (804f9d5d);Timer->Header.Inserted = 0;804f9d4c c6420300 mov byte ptr [edx+3],0;下面这几句,是从双向链表中删除当前Timer->Header.WaitListHead804f9d50 56 push esi;esi = Timer->Header.WaitListHead.Flink804f9d51 8b7218 mov esi,dword ptr [edx+18h];edx = Timer->Header.WaitListHead.Blink804f9d54 8b521c mov edx,dword ptr [edx+1Ch];Timer->Header.WaitListHead.Blink.Flink = Timer->Header.WaitListHead.Flink;804f9d57 8932 mov dword ptr [edx],esi;Timer->Header.WaitListHead.Blink.Blink = Timer->Header.WaitListHead.Blink;804f9d59 895604 mov dword ptr [esi+4],edx804f9d5c 5e pop esi;al 保存原来的OldIrql804f9d5d 8ac8 mov cl,al804f9d5f e8647f0400 call nt!KiUnlockDispatcherDatabase (80541cc8)804f9d64 8ac3 mov al,bl804f9d66 5b pop ebx804f9d67 5d pop ebp804f9d68 c20400 ret 4逆向后的代码如下:BOOLEAN NTAPI KeCancelTimer(IN OUT PKTIMER Timer){KIRQL OldIrql;BOOLEAN Inserted;OldIrql = KeRaiseIrqlToDpcLevel();Inserted = Timer->Header.Inserted;if (Inserted){Timer->Header.Inserted = FALSE;//从双向链表中删除当前Timer->Header.WaitListHeadTimer->Header.WaitListHead.Blink.Flink = Timer->Header.WaitListHead.Flink;Timer->Header.WaitListHead.Blink.Blink = Timer->Header.WaitListHead.Blink;}//这个函数作用,后面分析。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
2.4内核定时器内核中许多部分的工作都高度依赖于时间信息。
Linux内核利用硬件提供的不同的定时器以支持忙等待或睡眠等待等时间相关的服务。
忙等待时,CPU会不断运转。
但是睡眠等待时,进程将放弃CPU。
因此,只有在后者不可行的情况下,才考虑使用前者。
内核也提供了某些便利,可以在特定的时间之后调度某函数运行。
我们首先来讨论一些重要的内核定时器变量(jiffies、HZ和xtime)的含义。
接下来,我们会使用Pentium时间戳计数器(TSC)测量基于Pentium的系统的运行次数。
之后,我们也分析一下Linux怎么使用实时钟(RTC)。
2.4.1HZ和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)的代码片段。
该段代码会一直轮询磁盘驱动器的忙状态:unsigned long timeout = jiffies + (3*HZ);while(hwgroup->busy) {/* ... */if(time_after(jiffies, timeout)) {return-EBUSY;}/* ... */}return SUCCESS;如果忙条件在3s内被清除,上述代码将返回SUCCESS,否则,返回-EBUSY。
3*HZ 是3s内的jiffies数量。
计算出来的超时jiffies + 3*HZ将是3s超时发生后新的jiffies值。
time_after()的功能是将目前的jiffies值与请求的超时时间对比,检测溢出。
类似函数还包括time_before()、time_before_eq()和time_after_eq()。
jiffies被定义为volatile类型,它会告诉编译器不要优化该变量的存取代码。
这样就确保了每个节拍发生的定时器中断处理程序都能更新jiffies值,并且循环中的每一步都会重新读取jiffies值。
对于jiffies向秒转换,可以查看USB主机控制器驱动程序drivers/usb/host/ehci-sched.c 中的如下代码片段:if(stream->rescheduled) {ehci_info(ehci, "ep%ds-iso rescheduled " "%lu times in %luseconds\n", stream->bEndpointAddress, is_in? "in":"out", stream->rescheduled,((jiffies –stream->start)/HZ));}上述调试语句计算出USB端点流(见第11章)被重新调度stream->rescheduled次所耗费的秒数。
jiffies-stream->start是从开始到现在消耗的jiffies数量,将其除以HZ就得到了秒数值。
假定jiffies值为1000,32位的jiffies会在大约50天的时间内溢出。
由于系统的运行时间可以比该时间长许多倍,因此,内核提供了另一个变量jiffies_64以存放64位(u64)的jiffies。
链接器将jiffies_64的低32位与32位的jiffies指向同一个地址。
在32位的机器上,为了将一个u64变量赋值给另一个,编译器需要2条指令,因此,读jiffies_64的操作不具备原子性。
可以将drivers/cpufreq/cpufreq_stats.c文件中定义的cpufreq_stats_update()作为实例来学习。
2.4.2长延时在内核中,以jiffies为单位进行的延迟通常被认为是长延时。
一种可能但非最佳的实现长延时的方法是忙等待。
实现忙等待的函数有“占着茅坑不拉屎”之嫌,它本身不利用CPU 进行有用的工作,同时还不让其他程序使用CPU。
如下代码将占用CPU 1秒:unsigned long timeout = jiffies + HZ;while (time_before(jiffies, timeout)) continue;实现长延时的更好方法是睡眠等待而不是忙等待,在这种方式中,本进程会在等待时将处理器出让给其他进程。
schedule_timeout()完成此功能:unsigned long timeout = HZ;schedule_timeout(timeout); /* Allow other parts of the kernel to run */这种延时仅仅确保超时较低时的精度。
由于只有在时钟节拍引发的内核调度才会更新jiffies,所以无论是在内核空间还是在用户空间,都很难使超时的精度比HZ更大了。
另外,即使你的进程已经超时并可被调度,但是调度器仍然可能基于优先级策略选择运行队列的其他进程[1]。
用于睡眠等待的另2个函数是wait_event_timeout()和msleep(),它们的实现都基于schedule_timeout()。
wait_event_timeout()的使用场合是:在一个特定的条件满足或者超时发生后,希望代码继续运行。
msleep()表示睡眠指定的时间(以毫秒为单位)。
这种长延时技术仅仅适用于进程上下文。
睡眠等待不能用于中断上下文,因为中断上下文不允许执行schedule()或睡眠(4.2节给出了中断上下文可以做和不能做的事情)。
在中断中进行短时间的忙等待是可行的,但是进行长时间的忙等则被认为不可赦免的罪行。
在中断禁止时,进行长时间的忙等待也被看作禁忌。
为了支持在将来的某时刻进行某项工作,内核也提供了定时器API。
可以通过init_timer()动态定义一个定时器,也可以通过DEFINE_TIMER()静态创建定时器。
然后,将处理函数的地址和参数绑定给一个timer_list,并使用add_timer()注册它即可:#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 execute after n seconds */my_timer.data = func_parameter; /* Parameter to be passed to timer_func */add_timer(&my_timer); /* Start the timer */上述代码只会让定时器运行一次。
如果想让timer_func()函数周期性地执行,需要在timer_func()加上相关代码,指定其在下次超时后调度自身:static void timer_func(unsigned long func_parameter){/* Do work to be done periodically *//* ... */init_timer(&my_timer);my_timer.expire = jiffies + n*HZ;my_timer.data = func_parameter;my_timer.function = timer_func;add_timer(&my_timer);}你可以使用mod_timer()修改my_timer的到期时间,使用del_timer()取消定时器,或使用timer_pending()以查看my_timer当前是否处于等待状态。
查看kernel/timer.c源代码,会发现schedule_timeout()内部就使用了这些API。
clock_settime()和clock_gettime()等用户空间函数可用于获得内核定时器服务。
用户应用程序可以使用setitimer()和getitimer()来控制一个报警信号在特定的超时后发生。
2.4.3短延时在内核中,小于jiffy的延时被认为是短延时。
这种延时在进程或中断上下文都可能发生。
由于不可能使用基于jiffy的方法实现短延时,之前讨论的睡眠等待将不再能用于短的超时。
这种情况下,唯一的解决途径就是忙等待。
实现短延时的内核API包括mdelay()、udelay()和ndelay(),分别支持毫秒、微秒和纳秒级的延时。
这些函数的实际实现取决于体系架构,而且也并非在所有平台上都被完整实现。
忙等待的实现方法是测量处理器执行一条指令的时间,为了延时,执行一定数量的指令。
从前文可知,内核会在启动过程中进行测量并将该值存储在loops_per_jiffy变量中。
短延时API就使用了loops_per_jiffy值来决定它们需要进行循环的数量。
为了实现握手进程中1微秒的延时,USB主机控制器驱动程序(drivers/usb/host/ehci-hcd.c)会调用udelay(),而udelay()会内部调用loops_per_jiffy:do{result = ehci_readl(ehci, ptr);/* ... */if(result == done) return0;udelay(1); /* Internally uses loops_per_jiffy */usec--;} while(usec > 0);2.4.4Pentium时间戳计数器时间戳计数器(TSC)是Pentium兼容处理器中的一个计数器,它记录自启动以来处理器消耗的时钟周期数。