Linux内核编程(九)Linux内核定时器


  

   Linux内核定时器是一种基于未来时间点的计时方式。基于未来时间点的计时是以当前时刻为计时开始的时间点,以未来的某一时刻为计时的终点。比如,现在是早上7点,我想在睡一会我就用手机定时五分钟,定时时间就是7点+5分钟=7点5分。这种定时方式就和手机闹钟非常类似。

一、普通定时器

   普通定时器的精度不高,所以不能作为高精度定时器使用。并且内核定时器不是周期性运行的,到计时终点后会自动关闭。如果我们想要实现周期性定时,就需要定时处理函数中重新开启定时器。

1. 定时器结构体

struct timer_list {
    struct hlist_node entry; //用于将定时器插入到内核的定时器链表中。
    unsigned long expires; //定时器的到期时间(以 jiffies 为单位)。jiffies 是一个内核全局变量,表示自系统启动以来的滴答数。
    void (*function)(struct timer_list *); //定时器到期时要执行的回调函数。
    u64 slack;
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
    int start_pid;
    void *start_site;

    /* Timer-specific fields start here. */
    struct timer_base *base;
};

2. 定时时间计算

   timer_list结构体中,expires为计时终点的时间。单位是节拍数。节拍数怎么和时间关联上呢?

(1)宏定义HZ

   Linux内核中有一个宏HZ,这个宏用来表示一秒钟对应的节拍的数量。利用Linux内核中的这个宏,我们就可以把时间转换成节拍数。比如,定时一秒钟换成节拍数就是expires=jiffies+1*HZ,其中宏定义jiffies为系统当前时间对应的节拍数。
  宏HZ的值我们是可以设置的,也就是说一秒钟对应多少个节拍数我们是可以设置的。打开make menuconfig图形化配置界面进行设置,具体设置路径查看其它博客。

(2)全局变量jiffies

   全局变量jiffies用来记录自系统启动以来产生的节拍的总数。启动时, 内核将该变量初始化为 0。此后,每次时钟中断处理程序都会增加该变量的值。 因为一秒内时钟中断的次数为HZ(节拍数),所以jiffies一秒内增加的值也就为HZ(节拍数),系统运行时间以秒为单位计算, 就等于 jiffies/HZ。jiffies=seconds*HZ。

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

extern u64 jiffies_64;  //64位系统使用该宏
extern unsigned long volatile jiffies; //32位系统使用该宏

●jiffies转换函数

//将 jiffies 转换为毫秒
unsigned int jiffies_to_msecs(const unsigned long j);
//将 jiffies 转换为微秒
unsigned int jiffies_to_usecs(const unsigned long j);
//将毫秒转换为 jiffies
unsigned long msecs_to_jiffies(const unsigned int m);
//将微秒转换为 jiffies
unsigned long usecs_to_jiffies(const unsigned int u);

举例:定时10ms(63位系统)。 jiffies_64+msecs_to_jiffies(10);

3.相关API函数

  1. 初始化内核定时器
static struct timer_list my_timer;  //定义定时器
init_timer(&my_timer); //初始化定时器

//结构体赋值
my_timer.expires= jiffies_64+msecs_to_jiffies(2000); //定时2s
my_timer.function= time_function; //定时处理函数,当时间到时处理该函数。
my_timer.data=123;  //传递给定时处理函数的参数
  1. 在驱动入口函数中,向内核注册定时器
void add_timer(struct timer_list *timer);
  1. 在驱动出口函数中,注销定时器
void  del_timer(struct timer_list *timer);
  1. 若修改定时器的值
int  mod_timer(struct timer_list *timer, unsigned long expires);

4. 代码实现

功能:先打印start,然后过5s后定时时间到处理定时函数,打印Timer expired with data: 1234,打印完成后定时器原本应该关闭,但是由于重新设置定时器时间,则每过2s打印一次Timer expired with data: 1234。

misc.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/jiffies.h>

static struct timer_list my_timer;

void my_timer_callback(unsigned long data)
{
    printk(KERN_INFO "Timer expired with data: %lu\n", data);

    // 重新设置定时器,定时器将在 2 秒后再次触发
    mod_timer(&my_timer, jiffies + msecs_to_jiffies(2000));
}

static int __init my_module_init(void)
{
    printk(KERN_INFO "start\n");

    my_timer.function = my_timer_callback;
    my_timer.data = 1234; // 传递给回调函数的数据
    my_timer.expires = jiffies + msecs_to_jiffies(5000);
    init_timer(&my_timer);        // 初始化定时器

    // 添加定时器
    add_timer(&my_timer);

    return 0;
}

static void __exit my_module_exit(void)
{
    printk(KERN_INFO "Exiting module\n");

    // 删除定时器
    del_timer(&my_timer);
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");

使用dmesg查看内核打印信息:
在这里插入图片描述

5. 扩展:使用定时器实现秒字符设备

misc.c

#include <linux/module.h>     //模块
#include <linux/kernel.h>     //内核
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/atomic.h>

static struct timer_list my_timer;  //定时器
atomic64_t counter;  //定义原子

void my_timer_callback(unsigned long data) //定时处理函数
{
    // 重新设置定时器,定时器将在 1 秒后再次触发
    atomic64_inc(&counter);
    mod_timer(&my_timer, jiffies + msecs_to_jiffies(1000));
}

int misc_open(struct inode * node, struct file * fp)
{
    my_timer.function = my_timer_callback;
    my_timer.data = 1234; // 传递给回调函数的数据
    my_timer.expires = jiffies + msecs_to_jiffies(1000);
    init_timer(&my_timer); //初始化定时器
    add_timer(&my_timer);   // 添加定时器
    atomic64_set(&counter, 0); //将原子的值设置为0。
    return 0;
}

int misc_release(struct inode *node, struct file *fp)
{
	return 0;
}

ssize_t misc_read(struct file *fp, char __user *ubuf , size_t size, loff_t *offset)
{
	int ret;
	unsigned int count=0;
	count =atomic64_read(&counter);
	ret=copy_to_user(ubuf, &count , sizeof(count));
	if(ret !=0){
		 pr_err("copy_to_user error\r\n");
		 return -EINVAL;
	}   
	return 0;  
}

ssize_t misc_write(struct file *fp, const char __user *ubuf, size_t size, loff_t *offset)
{

	return 0;
}

//描述一个文件操作集
const struct file_operations misc_fops={
  .owner=THIS_MODULE,     //文件操作集的拥有者是 本模块。
  .open =misc_open,       //应用层对本模块的驱动节点open时触发的函数
  .release=misc_release,  //应用层对本模块的驱动节点close时触发的函数
  .read=misc_read,        //应用层对本模块的驱动节点read时触发的函数
  .write=misc_write,      //应用层对本模块的驱动节点write时触发的函数  
};

//描述一个杂项设备
struct miscdevice misc={
 .minor=MISC_DYNAMIC_MINOR, //由内核自动分配次设备号,misc的主设备号为10.
 .name ="qjl",             //在/dev下生成的驱动节点的名称,不能有空格!
 .fops =&misc_fops,
};

static int __init misc_init(void)
{
   int ret;
   ret=misc_register(&misc);  //向内核注册一个杂项设备
   	if(ret <0){
      pr_err("misc_register error\r\n");
	  return -1;
   }
   return 0;
}

static void __exit misc_exit(void)
{   
   // 删除定时器
   del_timer(&my_timer);
   misc_deregister(&misc);  //从内核注销一个杂项设备
}

module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");

read.c

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
	int fd;
	unsigned int counter=0;
	fd = open("/dev/qjl", O_RDWR);
	if (fd < 0) {
		perror("open error");
		return -1;
	}
	
	while(1){
		read(fd, &counter, sizeof(counter) ); // 预留一个字节用于 null 终止符
		printf("data: %d\n", counter);
		sleep(1);
	}
   close(fd);
   return 0;
}

实验现象:
在这里插入图片描述

二、高精度定时器

   普通定时器的时钟频率可以设置在 100Hz 到 1000Hz之间,所以精度只能限制在亳秒级别。所以无法满足精度较高的场景当中,为此 Linux 提供了高精度定时器,可以提供纳秒级别的精度。

1. 高精度定时器结构体

struct hrtimer {
    struct timerqueue_node node;    // 定时器队列节点,负责管理该高精度定时器在队列中的位置。
//最重要的成员    
    ktime_t _softexpires;           // 定时器的软到期时间,用于指定该定时器到期的具体时间点。
    enum hrtimer_restart (*function)(struct hrtimer *);  // 定时器到期时执行的回调函数,返回值决定定时器是否继续。
//------------    
    struct hrtimer_clock_base *base;  // 定时器的时钟基准,用于选择是使用实时时钟还是其他时钟。
    unsigned long state;             // 定时器的状态,记录是否激活、挂起等状态信息。

#ifdef CONFIG_TIMER_STATS
    int start_pid;                  // 定时器启动时的进程ID,用于统计信息和调试。
    void *start_site;               // 定时器启动时的调用位置,用于调试和分析性能。
    char start_comm[16];            // 启动该定时器的进程名,用于统计和调试。
#endif
};

2. 相关API函数

  1. 初始化一个定时器
void hrtimer_init(struct hrtimer *timer, clockid_t clock_id, enum hrtimer_mode mode);
// struct hrtimer *timer: 指向要初始化的定时器结构体的指针。
// clockid_t clock_id: 指定时间点和时间的测量方式,可以使用以下几个选项:
		//#define CLOCK_REALTIME 0 。基于系统实时时间的时钟,表示从 Epoch(1970年1月1日00:00:00 UTC)以来的秒数和纳秒数。 如果系统时间被用户更改,这个时钟也会改变。
       // #define CLOCK_MONOTONIC 1  (常用)。 单调递增的时钟,表示从某个固定点(例如系统启动)以来的时间。这个时钟不受系统时间更改的影响,适合用于测量经过的时间。
      // #define CLOCK_PROCESS_CPUTIME_ID 2 。 表示进程的 CPU 时间,用于测量当前进程消耗的 CPU 时间。
//mode: 指定定时器的模式,通常为 HRTIMER_MODE_REL(相对时间)或 HRTIMER_MODE_ABS(绝对时间)
  1. 设置定时时间
static inline ktime_t ktime_set(const s64 secs, const unsigned long nsecs)
/*
	const s64 secs : 需要定时的秒数。
	const unsigned long nsecs:需要定时的纳秒数。
*/
  1. 启动定时器
static inline void hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode)
 // timer 为要启动的定时器。
// tim 为定时时间。
//mode 为定时器模式,要和初始化时设置的模式一致。
  1. 重新设置定时时间
u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)
/*
	函数参数:structhrtimer*timer:要设置的定时器
	ktime tnow:当前时间
	ktime tinterval:定时时间
*/
  1. 关闭定时器
int hrtimer_cancel(struct hrtimer *timer)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值