Linux内核中的时间处理

在Linux内核中,高度依赖于时间信息(不管这个时间是精确的或非精确的),比如硬件驱动中的延时、延后访问等等。在内核中通过定时器来精确的度量时间,那么这个定时的时间间隔取决于HZ这个值,它是与体系结构相关的一个常数,在x86平台下,这个值默认为1000,HZ这个时它表示的是定时器中断的频率,也就是说HZ为1000,即1秒钟要发生1000次中断,也就说是定时器定时的时间间隔为1毫秒,HZ值设的越大,定时的时间间隔就越小,反之就越大。


jiffies有时称为时钟滴答,记录系统启动之后,定时器中断的次数,也就是说如果定时器发生了中断,jiffies值就会被加1,jiffies变量被定义为unsigned long型,如果在32位架构上只能连续工作大约49.7天(HZ为1000情况下,即每秒发生1000次中断),这显然在某些场合是达不到要求的。为此Linux提供了一个jiffies_64的变量,它是一个64位的变量,需要注意的是在32架构上访问jiffies_64并不是原子操作的,通常驱动程序开发中使用jiffies就行了,因为它的访问速度更快,而且是原子操作的,实际上,在32位架构上jiffies就是jiffies_64的低32位,因为链接器在链接时会将他们链接到相同的地址上。


1. 对jiffies的访问
访问jiffies这个变量需要包含头文件linux/jiffies.h,通过只需要包含linux/sched.h这头文件就行了,因为在sched.h中包含了头文件jiffies.h,例如:

unsigned long delay;
delay = jiffies + HZ;	/* delay表示jiffies的未来1秒 */
while (jiffies < delay)	/* 在这里会延时1秒 */
	;

Linux给我们提供了一些宏用于对jiffies的判断:

time_after(a, b);	如果a在b之后(a > b),返回真,否则返回假。
time_before(a, b);	如果a在b之前(a < b),返回真,否则返回假。
time_after_eq(a, b);	同time_after类似,如果a和b相等时也返回真(a >= b)。
time_before_eq(a, b);	同time_before类似,如果a和b相等时也返回真(a <= b)。

例如可以将前面的while语句修改如下:

while (time_before(jiffies, delay))
	;


2. 长延时
在驱动程序中,如果需要延时比较长的时间(大于一个时钟滴答),可以监视jiffies的值来实现长延时,例如:

unsigned long timeout = jiffies + HZ;
while (time_before(jiffies, timeout))
	cpu_relax();

其中cpu_relax()是同体系结构相关的代码,通常它也不会做任何事情,也就是同没有这个函数效果是一样的,这中延时方式称为忙等待,这种忙等待始终是一种不好的方式,尤其是这种比较长的延时,通常的处理办法是在不需要cpu的时候能够让出cpu,例如:

while (time_before(jiffies, timeout))
	schedule();


如果的确需要长时间的延时,还可使用msleep、ssleep函数:

void msleep(unsigned int msecs);
unsigned long msleep_interruptible(unsigned int msecs);
void ssleep(unsigned int seconds);

前两个函数是毫秒级的延时,其中msleep是不可中断的,而msleep_interruptible是可以被提前唤醒的,通常返回值为0,如果提前被唤醒,则返回值为睡眠时间剩余的毫秒数。ssleep睡眠的时间是以秒为单位。


3. 短延时
如果延时的时间小于一个时钟滴答的时间,那么使用上面的方法肯定是不行的,就需要使用这里的短延时。

#include <linux/delay.h>
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

ndelay()、udelay()和mdelay()分别表示纳米、微秒和毫秒的延时。主要注意的是这三个函数也是忙等待,所以不能用于长时间的延时。


4. 内核定时器
如果需要在将来某个时间点执行某个操作,或者周期性的执行某个操作,而又不阻塞当前进程,就需要用到这里的定时器,这种定时器称为软定时器(它是通过jiffies来实现的)。

#include <linux/timer.h>
struct timer_list {
	/* ... */
	unsigned long expires;
	void (*function)(unsigned long);
	unsigned long data;
};

其中expires表示定时器执行function时jiffies的值,function自然是定时执行的函数,data为function的参数。


4.1 定时器的初始化

void init_timer(struct timer_list *timer);
struct timer_list TIMER_INITIALIZER(_function, _expires, _data);

这两个函数用于初始化定时器(在使用之前必须初始化)。


4.2 定时器注册

void add_timer(struct timer_list *timer);


4.3 定时器删除

void del_timer(struct timer_list *timer);


4.4 定时器的例子

#include <linux/timer.h>

struct timer_list my_timer;

init_timer(&my_timer);
my_timer.expires = jiffies + HZ;
my_timer.function = timer_func;
my_timer.data = 0;
add_timer(&my_timer);

定时1秒后执行timer_func函数。


4.5 其它API

int mod_timer(struct timer_list *timer, unsigned long expires);

mod_timer用于修改struct timer_list中的expires值。


如果需要周期的执行某个操作怎么办:

#include <linux/timer.h>

struct timer_list my_timer;
void timer_func(unsigned long data)
{
	/* ... */
	mod_timer(&my_timer, jiffies + HZ);
}

也就是说在定时器时间到之后,在执行function时修改expires的值,然后继续等待定时时间。


int del_timer_sync(struct timer_list *timer);

同del_timer函数类似,只是该函数可以确保在返回时没有cpu运行定时器的处理函数,del_timer_sync可在多核系统避免竞态。


需要注意的是add_timer函数内部实际上就是调用的mod_timer函数去注册的,所以mod_timer函数也同样是注册定时器,最终mode_timer函数会调用internal_add_timer函数将定时器添加到一个链表上(Linux内核链表),最终由核心函数统一来处理定时器。

你可能感兴趣的:(Linux内核中的时间处理)