Chapter 7. Time, Delays, and Deferred Work
时间、延迟及延缓操作
1. 时钟中断是由系统定时硬件产生的,HZ在linux/param.h中定义。
2. jiffies的使用
jiffies 是一个unsigned long 变数要么和jiffies_64相同要么和他的低32位相同,我们一般使用的是jiffies
linux/jiffies.h
使用时候一般包含linux/sched.h就可以,jiffies和jiffies_64一般被看为只读变数
1) 时间前后判断函数
#include <linux/jiffies.h>
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);
2) 和用户空间的时间进行转换(struct timeval-s+ms, 和 struct timespec-s+ns)
#include <linux/time.h>
unsigned long timespec_to_jiffies(struct timespec *value);
void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);
unsigned long timeval_to_jiffies(struct timeval *value);
void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);
3)如果要直接访问64位的jiffies需要用u64 get_jiffies_64(void);
用户空间获得HZ可以通过读取/proc/interrupts 用/proc/interrupts 除以/proc/uptime文件报告的系统运行时间,即可以获得确切的HZ数。
3. 处理器指定特定的寄存器-如:TSC(Timestamp counter,时间戳计数器)
内核空间和用户空间都可以访问它,在包含头文件<asm/msr.h>(x86专用头文件”machine-specific registers ”)之后就可以使用如下宏:
rdtsc(low32,high32);
rdtscl(low32);
rdtscll(var64);
一般的tsc应用中只读低32位,1g的处理器每4.2秒才会溢出一次
4.某些平台提供类似的功能,在内核头文件中还有一个与体系结构无关的函数可以替代rdtsc--get_cycles, 它定义在asm/timex.h中(由linux/timex.h包含)原型:#include <linux/timex.h>
cycles_t get_cycles(void);
还可以通过内嵌汇编代码实现于rdtsc相同的功能如:
#define rdtscl(dest) \
__asm__ __volatile__(“mfc0 %0, $9 ; nop” : “=r” (dest))
5. 获取当前时间
#include <linux/time.h>
unsigned long mktime (unsigned int year, unsigned int mon, unsigned int day,
unsigned int hour,unsigned int min, unsigned int sec);
linux/time.h 导出do_gettimeofday该函数用秒或者微秒来填充一个指向struct timeval 的指针变量和系统调用gettimeofday 一样,所以他在很多体系结构下拥有接近微秒级的分辨率!
当前时间还可以用xtime变量获得,但是精度要差些,内核提供了一个辅助函数,由于无法保证访问的原子性所以最好使用这个函数
#include <linux/time.h>
Struct timespec current_kernel_time(void) ;
6. 延迟执行
延迟执行需要考虑的一件重要的事情就是相比时钟滴答并考虑各种平台上的HZ的范围,我们是不应该依赖时钟滴答来实现,即长于时钟滴答的不会因为她的分辨率低而导致问题,而非常断的延迟可以采用软件方式
1) 长延迟型
a.忙等待,我们可以用如下代码监视jiffies的办法实现
While(time_before(jiffies,j1)
cpu_relax() ;
但是缺点很多,如:严重降低系统性能,其次在cpu繁忙的系统中很可能有大很多的延迟,其中的cpu_relax( )是一个和平台相关的函数。这个方法在进程较多的系统或者抢占式内核的系统中都容易出现问题,对 cpu_relex 的调用使用了一个特定于体系的方式来说, 你此时没有在用处理器做事情. 在许多系统中它根本不做任何事; 在对称多线程(" 超线程" ) 系统中, 可能让出核心给其他线程. 在如何情况下, 无论何时有可能, 这个方法应当明确地避免.。
b.让出处理器的办法
while (time_before(jiffies, j1)) {
schedule( );
}
但是由于系统中一直有个进程处于可运行状态,而且空闲进程idle(进程号0,也称swapper)从不运行,同时cpu一直保持高占用率,其他缺点和前面的忙等待类似
c.超时
用wait_event_timeout和wait_event_interruptible_timeout函数,他会在给定的等待队列上睡眠,在超时的时候返回,但在系统满负荷的时候,真正的延时很容易超过预计的。因为读取进程的在等待超时的并不在运行队列中。但是内核依然为我们提供了schedule_timeout函数这样可以避免声明很多多余的等待队列头:
#include <linux/sched.h>
signed long schedule_timeout(signed long timeout);
这个函数要求使用者先设置当前进程的状态,因此典型的用法如下表示:
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout (delay);
其实wait_event_interruptible_timout在内部依赖于schedule_timeout函数,值得再次提出的是从超时到进程真正的被执行还要有额外的一段时间。
要是想实现没有中断的延迟则把状态设置为TASK_UNINTERRUPTIBLE,如果忘记改状态则对schedule_timeout的调用和schedule是一样的,内核定时器不会起作用。
2) 短延迟型
#include <linux/delay.h>void ndelay(unsigned long nsecs);void udelay(unsigned long usecs);void mdelay(unsigned long msecs);这几个函数可以很好的完成短延时任务他们分别指定的是纳秒、微秒、毫秒级别的时间,他们的实现包含在asm/delay.h中,具体实现和架构有关系。这3个函数均是忙等待函数,所以尽量使用细粒度粗的函数
实现毫秒级别的延迟还有个办法,这个方法不涉及忙等待:
<linux/delay.h> declares these functions:
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds)
通常我们如果可以容忍比所请求的时长长点的延迟,则应当使用schedule_timeout、sleep或者ssleep。
6.内核定时器
如果我们需要在将来的某个时间点调度执行某个动作,同时在时间点之前不会阻塞当前进程,则可以用内核定时器。内核定时器可以在未来的某个时间点执行某个动作。典型应用:关闭软驱马达。
内核定时器是一个数据结构,他告诉内核在特定时间点上用用户定制的特定参数去执行一个用户定义的函数。内核定时器常常是作为“软件中断”的结果而运行的。很多动作要在进程上下文中才能执行,如果处于进程上下文之外(如:中断上下文),则必须遵守以下原则:
1) 不容许访问用户空间;
2) Current指针在原子模式下没有任何意义,也是不可用的,因为代码和和被中断的进程没有任何关系。
3) 不能执行休眠或者调度,也不能调度任何可能引起休眠的函数(如kmalloc(…,GFP_KERNEL))
,信号量也没法使用,因为可能引起睡眠。
内核代码可以通过调用in_interrupt来判断自己是否处于中断上下文,如果返回非0就是处于中断上下文,而且无论是硬件中断还是软件中断。
还有就是in_atomic()当调度不容许时候,后者返回值也非0值,调度不容许在任何软件中断和硬件中断上下文中以及拥有自旋锁的地点,后一种情况虽然current可用但是如果使用会导致调度所以使用是有一定要考虑清楚到底该用哪个,这两个函数都是在asm/hardirq.h中声明。
另一个重要特性是,任务可以在自己注册在稍后的时间重新运行,在SMP系统中定时器会由注册它的同一个CPU执行,这样可以尽量获得缓存的局域性(locality).
切记:即使在单处理器上定时器也可能是竞态的潜在来源
定时器API:
#include <linux/timer.h>
struct timer_list {
/* ... */
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
};
void init_timer(struct timer_list *timer);
struct timer_list TIMER_INITIALIZER(_function, _expires, _data);
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
数据结构中给出的字段只是可由定时器代码以外代码访问,expires字段表示期望定时器的jiffies值;到达这个jiffies后会调用function并把data作为参数
其他几个API:
int mod_timer(struct timer_list *timer, unsigned long expires);
Updates the expiration time of a timer, a common task for which a timeout timer is used (again, the motor-off floppy timer is a typical example). mod_timer can be called on inactive timers as well, where you normally use add_timer.
int del_timer_sync(struct timer_list *timer);
Works like del_timer, but also guarantees that when it returns, the timer function is not running on any CPU. del_timer_sync is used to avoid race conditions on SMP systems and is the same as del_timer in UP kernels. This function should be preferred over del_timer in most situations. This function can sleep if it is called from a nonatomic context but busy waits in other situations. Be very careful about calling del_timer_sync while holding locks; if the timer function attempts to obtain the same lock, the system can deadlock. If the timer function reregisters itself, the caller must first ensure that this reregistration will not happen; this is usually accomplished by setting a "shutting down" flag, which is checked by the timer function.
int timer_pending(const struct timer_list * timer);
Returns true or false to indicate whether the timer is currently scheduled to run by reading one of the opaque fields of the structure.
7. 内核定时器的实现
内核定时器必须满足如下假定:
1) 定时器的管理必须尽可能的轻量级;
2) 其设计必须在定时器大量增加时有很好的伸缩性;
3) 大部分定时器都会在几秒或几分钟内到期,而很少有长期延迟的定时器;
4) 定时器应该在它注册的同一CPU上运行。
内核开发者李咏per-CPU机构,在time_list结构的base字段中包含指向该结构的指针,如果base为NULL则定时器尚未调度运行;否则指针会告诉我们指向那个数据结构也就是哪个CPU
不管内核在何时注册了一个定时器(通过add_time或者mod_timer), 其操作最终由internal_add_timer(kernel/timer.c)执行,该函数又会将新的定时器添加到当前CPU关联的“级联表”中的定时器双向链表。
8. tasklet
tasklet在很多方面类似内核定时器:他们始终在中断期间运行,始终会在调度他们的同一个CPU上运行,而且都接收一个unsigned log 参数。
不同点:tasklet无法在某个特定时间运行,调度一个tasklet是内核选择在后面一个安全的时间来执行,这个行为对于中断处理非常有用
和内核定时器相似的是,tasklet也会在“软件中断”上下文以原子模式执行。软件中断是打开硬件中断的同时执行一些异步任务的内核机制。
#include <linux/interrupt.h>
struct tasklet_struct {
/* ... */
void (*func)(unsigned long);
unsigned long data;
};
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
DECLARE_TASKLET(name, func, data);
DECLARE_TASKLET_DISABLED(name, func, data);
Tasklet 提供了一些有意思的特性:
1) 一个tasklet可以在稍后被禁止或者重新启用;只有启用次数和禁止次数相等的时候,tasklet才会别执行;
2) 和定时器一样,tasklet可以注册自身;
3) tasklet可被调度以在通常优先级或者高优先级执行,高优先级tasklet总会首先执行;
4) 如果系统负荷不重,tasklet会被立刻执行,但始终不会晚于下一个tick
5) 一个tasklet可以和其它tasklet并发,但对自己来说永远是串行的。
void tasklet_disable(struct tasklet_struct *t);
void tasklet_disable_nosync(struct tasklet_struct *t);
void tasklet_enable(struct tasklet_struct *t);
void tasklet_schedule(struct tasklet_struct *t);
void tasklet_hi_schedule(struct tasklet_struct *t);
void tasklet_kill(struct tasklet_struct *t);
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/human3000/archive/2007/12/08/1924370.aspx