6.实际时间
当前实际时间(墙上时间)定义在文件 kernel/timer.c 中:
struct timespec xtime;
timespec 数据结构定义在文件<linux/time.h>中,形式如下:
struct timespec { time_t tv_sec; /* 秒 */ long tv_nsec; /* 纳秒 */ };
xtime.tv_sec 以秒为单位,存放着自 1970年7月1日(UTC)以来经过的时间。xtime.tv_nsec 记录自上一秒开始经过的纳秒数。
读写 xtime 变量需要使用 xtime_lock 锁,该锁是一个 seqlock 锁。
7.定时器
定时器能够使工作在指定时间点上执行。定时器由结构 time_list 表示,定义在文件<linux/timer.h>中。
struct timer_list { struct list_head entry; /* 定时器链表的入口 */ unsigned long expires; /* 以 jiffies 为单位的定时值 */ spinlock_t lock; /* 保护定时器的锁 */ void (*function)(unsigned long); /* 定时器处理函数 */ unsigned long data; /* 传给处理函数的长整形参数 */ struct tvec_t_base_s *base; /* 定时器内部值,用户不要使用 */ };
使用定时器:
struct timer_list my_timer; init_timer( &my_timer ); /* 初始化定时器 */ my_timer.expires = jiffies + delay; /* 定时器超时时的节拍数 */ my_timer.data = 0; /* 给定时器处理函数传入 0 值 */ my_timer.function = my_function; /* 定时器超时时调用的函数 */ add_timer( &my_timer ); /* 激活定时器 */
如果需要更改超时时间,可以调用 mod_timer 函数:
mod_timer( &my_timer , jiffies + new_delay );
mod_timer 函数不管 my_timer 是否已被激活,一旦从 mod_timer 返回,my_timer 都被激活而且设置了新的定时值。如果调用时 my_timer 未被激活,该函数返回 0,否则返回 1。
如果需要停止定时器,可以使用 del_timer 和 del_timer_sync 函数
del_timer( &my_timer );
del_timer_sync( &my_timer );
被激活或未被激活的定时器都可以使用,如果定时器还未被激活,该函数返回 0,否则返回 1。注意,不需要为已经超时的定时器调用该函数,因为它们会自动被删除。两者区别在于 del_timer_sync 会等待其他处理器上运行的定时器处理程序都退出才执行。所以 del_timer_sync 不能在中断上下文中使用。但在安全性方面考虑,优先使用 del_timer_sync。
8.延迟执行
1)忙等待
unsigned long delay = jiffies + 5 * HZ; while( time_before( jiffies, delay ) ) cond_resched();
cond_resched 函数将调度一个新程序投入运行,但它只有在设置完 need_resched 标志后,才能生效。换句话说,该方法有效的条件是系统中存在更重要的任务需要运行。注意因为该方法需要调用调度程序,所以它只能在进程上下文中使用。
2)短延迟
void udelay( unsigned long usecs ); void mdelay( unsigned long msecs );
前一个函数利用忙循环将任务延迟到指定的微秒数后运行,后者延迟指定的毫秒数。
3)schedule_timeout
该方法会让需要延迟执行的任务睡眠到指定的延迟时间耗尽后再重新运行,但不保证睡眠时间正好等于指定的延迟时间——只能尽量使睡眠时间接近指定的延迟时间。当指定的时间到期后,内核唤醒被延迟的任务并将其重新放回运行队列。
/* 将任务设置为可中断睡眠状态 */ set_current_state( TASK_INTERRUPTIBLE ); /* 小睡一会儿,“s”秒后唤醒 */ schedule_timeout( s * HZ);
唯一的参数是延迟的相对时间,单位为 jiffies,注意在调用 schedule_timeout 函数前必须首先将任务设置为 TASK_INTERRUPTIBLE 或 TASK_UNINTERRUPTIBLE 两种状态之一,否则任务不会睡眠。
4)设置超时时间,在等待队列上睡眠
可以将自己放入等待队列,然后调用调度程序去执行新任务。一旦事件发生后,内核调用 wake_up 函数唤醒在睡眠队列上的任务,使其重新投入运行。