Linux驱动开发——(四)内核定时器

目录

一、内核的时间管理

[1.1 节拍率](#1.1 节拍率)

[1.2 全局变量jiffies](#1.2 全局变量jiffies)

[1.3 绕回](#1.3 绕回)

二、内核定时器

[2.1 简介](#2.1 简介)

[2.2 定时器API函数](#2.2 定时器API函数)

[2.2.1 init_timer函数](#2.2.1 init_timer函数)

[2.2.2 add_timer函数](#2.2.2 add_timer函数)

[2.2.3 del_timer函数](#2.2.3 del_timer函数)

[2.2.4 del_timer_sync函数](#2.2.4 del_timer_sync函数)

[2.2.5 mod_timer函数](#2.2.5 mod_timer函数)

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

三、驱动代码简单模板


一、内核的时间管理

1.1 节拍率

Linux内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序等等,对于驱动编写者来说最常用的是定时器。

硬件定时器提供时钟源,时钟源的频率可以设置,设置好以后就周期性的产生定时中断,系统使用定时中断来计时 。中断周期性产生的频率就是系统频率 ,也叫节拍率系统节拍率是可以设置的,单位是Hz:

高节拍率会提高系统时间精度

如果采用100Hz 的节拍率,时间精度就是10ms

如果采用1000Hz 的的节拍率,时间精度就是1ms

在编译 Linux内核的时候可以通过图形化界面设置系统节拍率,默认情况下选择100Hz,打开Linux内核源码根目录下的.config文件,在此文件中有如图定义:

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

1.2 全局变量jiffies

Linux内核使用全局变量jiffies 来记录系统从启动以来的系统节拍数,系统启动的时候会将jiffies初始化为 0。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位系统。为了兼容不同的硬件,jiffies事实上是jiffies_64的低32位,jiffies_64和 jiffies的结构如图:

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

1.3 绕回

不管是32位还是64位的jiffies,都有溢出的风险,溢出以后会重新从0开始计数,相当于绕回来了,因此也叫做绕回。假如HZ为最大值1000的时候,32 位的jiffies只需要49.7天就发生了绕回,对于64位的jiffies 来说大概需要5.8亿年才能绕回,因此jiffies_64的绕回忽略不计,处理32 位jiffies的绕回显得尤为重要。Linux内核提供了如表所示的API函数来处理绕回:

函数 描述
time_after(unkown, known) 如果unkown超过known,返回真,否则返回假。
time_before(unkown, known) 如果unkown没有超过known,返回真,否则返回假。

unkown通常为jiffies,known通常是需要对比的值。

比如要判断某段代码执行时间有没有超时2s,此时使用如下所示代码:

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

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

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

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

函数 描述
int jiffies_to_msecs(const unsigned long j) 将jiffies类型的参数j分别转换为对应的毫秒、微秒、纳秒。
int jiffies_to_usecs(const unsigned long j) 将jiffies类型的参数j分别转换为对应的毫秒、微秒、纳秒。
u64 jiffies_to_nsecs(const unsigned long j) 将jiffies类型的参数j分别转换为对应的毫秒、微秒、纳秒。
long msecs_to_jiffies(const unsigned int m) 将毫秒、微秒、纳秒转换为jiffies类型。
long usecs_to_jiffies(const unsigned int u) 将毫秒、微秒、纳秒转换为jiffies类型。
unsigned long nsecs_to_jiffies(u64 n) 将毫秒、微秒、纳秒转换为jiffies类型。

二、内核定时器

2.1 简介

Linux内核定时器使用很简单,只需要提供超时时间(相当于定时值)和定时处理函数即可,当超时时间到了以后设置的定时处理函数就会执行。但是,内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。

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

cpp 复制代码
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就是定时器超时以后的定时处理函数。

2.2 定时器API函数

2.2.1 init_timer函数

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

cpp 复制代码
void init_timer(struct timer_list *timer)

timer :要初始化定时器。
返回值:没有返回值。

2.2.2 add_timer函数

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

cpp 复制代码
void add_timer(struct timer_list *timer)

timer :要注册的定时器。
返回值:没有返回值。

2.2.3 del_timer函数

del_timer函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用del_timer函数删除定时器之前要先等待其他处理器的定时处理器函数退出:

cpp 复制代码
int del_timer(struct timer_list * timer) 

timer :要删除的定时器。
返回值:0,定时器还没被激活 1,定时器已经激活。

2.2.4 del_timer_sync函数

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

cpp 复制代码
int del_timer_sync(struct timer_list *timer)

timer :要删除的定时器。
返回值:0,定时器还没被激活;1,定时器已经激活。

2.2.5 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函数前定时器已被激活。

2.3 Linux内核短延时函数

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

函数 描述
void ndelay(unsigned long nsecs) 纳秒、微秒和毫秒延时函数。
void ndelay(unsigned long nsecs) 纳秒、微秒和毫秒延时函数。
void mdelay(unsigned long mseces) 纳秒、微秒和毫秒延时函数。

三、驱动代码简单模板

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

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); 
}
相关推荐
HLC++15 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室16 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy19 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕19 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫21 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生21 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.111 天前
linux基础IO
linux·运维·服务器
BelongPanda1 天前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯1 天前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_777998341 天前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器