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;}定时器部份到这里就介绍完了.为了管理定时器.内核用了一个很巧妙的数据结构.值得好好的体会.。
kernelAPI
这个函数返回0表示成功。<linux/fs.h>
3.根据inode节点获得主设备号:
unsigned int iminor(struct inode *inode);
第三章:字符设备驱动
1.获得主次设备号:MAJOR(dev_t dev) MINOR(dev_t dev),MKDEV(int major,int minor)通常dev_t是32bit,高12位用于主设备号,次20bit用于此设备号。<linux/kdev_t.h>
2.获得和释放设备号
int register_chrdev_region(dev_t first,unsigned int count,char *name)//静态获得
void __free_page(struct page*)
vodd __free_pages(struct page*,int order);
13.io端口以及io内存访问
struct resource *request_resource(unsigned long first,unsigned long n,const char *name)
int cdev_del(struct cdev *)//从内核删除
int cdev_init(struct cdev *,file_operations *);
5.用户空间和内核空间数据交换<asm/uaccess.h>
unsigned long copy_to_user(void __user*to,void *from,unsigned long count)
linux开机发现会有个kworker进程规律性占用CPU负载超过50%
linux开机发现会有个kworker进程规律性占⽤CPU负载超过50%kworker 进程是内核⼯作进程,并且有很多进程是⽆害的。
内核⼯作线程可以做任何事情,例如⼀些随机的例⼦:1. 做页⾯缓存写回2. 处理某些种类的硬件事件 (如硬件中断,定时器,I / O等)3. 很多很多其他的东西要知道任何kworker在做什么,你可以看看。
1 cat /proc/<kworker_pid>/stack第⼀种思路是使⽤perf进⾏排查,执⾏以下代码1 sudo echo "l" > /proc/sysrq-trigger //将loglevel设置为12 sudo perf record -g -a sleep 10 // 记录10s内所有进程进⾏的动作,动作完成后会在当前⽬录下⽣成⼀个perf.data3 sudo perf report // 通过该命令进⾏查看sysrq可以使⽤的command66 'b' - Will immediately reboot the system without syncing or unmounting 67 your disks. 68 69'c' - Will perform a system crash by a NULL pointer dereference. 70 A crashdump will be taken if configured. 71 72'd' - Shows all locks that are held. 73 74'e' - Send a SIGTERM to all processes, except for init. 75 76'f' - Will call oom_kill to kill a memory hog process. 77 78'g' - Used by kgdb on ppc and sh platforms. 79 80'h' - Will display help (actually any other key than those listed 81 here will display help. but 'h' is easy to remember :-) 82 83'i' - Send a SIGKILL to all processes, except for init. 84 85'j' - Forcibly "Just thaw it" - filesystems frozen by the FIFREEZE ioctl. 86 87'k' - Secure Access Key (SAK) Kills all programs on the current virtual 88 console. NOTE: See important comments below in SAK section. 89 90'l' - Shows a stack backtrace for all active CPUs. 91 92'm' - Will dump current memory info to your console. 93 94'n' - Used to make RT tasks nice-able 95 96'o' - Will shut your system off (if configured and supported). 97 98'p' - Will dump the current registers and flags to your console. 99 100'q' - Will dump per CPU lists of all armed hrtimers (but NOT regular 101 timer_list timers) and detailed information about all 102 clockevent devices. 103 104'r' - Turns off keyboard raw mode and sets it to XLATE. 105 106's' - Will attempt to sync all mounted filesystems. 107 108't' - Will dump a list of current tasks and their information to your 109 console. 110 111'u' - Will attempt to remount all mounted filesystems read-only. 112 113'v' - Dumps Voyager SMP processor info to your console. 114 115'w' - Dumps tasks that are in uninterruptable (blocked) state. 116 117'x' - Used by xmon interface on ppc/powerpc platforms. 118 119'z' - Dump the ftrace buffer 120 121'0'-'9' - Sets the console log level, controlling which kernel messages 122 will be printed to your console. ('0', for example would make 123 it so that only emergency messages like PANICs or OOPSes would 124 make it to yo 根据kworker中执⾏的程序分析到有⼀些i2c传输的信息。
cancel_delayed_work的用法 -回复
cancel_delayed_work的用法-回复取消Delayed_work的用法在编程中,我们经常需要进行一些异步操作,这些操作不会立即执行,而是延迟一段时间后才会执行。
在这种情况下,我们可以使用延迟任务(Delayed_work)来处理。
Delayed_work是一种在给定的延迟时间后执行的异步任务,它可以帮助我们实现一些定时操作或在特定条件下执行某个任务。
本文将详细介绍Delayed_work的用法以及如何取消延迟任务。
第一步:理解Delayed_work的基本定义和使用Delayed_work是Linux内核中提供的一个延迟执行的机制,它允许我们将工作(work)延迟一段时间后再执行。
在Linux内核中,Delayed_work 是以结构体的形式存在,如下所示:cstruct delayed_work {struct work_struct work;struct timer_list timer;};其中,work_struct是一个包含执行任务的回调函数的结构体,timer_list 则是一个定时器结构体。
在使用Delayed_work之前,我们需要先初始化它。
可以使用INIT_DELAYED_WORK宏来初始化一个Delayed_work对象,如下所示:cstruct delayed_work my_delayed_work;INIT_DELAYED_WORK(&my_delayed_work, my_work_function);上述代码会将my_work_function函数作为回调函数传递给my_delayed_work对象,这意味着当延迟时间到达后,my_work_function函数将被执行。
第二步:设置延迟时间和调度延迟任务在使用Delayed_work之前,我们需要设定一个延迟时间,并将Delayed_work对象加入到内核的任务队列中。
我们可以使用schedule_delayed_work函数来设置延迟时间和调度Delayed_work任务,如下所示:cint schedule_delayed_work(struct delayed_work *dwork, unsigned long delay);上述函数会将dwork对象加入内核的任务队列中,并延迟delay个单位的时间后执行。
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 可以建立进程定时器,单次或者周期性定时。
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设备驱动程序学习(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位)。
init_delayed_work用法
init_delayed_work用法init_delayed_work是Linux内核中的一个函数,用于创建一个延迟执行的工作任务。
该函数的使用非常广泛,特别是在各种驱动程序中。
它的调用方法如下:int init_delayed_work(struct delayed_work *work, delayed_work_fn_t fn);其中,work是一个delayed_work结构体指针,用于指向即将创建的延迟执行工作任务。
fn是一个回调函数,用于在工作任务执行时被调用。
delayed_work结构体定义如下:struct delayed_work {struct work_struct work;struct timer_list timer;};其中,work是一个work_struct结构体,用于表示工作任务的基本信息,timer是一个timer_list结构体,用于表示延迟执行的时间。
使用init_delayed_work函数创建的延迟执行工作任务,可以使用queue_delayed_work函数将其添加到系统的工作队列中,并设置延迟执行的时间。
示例:#include <linux/workqueue.h>static struct workqueue_struct *my_wq;static struct delayed_work my_work;static void my_work_handler(struct work_struct *work) {//执行工作任务}static int __init my_init(void){//创建工作队列my_wq = create_workqueue('my_wq');//初始化延迟执行工作任务INIT_DELAYED_WORK(&my_work, my_work_handler);//将延迟执行工作任务添加到工作队列中queue_delayed_work(my_wq, &my_work,msecs_to_jiffies(1000));return 0;}static void __exit my_exit(void){//取消延迟执行工作任务cancel_delayed_work(&my_work);//销毁工作队列destroy_workqueue(my_wq);}module_init(my_init);module_exit(my_exit);在上面的示例中,我们首先创建了一个工作队列my_wq,然后使用INIT_DELAYED_WORK函数初始化了一个延迟执行工作任务my_work,并将其添加到工作队列中,延迟执行时间为1秒。
进程结构体task_struct
1. 调度数据成员(1) volatile long states;表示进程的当前状态:-TASK_RUNNING:正在运行或在就绪队列run-queue中准备运行的进程,实际参与进程调度。
-TASK_INTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,也可由其它进程通过信号(signal)或定时中断唤醒后进入就绪队列run-queue。
-TASK_UNINTERRUPTIBLE:处于等待队列中的进程,待资源有效时唤醒,不可由其它进程通过信号(signal)或定时中断唤醒。
-TASK_ZOMBIE:表示进程结束但尚未消亡的一种状态(僵死状态)。
此时,进程已经结束运行且释放大部分资源,但尚未释放进程控制块。
-TASK_STOPPED:进程被暂停,通过其它进程的信号才能唤醒。
导致这种状态的原因有二,或者是对收到SIGSTOP、SIGSTP、SIGTTIN或SIGTTOU信号的反应,或者是受其它进程的ptrace系统调用的控制而暂时将CPU交给控制进程。
-TASK_SWAPPING: 进程页面被交换出内存的进程。
(2) unsigned long flags;进程标志:-PF_ALIGNWARN 打印“对齐”警告信息。
-PF_PTRACED 被ptrace系统调用监控。
-PF_TRACESYS 正在跟踪。
-PF_FORKNOEXEC 进程刚创建,但还没执行。
-PF_SUPERPRIV 超级用户特权。
-PF_DUMPCORE dumped core。
-PF_SIGNALED 进程被信号(signal)杀出。
-PF_STARTING 进程正被创建。
-PF_EXITING 进程开始关闭。
-PF_USEDFPU 该进程使用FPU(SMP only)。
-PF_DTRACE delayed trace (used on m68k)。
(3) long priority;进程优先级。
Priority的值给出进程每次获取CPU后可使用的时间(按jiffies计)。
linux-2[1].6.31-探秘Linux SysRq魔法键
记住,在触发 sysrq 命令后将日志级别返回到正常状态
有问题的时候向谁咨询 ~~~~~~~~~~~~~~~~~~~
我会尽快解答关于注册系统的问题。 - Crutcher
5
致谢 ~~~
Mydraal <vulpyne@> 编写 Adam Sulmicki <adam@> 进行了更新 Jeremy M. Dolan <jmd@> 2001/01/28 10:15:59 进行了更新 Crutcher Dunnavant <crutcher+kernel@> 添加了新的信息
创建 sysrq_key_op 后,调用内核函数 register_sysrq_key(int key, struct sysrq_key_op *op_p),该函数会将'op_p'所指的操作注册到表中键值'key'处,如果表中的对应位置为空的 话。模块卸载时,必须调用 unregister_sysrq_key(int key, struct sysrq_key_op *op_p),该函数 会移除键值'key'中的'op_p'指向的操作结构,如果操作结构注册在对应项中的话。这是为了 防止对应项在你注册后被覆盖。
t'E'rm 和 k'I'll 用于一些进程脱离你的控制而你无法将它们杀死,而它们会生成其它进 程的场合。
"'J'ust thaw it"用于因为 FIFREEZE ioctl 冻结一个(可能是 root)文件系统而导致系统 没有反应的场合。
有时候使用 SysRq 后似乎卡住了,这时该怎么做 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Linux操作系统分析-Linux中的时钟和定时测量
12
在init_IRQ()中初始化时钟中断频率
此后,只要允许处理时钟中断,约每10ms就会产生一个时钟中断 1tick约为10ms
Linux Operating Systems Analysis
13
如何计算CPU的时钟频率CLK
Linux在初始化的时候,利用可编程间隔定时 器获得CPU的频率 观察calibrate_tsc(),了解如何计算CPU的频 率
Linux Operating Systems Analysis
36
Linux Operating Systems Analysis
5
实时时钟RTC
基本上所有的PC都包含实时时钟
独立于CPU与所有其他芯片 依靠一个独立的小电池供电给RTC中的振荡器
即使关闭PC电源,还会继续运转
与CMOS RAM往往集成在一个芯片内
例如:Motorala 146818
run_timer_list
下半部分timer_bh()调用run_timer_list()检查到 期的动态定时器,包括:
执行动态定时器 更新链表 观察run_timer_list()
Linux Operating Systems Analysis
31
动态定时器的应用
使用schedule_timeout()可以使进程被延迟 (睡眠一段时间) 观察schedule_timeout()并看一个内核应用实 例
维护软定时器处理
Linux Operating Systems Analysis
21
更新时间和日期
用户程序从下面这个变量中获得当前时间和日 期
task_struct结构描述
0-0xBFFFFFFF for user-thead
0-0xFFFFFFFF for kernel-thread
----------------------------------------
volatile long need_resched;
调度标志,表示该进程是否需要重新调度,
若非 0, 则当从内核态返回到用户态,会发生调度
----------------------------------------
int lock_depth; 锁深度
long nice; 进程的基本时间片
----------------------------------------
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
int ngroups; //记录进程在多少个用户组中
gid_t groups[NGROUPS]; //记录进程所在的组
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;//进程的权能,分别是有效位集合,继承位集合,允许位集合
==========================================================================
打开/include/linux/sched.h可以找到task_struct 的定义
struct task_struct {
volatile long state; 说明了该进程是否可以执行,还是可中断等信息
设备驱动常用内核机制与总结
select系统调用(3)
timeout取不同的值,该调用就表现不同的性质: timeout为0,调用立即返回; timeout为NULL,调用就阻塞,直到知道有文件描述 符就绪; timeout为正整数,就是一般的定时器。 返回值
正常情况下返回就绪的文件描述符个数; 经过了timeout时长后仍无设备准备好,返回值为0; 如果select被某个信号中断,返回-1并设置errno为EINTR 如果出错,返回-1并设置相应的errno。
25
Linux内核时钟
Linux系统时钟以读取的硬件时钟为起始点, 根据系统启动后的滴答数来计算时间,系 统内的所有计时均基于它。系统用一个全 局变量jiffies表示,该变量每个时钟周期更 新一次,即表示系统自启动以来的时钟滴 答数目。
19
异步通知应用程序范例
... signal(SIGIO, input_handler); //第3步 fcntl(STDIN_FILENO, F_SETOWN, getpid()); //第1步 oflags = fcntl(STDIN_FILENO, F_GETFL); //以下两 个为第2步 fcntl (STDIN_FILENO, F_SETFL, oflags | FASYNC); while(1); ...
20
异步通知应用程序范例
/*接收到异步读信号后的动作*/ void input_handler(int signum) { printf("receive a signal from tty0,signalnum:%d\n",signum); }
21
信号的释放(驱动中实现)
为了使设备支持异步通知机制,驱动程序要完成 以下3项工作: 1,支持F_SETOWN命令,能在这个控制命令中设 置filp->f_owner为对应进程ID。此项工作已由 内核完成,驱动无须处理。 2,支持F_SETFL命令的出来,每当FASYNC标志改 变时,驱动程序中的fasync()函数将得以执行。 因此驱动程序中应实现fasync()函数。 3,在设备资源可获得时,用kill_fasync()函数激发 相应的信号。
Linux内核中工作队列(work_queue)的操作
cwq->thread = p;
return p;
}
static int worker_thread(void *__cwq)
{
struct cpu_workqueue_struct *cwq = __cwq;
// 声明一个等待队列
DECLARE_WAITQUEUE(wait, current);
// 参数初始化定义, 而该宏用在程式之中对工作结构赋值
#define INIT_WORK(_work, _func, _data) \
do { \
INIT_LIST_HEAD(&(_work)->entry); \
(_work)->pending = 0; \
/*
* The externally visible workqueue abstraction is an array of
* per-CPU workqueues:
*/
// 工作队列结构
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq;
int cpu)
{
// 每个CPU的工作队列
struct cpu_workqueue_struct *cwq = per_cpu_ptr(wq->cpu_wq, cpu);
struct task_struct *p;
spin_lock_init(&cwq->lock);
{
...
keventd_wq = create_workqueue("events");
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 字段。
Linux下timer的使用介绍
1、alarm-------------------------------------------如果不要求很精确的话,用alarm()和signal()就够了unsigned int alarm(unsigned int seconds)函数说明: alarm()用来设置信号SIGALRM在经过参数seconds指定的秒数后传送给目前的进程。
如果参数seconds为0,则之前设置的闹钟会被取消,并将剩下的时间返回。
返回值: 返回之前闹钟的剩余秒数,如果之前未设闹钟则返回0。
alarm()执行后,进程将继续执行,在后期(alarm以后)的执行过程中将会在seconds秒后收到信号SIGALRM并执行其处理函数。
#include <stdio.h>#include <unistd.h>#include <signal.h>void sigalrm_fn(int sig){printf("alarm!/n");alarm(2);return;}int main(void){signal(SIGALRM, sigalrm_fn);alarm(1);while(1) pause();}2、setitimer()-------------------------------------------int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));setitimer()比alarm功能强大,支持3种类型的定时器:ITIMER_REAL : 以系统真实的时间来计算,它送出SIGALRM信号。
ITIMER_VIRTUAL: 以该进程在用户态下花费的时间来计算,它送出SIGVTALRM信号。
ITIMER_PROF : 以该进程在用户态下和内核态下所费的时间来计算,它送出SIGPROF信号。
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 个数据行. 这些数据行表示当前代码运行的环境; 第一个由读文件操作产生并且其他的由定时器. 下列的输出在编译内核时被记录:
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
linux 内核定时器timer_list
linux 内核使用timer_list 结构体当作定时器。
#include “linux/timer.h”
#include “linux/module.h”MODULE_LICENSE(“GPL”);//不加这句话,虽然不影响功能,但有时候程序执行时会打印错误,类似Disabling lock debugging //due to kernel taint 之类的话struct timer_list tm;static int num;static void func(){ num++; mod_timer(&tm,jiffies+1*HZ); //timer 一旦超时,就
会执行fuc 函数,然后永远的休眠,//所以如果没有这mod_timer,hello world 只会执行一次,也就是timer 第一次超时时执行的那次。
//mod_timer
可以激活timer。
如果你没有add_timer(),激活也没用printk(“hello,world\n ,%d”,num);}static int timer_init(void){ init_timer(&tm); //初始化定时
器,必须在所有下面复制操作前进行定时器初始化tm.expires = jiffies +1*HZ;
//超时1 秒,执行function tm.function = func; //超时后执行的函数add_timer(&tm); //将定时器加入定时器等待队列中return 0;}static void timer_destory(void){ del_timer(&tm); printk(“remove timer\n”);}tips:感谢大家
的阅读,本文由我司收集整编。
仅供参阅!。