系统定时器频率是通过静态预处理定义的,也就是HZ,在系统启动时按照Hz对硬件进行设置。体系结构不同,HZ的值也不同。内核在文件
全局变量 jiffies 用来记录自系统启动以来产生的节拍的总数。启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序都会增加该变量的值。因为一秒内时钟中断的次数等于Hz,所以jiffes一秒内增加的值也就为Hz,系统运行时间以秒为单位计算,就等于jiffes/Hz。jiffes=seconds*HZ。
jiffes 定义在文件 linux/jiffs.h 中
extern unsigned long volatile jiffies;
u64 get_jiffies_64(void);
关键字volatile指示编译器在每次访问变量时都重新从主内存中获得,而不是通过寄存器中的变量别名访问,从而确保前面的循环能按预期的方式执行。
当jiffies变量的值超过它的最大存放范围后就会发生溢出,对于32位无符号长整型,最大取值为2^32-1,在溢出前,定时器节拍计数最大为4294967295,如果节拍数达到了最大值后还要继续增加的话,它的值会回绕到0。回绕会引起许多问题,下面的宏可以正确的处理节拍计数回绕的情况:
#define time_after(unknown, known) ((long)(konwn) - (long)(unknown) < 0)
#define time_before(unknown, known) ((long)(unknown) - (long)(known) < 0)
#define time_after_eq(unknown, known) ((long)(unknown) - (long)(known) >=0)
#define time_before_eq(unknown, known) ((long)(known) - (long)(unknown)>= 0)
墙上时间,在系统启动过程中根据实时钟(RTC)芯片保存数据进行初始化,在系统运行期间由系统时钟维护并在合适的时刻和RTC芯片进行同步。墙上时间存储于系统核心变量xtime中,该变量记录了现实世界中的年月日格式的时间,以便内核对某些对象和事件作时间标记,如记录文件的创建时间、修改时间、上次访问时间,或者供用户进程通过系统调用来使用。
内核中使用struct timespec类型的变量xtime来记录墙上时间,该变量在文件src/kernel/time.c中的第564行声明如下:
struct timespec xtime __attribute__ ((aligned (16)));
其中,数据结构struct timespec在文件src/include/Linux/time.h中的第12行开始定义,代码如下:
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
该结构用来表示当前时刻距UNIX时间基准1970年7月1日00:00:00的相对时间。其中成员变量tv_sec用来记录距标准时间1970/01/01/00:00:00的秒数,成员变量tv_nsec用来记录不足一秒的微秒值,其取值范围为0~999 999。
struct timespec
{
__time_t tv_sec; /* Seconds. */
long int tv_nsec; /* Nanoseconds. */
};
void getnstimeofday(struct timespec *tv)
功能:此函数用于返回当前内核时间,该时间是距离1970开始的秒和纳秒
参数: timespec结构体指针
头文件:#include
struct timeval
{
__time_t tv_sec; /* Seconds. */
__suseconds_t tv_usec; /* Microseconds. */
};
tv_sec:存放自1970年1月1日0时(UTC时间)以来经过的秒数。__kernel_time_t最终定义成了long型,也就是在32位系统上是32位长,而在64位系统上是64位长。
tv_usec:__kernel_suseconds_t实际最终也被定义成了long型,存放自上一秒开始经过的微秒(us)数。
所以,这个结构体其实和timespec结构体大同小异,tv_sec存的值是一样的,而只需要将timespec中的tv_nsec除以1000就是timeval中的tv_usec。
void do_gettimeofday(struct timeval *tv)
功能:此函数用于返回当前内核时间,该时间是距离1970开始的秒和微妙
参数: timeval结构体指针
头文件:#include
ktime_t
在Linux的时间子系统内,一般使用ktime_t来表示时间,其定义如下(代码位于include/linux/ktime.h):
typedef s64 ktime_t;
就是一个非常简单的64位带符号整数,表示的时间单位是纳秒。
#include //需要包含的头文件
struct timeval tstart; //结构体
do_gettimeofday(&tstart);//获取时间
//打印秒、微妙值
printk("%s, stime: %lds, utime: %ldus\n"\
, __func__, tstart.tv_sec, tstart.tv_usec);
struct timespec ts;
ts = current_kernel_time();
printk(KERN_ALERT "%ld %ld\n", ts.tv_sec, ts.tv_nsec);
见博文https://blog.csdn.net/zjhqlmzldx/article/details/107458320。
unsigned long delay = jiffies + 5*HZ;
while(time_before(jiffies, delay))
cond_resched() 函数将调度一个新程序投入运行,但它只有在设置完need_resched标志后,才能生效。延迟执行不管在哪种情况下都不应该在持有锁或禁止中断时发生。
有时内核代码(通常也是驱动程序)不但需要很短暂的延迟(比时钟节拍还短)而且还要求延迟的时间按很精确。这种情况多发生在和硬件同步时,内核提供了两个可以处理微秒和毫秒级别的延迟函数,它们都定义在
void udelay(unsigned long usecs)
void mdelay(unsigned long msecs)
更理想的延迟执行方法是使用 schedule_timeout() 函数,用法如下:
set_current_state(TASK_INTERRUPTIBLE); /* 将任务设置为可中断睡眠状态 */
schedule_timeout(s *HZ); /* 小睡一会,s秒后唤醒 */
唯一的参数是延迟的相对时间,单位为 jiffies。上例中将相应的任务推入可中断睡眠队列(注意了,这里的进入睡眠队列,就意味着可以去执行其他任务了),睡眠s秒。在调用schedule_timeout()函数前必须首先将任务设置成TASK_INTERRUPTILE和TASK_UNINTERRUPTIBLE面两种状态之一,否则任务不会睡眠。调用代码绝对不能持有锁(因为持有锁的任务是不能睡眠的)。
当任务被重新调度时,将返回代码进入睡眠前的位置继续执行。