首页 > 代码库 > LDD读书笔记_时间,延迟和延缓操作
LDD读书笔记_时间,延迟和延缓操作
Agenda
?如何获得当前时间
?如何获得当前时间
?如何度量时间差,如何比较时间
?如何将操作延迟指定的一段时间
?如何调度异步函数到指定的时间之后执行
?如何获得当前时间
?如何获得当前时间
?HZ: 宏定义,每秒的时间中断次数
?Jiffies变量:系统引导时初始化为0,每发生一次时间中断就加1
–#include <linux/jiffies.h>
–unsigned long j, stamp_1, stamp_half, stamp_n;
–j = jiffies;/* current value */
–stamp_1 = j + HZ;/* 1 s */
–stamp_half = j + HZ/2;/* 0.5s */
–stamp_n = j + n*HZ/1000;/* n ms */
?TSC(timestamp counter)寄存器, pentium开始提供, 64位,记录CPU时间周期
–Rdtsc(low32, high32)//宏定义, 将64位数值读到2个32位变量
–Rdtscl(low32)//只读低32位
–Rdtscll(var64)
?如何度量时间差,如何比较时间
?分别读取jiffies
Jiffies溢出怎么办?分别读取TSC
Jiffies溢出怎么办
?int time_after(unsigned long a, unsigned long b);
?int time_before(unsigned long a, unsigned long b);
?int time_after_eq(unsigned long a, unsigned long b);
?int time_before_eq(unsigned long a, unsigned long b);
?如何将操作延迟指定的一段时间
?长延时
最笨的方法(忙等待)
–unsigned long j = jiffies + jit_delay*HZ;
–while (jiffies<j);
?这个忙等待循环在延迟期间会锁住整台计算机,因为调度器不会中断运行在内核空间的进程
?更糟糕的是,如果在进入循环之前关闭了中断, jiffies值就不会得到更新,那么while循环的条件就永真.不得不对机器进行重启.
稍微好点的方法
更好点的方法
好上加好
稍微好点的方法
?它允许其他进程在延迟的时间间隔内运行,尽管这种方法不能用于实时任务或者其他时间要求很严格的场合:
–while (jiffies<j)
schedule();
?当前任务除了释放CPU之外不做任何工作,但是它仍在任务队列中. 如果它是系统中唯一的可运行的进程,它还会被运行(系统调用调度器,调度器还是同一个进程,
此进程又再调用调度器,然后...).换句话说,机器的负载(系统中运行的进程个数)至少为1,而idle空闲进程(进程为0,历史性的被称为"swapper")绝不会被运行.
尽管这个问题看来无所谓,当系统空闲时运行idle空闲进程可以减轻处理器负载,降低处理器温度,延长处理器寿命,如果是手提电脑,电池的寿命也可延长.
而且,延迟期间实际上进程是在执行的,因此这段延迟还是记在它的运行时间上的.
此进程又再调用调度器,然后...).换句话说,机器的负载(系统中运行的进程个数)至少为1,而idle空闲进程(进程为0,历史性的被称为"swapper")绝不会被运行.
尽管这个问题看来无所谓,当系统空闲时运行idle空闲进程可以减轻处理器负载,降低处理器温度,延长处理器寿命,如果是手提电脑,电池的寿命也可延长.
而且,延迟期间实际上进程是在执行的,因此这段延迟还是记在它的运行时间上的.
更好点的方法
?在内核态下让进程进入睡眠态的正确方式是设置timeout后睡眠在一个等待队列上. 调度器每次运行时都会比较进程的timeout 值和当前的jiffies值,
只要没有系统事件唤醒进程使它离开等待队列,那么一旦当前时间达到timeout值,调度器就唤醒睡眠进程.这种延迟实现如下:
只要没有系统事件唤醒进程使它离开等待队列,那么一旦当前时间达到timeout值,调度器就唤醒睡眠进程.这种延迟实现如下:
–struct wait_queue *wait=NULL;
–current->timeout=j;
–interruptible_sleep_on(&wait);
?注意要调用interruptible_sleep_on而不是sleep_on,因为调度器不检查不可中断的进程的timeout值-这种进程的睡眠即使超时也不被中断.
因此,如果你调用sleep_on,就无法中断该睡眠进程.
因此,如果你调用sleep_on,就无法中断该睡眠进程.
好上加好
?如果目的只是插入延迟,这里并没有必要使用等待队列.实际上,如下所示,用current->timeout而不用等待队列就可以达到目的:
?current->timeout=j; ?current->state=TASK_INTERRUPTIBLE; ?schedule(); ?current->timeout=0;/* 重置timeout 值*/
?这段语句是在调用调度器之前先改变进程的状态.进程的状态被标记为TASK_INTERRUPTIBLE(与TASk_RUNNING相对应),
这保证了该进程在超时前不会被再次运行(但其他系统事件如信号可能会唤醒它).
这种延迟方法在文件/proc/jitself中实现了-这个名字强调了,进程是“自己进入睡眠的”,而不是通过调用sleep_on.
这保证了该进程在超时前不会被再次运行(但其他系统事件如信号可能会唤醒它).
这种延迟方法在文件/proc/jitself中实现了-这个名字强调了,进程是“自己进入睡眠的”,而不是通过调用sleep_on.
?短延时
?如何调度异步函数到指定的时间之后执行
API
?有时驱动程序需要非常短的延迟来和硬件同步.此时,使用jiffies值就不能达到目的.
?这时就要用内核函数ndelay,udelay,以及mdelay,使用软件循环将执行延迟指定数量的微秒数,是个忙等待函数,在延迟的时间段内无法运行其他的任务.
?实际上,当前没有平台获得了纳秒的精度.同时,实际的延时比预设的来的长并不会导致问题,因为要求短延时的往往是硬件,而要求的往往是至少需要多少时间.
?如何调度异步函数到指定的时间之后执行
?定时器
定时器相关的API
?Tasklet
API
#include <linux/timer.h> struct timer_list { struct timer_list *next; /*不要直接修改它 */ struct timer_list *prev; /*不要直接修改它 */ unsigned long expires; /* timeout 超时值,以jiffies 值为单位 */ unsigned long data; /* 传递给定时器处理程序的参数 */ void (*function)(unsigned long); /* 超时时调用的定时器处理程序 */ }; #define TIMER_INITIALIZER(_function, _expires, _data)
定时器相关的API
void init_timer(struct timer_list *timer); void add_timer(struct timer_list * timer); 初始化完timer_list 结构,add_timer函数就将它插入一张有序表中. int del_timer(struct timer_list * timer); 如果需要在定时器超时前将它从列表中删除,应调用del_timer 函数.但当定时器超时时,系统会自动地将它从列表中删除.int mod_timer(struct timer_list *timer, unsigned long expires) 修改timer, 相当于 del_timer(timer); timer->expires = expires; add_timer(timer); 的操作 <span style="color:#FF0000;"><strong>但是, 在考虑并发的情况下, mod_timer才是修改timeout的安全的方法, 因为add_timer不能去修改一个正在running的timer.</strong></span> Note: 定时器中断是软中断
?Tasklet
tasklet和内核定时器很相似,唯一的区别是我们不能要求tasklet在某个给定的时间执行,tasklet典型的应用是在中断服务函数中,硬件中断时要求尽可能快的管理硬件中断,
而大部分的数据管理可以安全的延迟到后期执行. Tasklet 是软中断的一种.
而大部分的数据管理可以安全的延迟到后期执行. Tasklet 是软中断的一种.
#include <linux/interrupt.h> struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); unsigned long data; }
API
?void tasklet_disable(struct tasklet_struct *t);
– 这个函数禁止给定的 tasklet. tasklet可能仍然被 tasklet_schedule调度, 但是它的执行被延后直到这个 tasklet被再次使能.
如果这个 tasklet 当前在运行,这个函数忙等待直到这个tasklet退出;因此, 在调用tasklet_disable后, 你可以确保这个 tasklet在系统任何地方都不在运行.
如果这个 tasklet 当前在运行,这个函数忙等待直到这个tasklet退出;因此, 在调用tasklet_disable后, 你可以确保这个 tasklet在系统任何地方都不在运行.
?void tasklet_disable_nosync(struct tasklet_struct *t);
– 禁止这个 tasklet,不过它无须在返回前等待tasklet执行完毕.这么做往往不太安全,因为你无法估计该tasklet是否仍在执行.
当它返回, 这个tasklet被禁止并且不会在以后被调度直到重新使能.
当它返回, 这个tasklet被禁止并且不会在以后被调度直到重新使能.
?void tasklet_enable(struct tasklet_struct *t);
– 使能一个之前被禁止的 tasklet.如果这个 tasklet已经被调度, 它会很快运行.
一个对 tasklet_enable 的调用必须匹配每个对 tasklet_disable的调用,因为内核跟踪每个 tasklet的"禁止次数".
一个对 tasklet_enable 的调用必须匹配每个对 tasklet_disable的调用,因为内核跟踪每个 tasklet的"禁止次数".
?void tasklet_schedule(struct tasklet_struct *t);
– 调度 tasklet执行. 如果一个 tasklet在它有机会运行前被再次调度,它只运行一次.但是, 如果他在运行中被调度, 它在完成后再次运行;
这保证了在其他事件被处理当中发生的事件收到应有的注意.这个做法也允许一个 tasklet重新调度它自己.
这保证了在其他事件被处理当中发生的事件收到应有的注意.这个做法也允许一个 tasklet重新调度它自己.
?void tasklet_hi_schedule(struct tasklet_struct *t);
– 调度 tasklet在更高优先级执行. 当软中断处理运行时, 它处理高优先级tasklet 在其他软中断之前, 包括"正常的" tasklet.
?void tasklet_kill(struct tasklet_struct *t);
– 这个函数确保了这个 tasklet没被再次调度来运行; 它常常被调用当一个设备正被关闭或者模块卸载时.如果这个 tasklet被调度来运行, 这个函数等待直到它已执行.
如果这个 tasklet 重新调度它自己,你必须阻止在调用 tasklet_kill前它重新调度它自己.
如果这个 tasklet 重新调度它自己,你必须阻止在调用 tasklet_kill前它重新调度它自己.
?工作队列
工作队列它把工作推后,交由一个特殊的内核线程去执行,因此可以睡眠.
#include <linux/workqueue.h> struct work_struct { struct cpu_workqueue_struct *cpu_wq; struct list_head list; const char *name; int singlethread; int freezeable; /* Freeze threads during suspend */ #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
API
?工作队列必须在使用前创建
–struct workqueue_struct *create_workqueue(const char *name);
–struct workqueue_struct *create_singlethread_workqueue(const char*name);
?提交一个任务给一个工作队列,你需要填充一个work_struct结构.如下:
–DECLARE_WORK(name, void (*function)(void *), void *data);
?这里 name是声明的结构名称, function是从工作队列被调用的函数,以及 data 是一个传递给这个function的参数
?如果在运行时需要建立 work_struct结构, 使用下面 2个宏定义:
–INIT_WORK(struct work_struct *work, void (*function)(void *), void*data);
–PREPARE_WORK(struct work_struct *work, void (*function)(void *),void *data);
?提交工作给一个工作队列
–int queue_work(struct workqueue_struct *queue, structwork_struct *work);
–int queue_delayed_work(struct workqueue_struct *queue,struct work_struct *work, unsigned long delay);
?取消一个挂起的工作队列入口
–int cancel_delayed_work(struct work_struct *work);
?用完一个工作队列,可以去掉它
–void destroy_workqueue(struct workqueue_struct *queue);
三者的应用
?对时间有比较精确要求,用timer
?对时间精度要求不高,但又要运行的比较迅速,用tasklet, 一般用在中断响应中
?允许sleep的可以用workqueue
LDD读书笔记_时间,延迟和延缓操作
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。