Linux之内核定时器

目录

一、内核时间管理

1、节拍率

2、jiffies

二、内核定时器


一、内核时间管理

时间管理在内核中占有非常重要的地位。 相对于事件驱动, 内核中有大量的函数都是基于时间驱动的。内核必须管理系统的运行时间以及当前的日期和时间

周期产生的事件都是由系统定时器驱动的。 系统定时器是一种可编程硬件芯片, 它已固定频率产生中断。 该中断就是所谓的定时器中断, 它所对应的中断处理程序负责更新系统时间, 还负责执行需要周期性运行的任务。 系统定时器和时钟中断处理程序是 Linux 系统内核管理机制中的中枢

硬件为内核提供了一个系统定时器用以计算流逝的时间, 系统定时器以某种频率自行触发时钟中断,该频率可以通过编程预定, 称节拍率

当时钟中断发生时, 内核就通过一种特殊中断处理程序对其进行处理。 内核知道连续两次时钟中断的间隔时间。 这个间隔时间称为节拍(tick) 。内核就是靠这种已知的时钟中断来计算墙上时间和系统运行时间。 
 

1、节拍率

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

在编译 Linux 内核的时候可以通过图形化界面设置系统节拍率, 按照如下路径打开配置界面:

-> Kernel Features
-> Timer frequency ( [=y])

选中“Timer frequency”, 打开以后如下图所示:
Linux之内核定时器_第1张图片 从上图可以看出可选的系统节拍率为 100Hz、 200Hz、 250Hz、 300Hz、 500Hz 和 1000Hz, 默认情况下选择 100Hz。

设置好以后打开 Linux 内核源码根目录下的.config 文件, 在此文件中有如下图所示定义:

Linux之内核定时器_第2张图片

Linux 内核会使用 CONFIG_HZ 来设置自己的系统时钟。 打开文件include/asm-generic/param.h, 有如下内容:

# undef HZ
# define HZ CONFIG_HZ
# define USER_HZ 100
# define CLOCKS_PER_SEC (USER_HZ)

宏 HZ 就是 CONFIG_HZ,HZ=100,后面编写 Linux驱动的时候会常常用到 HZ,因为 HZ 表示一秒的节拍数,也就是频率

高节拍率

优点:

提高系统时间精度,如果采用 100Hz 的节拍率,时间精度就是 10ms,采用1000Hz 的话时间精度就是 1ms。能够以更高的精度运行,时间测量也更加准确

缺点:

高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担, 1000Hz 和 100Hz
的系统节拍率相比,系统要花费 10 倍的精力去处理中断。中断服务函数占用处理器的时间
增加
,需要根据实际情况,选择合适的系统节拍率。

2、jiffies

全局变量 jiffies 用来记录自系统启动以来产生的节拍的总数。 启动时, 内核将该变量初始化为 0, 每次时钟中断处理程序都会增加该变量的值。

因为一秒内时钟中断的次数等于 Hz, 所以 jiffes 一秒内增加的值为 Hz, 系统运行时间以秒为单位计算, 就等于time =  jiffes/Hzjiffes = seconds*HZ)

jiffies 定义在文件 include/linux/jiffies.h 中

extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

jiffies_64 和 jiffies 是同一个东西, jiffies_64 用于 64 位系统, 而 jiffies 用于 32 位系统。关键字 volatile 指示编译器在每次访问变量时都重新从主内存中获得, 而不是通过寄存器中的变量别名访
问, 从而确保前面的循环能按预期的方式执行。

Linux之内核定时器_第3张图片 当访问 jiffies 的时是访问的是 jiffies_64 的低 32 位,使用 get_jiffies_64 这个函数可以获取 jiffies_64 的值。在 32 位的系统上读取 jiffies 的值,在 64 位的系统上 jiffes 和 jiffies_64
表示同一个变量,因此也可以直接读取 jiffies 的值。所以不管是 32 位的系统还是 64 位系统,
都可以使用 jiffies。

jiffies 变量总是无符号长整型(unsigned long), 因此, 在 32 位体系结构上是 32 位, 在时钟频率为 100的情况下, 497 天后会溢出, 如果频率是 1000, 49.7 天后会溢出。

当 jiffies 变量的值超过它的最大存放范围后就会发生溢出, 对于 32 位无符号长整型, 最大取值为 2^32-1,在溢出前, 定时器节拍计数最大为 4294967295, 如果节拍数达到了最大值后还要继续增加的话, 它的值会回绕到 0。

处理绕回API 函数

Linux之内核定时器_第4张图片

如果 unkown 超过 known 的话, time_after 函数返回真, 否则返回假。

如果 unkown 没有超过 known的话 time_before 函数返回真, 否则返回假。

time_after_eq 函数在time_after上,多判断了等于这个条件, time_before_eq 也类似。

 判断某段代码执行时间有没有超时

unsigned long timeout;
timeout = jiffies + (2 * HZ); /* 超时的时间点 */
....

/* 判断有没有超时 */
if(time_before(jiffies, timeout)) 
{
    /* 超时未发生 */
} 
else 
{
    /* 超时发生 */
}

 timeout 就是超时时间点,比如要判断代码执行时间是不是超过了 2 秒,那么超时时间点就是 jiffies+(2*HZ),如果 jiffies 大于 timeout 那就表示超时了,否则就是没有超时。

为了方便开发, Linux 内核提供了几个 jiffies 和 ms、 us、 ns 之间的转换函数
Linux之内核定时器_第5张图片

①定时 10ms
jiffies +msecs_to_jiffies(10)

②定时 10us
jiffies +usecs_to_jiffies(10)

二、内核定时器

Linux 内核定时器采用系统时钟来实现,只需要提供超时时间(定时值)定时处理函数即可。

当超时时间到了后设置的定时处理函数就会执行。在使用内核定时器的时候要注意一点,内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器

Linux 内核使用 timer_list 结构体表示内核定时器, timer_list 定义在文件include/linux/timer.h 中

struct timer_list
{
    struct list_head entry;
    unsigned long expires; /* 定时器超时时间, 单位是节拍数 */
    struct tvec_base *base;
    void (*function)(unsigned long); /* 定时处理函数指针 */
    unsigned long data; /* 要传递给 function 函数的参数 */
    int slack;
};

要使用内核定时器首先要先定义一个 timer_list 变量, 表示定时器, tiemr_list 结构体的 expires 成员变量表示超时时间, 单位为节拍数。

定义一个周期为 2 秒的定时器,这个定时器的超时时间是 jiffies+(2*HZ),expires=jiffies+(2*HZ)。 function 是定时器超时以后的定时处理函数,要做的工作就放到这个函数里面, 需要编写这个定时处理函数。

内核定时器API

Linux之内核定时器_第6张图片Linux之内核定时器_第7张图片

 内核定时器使用流程

struct timer_list timer; /* 定义定时器 */

/* 定时器回调函数 */
void function(unsigned long arg)
{ 
    定时器处理代码

    //如果需要定时器周期性运行的话就使用 mod_timer函数重新设置超时值并且启动定时器。
    mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));

} 

/* 初始化函数 */
void init(void)
{ 
    init_timer(&timer); /* 初始化定时器 */
    timer.function = function; /* 设置定时处理函数 */
    timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
    timer.data = (unsigned long)&dev; /* 将设备结构体作为参数,传给定时器回调函数参数 */
    add_timer(&timer); /* 启动定时器 */
} 

/* 退出函数 */
void exit(void)
{ 
    del_timer(&timer); /* 删除定时器 */
    /* 或者使用 */
    del_timer_sync(&timer);
}

 Linux 内核短延时函数,Linux 内核提供了毫秒、微秒和纳秒延时函数

你可能感兴趣的:(#,Linux驱动开发,单片机,嵌入式硬件,linux)