首页 > 代码库 > 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位数值读到232位变量
Rdtscl(low32)//只读低32
Rdtscll(var64)


?如何度量时间差,如何比较时间
?分别读取jiffies
       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);
?分别读取TSC

?如何将操作延迟指定的一段时间
?长延时
       最笨的方法(忙等待)
             –unsigned long j = jiffies + jit_delay*HZ;
             –while (jiffies<j);
               ?这个忙等待循环在延迟期间会锁住整台计算机,因为调度器不会中断运行在内核空间的进程
               ?更糟糕的是,如果在进入循环之前关闭了中断, jiffies值就不会得到更新,那么while循环的条件就永真.不得不对机器进行重启.
       稍微好点的方法
       ?它允许其他进程在延迟的时间间隔内运行,尽管这种方法不能用于实时任务或者其他时间要求很严格的场合:
            –while (jiffies<j)
                   schedule();
      ?当前任务除了释放CPU之外不做任何工作,但是它仍在任务队列中. 如果它是系统中唯一的可运行的进程,它还会被运行(系统调用调度器,调度器还是同一个进程,
       此进程又再调用调度器
,然后...).换句话说,机器的负载(系统中运行的进程个数)至少为1,idle空闲进程(进程为0,历史性的被称"swapper")绝不会被运行.
      尽管这个问题看来无所谓
,当系统空闲时运行idle空闲进程可以减轻处理器负载,降低处理器温度,延长处理器寿命,如果是手提电脑,电池的寿命也可延长.
      而且
,延迟期间实际上进程是在执行的,因此这段延迟还是记在它的运行时间上的.

       更好点的方法
       ?在内核态下让进程进入睡眠态的正确方式是设置timeout睡眠在一个等待队列上. 调度器每次运行时都会比较进程的timeout 值和当前的jiffies,
       只要没有系统事件唤醒进程
使它离开等待队列,那么一旦当前时间达到timeout,调度器就唤醒睡眠进程.这种延迟实现如下:
              –struct wait_queue *wait=NULL;
              –current->timeout=j;
              –interruptible_sleep_on(&wait);
       ?注意要调用interruptible_sleep_on而不是sleep_on,因为调度器不检查不可中断的进程的timeout值-这种进程的睡眠即使超时也不被中断.
       因此
,如果你调用sleep_on,就无法中断该睡眠进程.

       好上加好
       ?如果目的只是插入延迟,这里并没有必要使用等待队列.实际上,如下所示,current->timeout而不用等待队列就可以达到目的:
        ?current->timeout=j;
        ?current->state=TASK_INTERRUPTIBLE;
        ?schedule();
        ?current->timeout=0;/* 重置timeout 值*/ 

       ?这段语句是在调用调度器之前先改变进程的状态.进程的状态被标记为TASK_INTERRUPTIBLE(TASk_RUNNING相对应),
        这保证了该进程
在超时前不会被再次运行(但其他系统事件如信号可能会唤醒它).
        这种延
迟方法在文件/proc/jitself中实现了-这个名字强调了,进程是自己进入睡眠的,而不是通过调用sleep_on.

?短延时
     ?有时驱动程序需要非常短的延迟来和硬件同步.此时,使用jiffies值就不能达到目的.
     ?这时就要用内核函数ndelay,udelay,以及mdelay,使用软件循环将执行延迟指定数量的微秒数,是个忙等待函数,在延迟的时间段内无法运行其他的任务.
     ?实际上,当前没有平台获得了纳秒的精度.同时,实际的延时比预设的来的长并不会导致问题,因为要求短延时的往往是硬件,而要求的往往是至少需要多少时间.

?如何调度异步函数到指定的时间之后执行
?定时器

#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 是软中断的一种.
#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在系统任何地方都不在运行.
?void tasklet_disable_nosync(struct tasklet_struct *t);
   – 禁止这个 tasklet,不过它无须在返回前等待tasklet执行完毕.这么做往往不太安全,因为你无法估计该tasklet是否仍在执行.
      当它返回
, 这个tasklet被禁止并且不会在以后被调度直到重新使能.
?void tasklet_enable(struct tasklet_struct *t);
   – 使能一个之前被禁止的 tasklet.如果这个 tasklet已经被调度, 它会很快运行.
       一个对
tasklet_enable 的调用必须匹配每个对 tasklet_disable调用,因为内核跟踪每个 tasklet"禁止次数".
?void tasklet_schedule(struct tasklet_struct *t);
   – 调度 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前它重新调度它自己.

?工作队列
工作队列它把工作推后,交由一个特殊的内核线程去执行,因此可以睡眠.
#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读书笔记_时间,延迟和延缓操作