imx6ull-驱动开发篇22——Linux 时间管理和内核定时器

目录

内核时间管理

系统节拍率

高/低节拍率的优缺点

[jiffies 节拍数](#jiffies 节拍数)

时间绕回

时间转换函数

内核定时器

[timer_list 结构体](#timer_list 结构体)

定时器API函数

[init_timer 函数](#init_timer 函数)

[add_timer 函数](#add_timer 函数)

[del_timer 函数](#del_timer 函数)

[del_timer_sync 函数](#del_timer_sync 函数)

[mod_timer 函数](#mod_timer 函数)

[Linux 内核短延时函数](#Linux 内核短延时函数)


内核时间管理

Linux 内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序,最常用的就是定时器。

系统节拍率

硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以后就周期性的产生定时中断,系统使用定时中断来计时。

中断周期性产生的频率就是系统频率,也叫做节拍率(tick rate)

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

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

选中"Timer frequency",可选的系统节拍率如下:

默认情况下选择 100Hz。

设置好以后打开 Linux 内核源码根目录下的**.config 文件**,可以找到相关宏定义:

宏定义CONFIG_HZ 为 100, Linux 内核会使用 CONFIG_HZ 来设置自己的系统时钟。

打开文件 include/asm-generic/param.h,有如下内容:

cpp 复制代码
# undef HZ
# define HZ CONFIG_HZ
# define USER_HZ 100
# define CLOCKS_PER_SEC (USER_HZ)

定义了一个宏 HZ,表示一秒的节拍数,也就是频率。

宏 HZ 就是 CONFIG_HZ,因此 HZ=100。

高/低节拍率的优缺点

  • ​高节拍率​​:通常 ≥1000Hz(如1000Hz=1ms/次)

  • ​低节拍率​​:通常 ≤100Hz(如100Hz=10ms/次)

高节拍率会提高系统时间精度,但也会导致中断的产生更加频繁,从而加剧系统的负担。

所以需要根据项目的实际情况,选择合适的系统节拍率。

jiffies 节拍数

Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会将 jiffies 初始化为 0,。

jiffies 定义在文件 include/linux/jiffies.h 中,定义如下:

cpp 复制代码
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

jiffies_64 用于 64 位系统,而 jiffies 用于 32 位系统:

HZ 表示每秒的节拍数, jiffies 表示系统运行的 jiffies 节拍数,所以 jiffies/HZ 就是系统运行时间,单位为秒。

时间绕回

不管是 32 位还是 64 位的 jiffies,都有溢出的风险,溢出以后会重新从 0 开始计数。

存储限制​​:

  • 32位jiffies:最大值 0xFFFFFFFF(约49.7天溢出)

  • 64位jiffies:理论永不溢出(5.8 亿年,实际内核仍做防御性编程)

cpp 复制代码
// 32位无符号整数溢出示例
u32 jiffies = 0xFFFFFFFE;
jiffies += 3; // 结果变为1(非5)

Linux 内核提供了API 函数来处理绕回:

我们要判断某段代码执行时间有没有超时,可以使用如下所示代码:

cpp 复制代码
unsigned long timeout;
timeout = jiffies + (2 * HZ); /* 超时的时间点 */

/*************************************
具体的代码
************************************/

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

timeout 就是超时时间点,通过函数 time_before 来判断 jiffies 是否小于 timeout,如果小于的话就表示没有超时。

时间转换函数

Linux 内核提供了几个 jiffies 和 ms、 us、 ns 之间的转换函数,如表:

这些函数使用的示例代码如下:

cpp 复制代码
#include <linux/jiffies.h>
#include <linux/ktime.h>

void time_conversion_demo(void) {
    unsigned long jiffies_value;
    u64 nanoseconds;
    unsigned int milliseconds, microseconds;

    /* 示例1:jiffies转时间单位 */
    jiffies_value = jiffies; // 获取当前jiffies值
    
    // 转换为毫秒/微秒/纳秒
    milliseconds = jiffies_to_msecs(jiffies_value);
    microseconds = jiffies_to_usecs(jiffies_value);
    nanoseconds = jiffies_to_nsecs(jiffies_value);
    
    printk(KERN_INFO "Current jiffies: %lu\n", jiffies_value);
    printk(KERN_INFO "Converted to: %u ms, %u us, %llu ns\n", 
           milliseconds, microseconds, nanoseconds);

    /* 示例2:时间单位转jiffies */
    milliseconds = 1000; // 1秒
    microseconds = 1000000; // 1秒
    nanoseconds = 1000000000; // 1秒
    
    // 转换为jiffies
    jiffies_value = msecs_to_jiffies(milliseconds);
    printk(KERN_INFO "%u ms = %lu jiffies\n", milliseconds, jiffies_value);
    
    jiffies_value = usecs_to_jiffies(microseconds);
    printk(KERN_INFO "%u us = %lu jiffies\n", microseconds, jiffies_value);
    
    jiffies_value = nsecs_to_jiffies(nanoseconds);
    printk(KERN_INFO "%llu ns = %lu jiffies\n", nanoseconds, jiffies_value);

    /* 示例3:混合使用场景 */
    // 设置2秒后的超时点(考虑HZ可能不同)
    unsigned long timeout = jiffies + msecs_to_jiffies(2000);
    printk(KERN_INFO "Timeout at jiffies: %lu\n", timeout);
}

内核定时器

Linux 内核定时器采用系统时钟来实现,内核定时器并不是周期性运行的,超时以后就会自动关闭。

因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。

timer_list 结构体

Linux 内核使用 timer_list 结构体表示内核定时器。

timer_list 定义在文件include/linux/timer.h 中,定义如下:

cpp 复制代码
struct timer_list {
    struct list_head entry;      // 链表节点(用于加入定时器队列)
    unsigned long expires;       // 超时时间(基于jiffies的绝对时间)
    struct tvec_base *base;      // 关联的定时器管理基类
    void (*function)(unsigned long); // 回调函数指针
    unsigned long data;          // 传递给回调函数的参数
    int slack;                   // 允许的时间误差(优化用)
};

典型使用流程如下:

定时器API函数

linux 内核定时器常用的 API 函数如下:

init_timer 函数

init_timer 函数负责初始化 timer_list 类型变量,当我们定义了一个 timer_list 变量以后一定要先用 init_timer 初始化一下。

init_timer 函数原型如下:

cpp 复制代码
void init_timer(struct timer_list *timer)
  • timer:要初始化定时器。

add_timer 函数

add_timer 函数用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行。

add_timer函数原型如下:

cpp 复制代码
void add_timer(struct timer_list *timer)
  • timer:要注册的定时器。

del_timer 函数

del_timer 函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。

在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时器之前要先等待其他处理器的定时处理器函数退出。

del_timer 函数原型如下:

cpp 复制代码
int del_timer(struct timer_list * timer)
  • timer:要删除的定时器。
  • 返回值: 0,定时器还没被激活; 1,定时器已经激活。

del_timer_sync 函数

del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除, del_timer_sync 不能使用在中断上下文中。

del_timer_sync 函数原型如下所示:

cpp 复制代码
int del_timer_sync(struct timer_list *timer)
  • timer:要删除的定时器。
  • 返回值: 0,定时器还没被激活; 1,定时器已经激活。

mod_timer 函数

mod_timer 函数用于修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器。

mod_timer 函数原型如下:

cpp 复制代码
int mod_timer(struct timer_list *timer, unsigned long expires)
  • timer:要修改超时时间(定时值)的定时器。
  • expires:修改后的超时时间。
  • 返回值: 0,调用 mod_timer 函数前定时器未被激活;
  • 1,调用 mod_timer 函数前定时器已被激活。

内核定时器一般的使用流程如下所示:

cpp 复制代码
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 内核种,提供了毫秒、微秒和纳秒延时函数,如表:

使用示例如下:

cpp 复制代码
#include <linux/delay.h>  // 必须包含的头文件

void hardware_operations(void)
{
    /* 硬件寄存器写入后需要稳定时间 */
    writel(0x55AA, reg_addr);
    
    // 纳秒级延时(通常用于信号电平稳定)
    ndelay(100);  // 延迟100纳秒
    
    /* 发送启动命令 */
    writel(0xCC33, cmd_reg);
    
    // 微秒级延时(适合短时等待)
    udelay(50);   // 延迟50微秒
    
    /* 检查设备状态 */
    while (!(readl(status_reg) & READY_BIT)) {
        // 毫秒级延时(较长等待)
        mdelay(1);  // 每次循环延迟1毫秒
    }
    
    // 更长的延时(不推荐在原子上下文使用)
    mdelay(100);    // 延迟100毫秒
}
相关推荐
huangyuchi.18 分钟前
【Linux系统】动静态库的制作
linux·运维·服务器·动态库·静态库·库的简单制作
闻不多20 分钟前
用llamaindex搭建GAR遇到400
android·运维·服务器
jim写博客1 小时前
Linux进程概念(四)环境地址变量
linux·运维·服务器
是小崔啊1 小时前
【Jenkins】01 - Jenkins安装
运维·jenkins
稚辉君.MCA_P8_Java1 小时前
豆包 Java的23种设计模式
java·linux·jvm·设计模式·kubernetes
Nie_Xun2 小时前
ubuntu网络共享
linux·运维·ubuntu
天上掉下来个程小白2 小时前
Docker-14.项目部署-DockerCompose
运维·docker·微服务·容器
你好,赵志伟4 小时前
Socket 编程 TCP
linux·服务器·tcp/ip
Liang_GaRy5 小时前
心路历程-三个了解敲开linux的大门
linux·运维·服务器