linux内核时间管理

1.节拍率

      系统定时器频率是通过静态预处理定义的,也就是HZ,在系统启动时按照Hz对硬件进行设置。体系结构不同,HZ的值也不同。内核在文件  中定义了HZ的实际值,节拍率就是HZ,周期为1/HZ。i386的节拍率为1000,其它体系结构(包括ARM)的节拍率多数都等于100。

2.jiffies

      全局变量 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)

3.xtime墙上时间/struct timespec

墙上时间,在系统启动过程中根据实时钟(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。

4.时间相关结构体

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);

4.延时操作

4.1struct timer_list定时器

见博文https://blog.csdn.net/zjhqlmzldx/article/details/107458320。

4.2忙等待

unsigned long delay = jiffies + 5*HZ;
 
while(time_before(jiffies, delay))

cond_resched() 函数将调度一个新程序投入运行,但它只有在设置完need_resched标志后,才能生效。延迟执行不管在哪种情况下都不应该在持有锁或禁止中断时发生。

4.3短延迟

有时内核代码(通常也是驱动程序)不但需要很短暂的延迟(比时钟节拍还短)而且还要求延迟的时间按很精确。这种情况多发生在和硬件同步时,内核提供了两个可以处理微秒和毫秒级别的延迟函数,它们都定义在中,可以看到它们并不使用 jiffies:


void udelay(unsigned long usecs)
 
void mdelay(unsigned long msecs)

4.4schedule_timeout()

更理想的延迟执行方法是使用 schedule_timeout() 函数,用法如下:

set_current_state(TASK_INTERRUPTIBLE);      /* 将任务设置为可中断睡眠状态   */
 
schedule_timeout(s *HZ);        /* 小睡一会,s秒后唤醒   */

唯一的参数是延迟的相对时间,单位为 jiffies。上例中将相应的任务推入可中断睡眠队列(注意了,这里的进入睡眠队列,就意味着可以去执行其他任务了),睡眠s秒。在调用schedule_timeout()函数前必须首先将任务设置成TASK_INTERRUPTILE和TASK_UNINTERRUPTIBLE面两种状态之一,否则任务不会睡眠。调用代码绝对不能持有锁(因为持有锁的任务是不能睡眠的)。

 当任务被重新调度时,将返回代码进入睡眠前的位置继续执行。

你可能感兴趣的:(linux内核时间管理)