Linux驱动技术(七) _内核定时器与延迟工作
内核定时器
软件上的定时器最终要依靠硬件时钟来实现,简单的说,内核会在时钟中断发生后检测各个注册到内核的定时器是否到期,如果到期,就回调相应的注册函数,将其作为中断底半部来执行。实际上,时钟中断处理程序会触发TIMER_SOFTIRQ软中断,运行当前处理器上到期的所有定时器。
设备驱动程序如要获得时间信息以及需要定时服务,都可以使用内核定时器。
jiffies
要说内核定时器,首先就得说说内核中关于时间的一个重要的概念:jiffies变量,作为内核时钟的基础,jiffies每隔一个固定的时间就会增加1,称为增加一个节拍,这个固定间隔由定时器中断来实现,每秒中产生多少个定时器中断,由在 linux/param.h 中定义的HZ宏来确定,如此,可以通过jiffies获取一段时间,比如jiffies/HZ表示自系统启动的秒数。下两秒就是(jiffies/HZ+2),内核中用jiffies来计时,秒转换成的jiffies:seconds*HZ,所以以jiffiy为单位,以当前时刻为基准计时2秒:(jiffies/HZ+2)*HZ=jiffies+2*HZ如果要获取当前时间,可以使用do_gettimeofday(),该函数填充一个struct timeval结构,有着接近微妙的分辨率。
//kernel/time/timekeeping.c * do_gettimeofday - Returns the time of day in a timeval * @tv: pointer to the timeval to be set * NOTE: Users should be converted to using getnstimeofday() void do_gettimeofday(struct timeval *tv)
驱动程序为了让硬件有足够的时间完成一些任务,常常需要将特定的代码延后一段时间来执行,根据延时的长短,内核开发中使用长延时和短延时两个概念。长延时的定义为:延时时间 多个jiffies,实现长延时可以用查询jiffies的方法:
time_before(jiffies, new_jiffies); time_after(new_jiffiesmjiffies);
**短延时的定义为:延迟事件接近或短于一个jiffy,实现短延时可以调用
udelay(); mdelay();
这两个函数都是忙等待函数,大量消耗CPU时间,前者使用软件循环来延迟指定数目的微妙数,后者使用前者的嵌套来实现毫秒级的延时。
定时器
驱动可以注册一个内核定时器,来指定一个函数在未来某个时间来执行。定时器从注册到内核开始计时,达到指定的时间后会执行注册的函数。即超时值是一个jiffies值,当jiffies值大于timer- expires时,timer- function就会被执行。API如下
//定一个定时器 struct timer_list my_timer;//初始化定时器 void init_timer(struct timer_list *timer); mytimer.function = my_function; mytimer.expires = jiffies +HZ;//增加定时器 void add_timer(struct timer_list *timer);//删除定时器 int del_tiemr(struct timer_list *timer);
实例
static struct timer_list tm; struct timeval oldtv;void callback(unsigned long arg){ struct timeval tv; char *strp = (char*)arg; do_gettimeofday( tv); printk("%s: %ld, %ld\n", __func__, tv.tv_sec - oldtv.tv_sec, tv.tv_usec- oldtv.tv_usec); oldtv = tv; tm.expires = jiffies+1*HZ; add_timer( tm); static int __init demo_init(void){ init_timer( tm); do_gettimeofday( oldtv); tm.function= callback; tm.data = (unsigned long)"hello world"; tm.expires = jiffies+1*HZ; add_timer( tm); return 0;
延迟工作
除了使用内核定时器完成定时延迟工作,Linux内核还提供了一套封装好的"快捷方式"-delayed_work,和内核定时器类似,其本质也是利用工作队列和定时器实现,
//include/linux/workqueue.h struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func; #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif struct delayed_work { 114 struct work_struct work; struct timer_list timer; /* target workqueue and CPU - timer uses to queue - work */ struct workqueue_struct *wq; int cpu; };
--103-- 需要延迟执行的函数, typedef void (work_func_t)(struct work_struct work);
至此,我们可以使用一个delayed_work对象以及相应的调度API实现对指定任务的延时执行
//注册一个延迟执行 591 static inline bool schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)//注销一个延迟执行 2975 bool cancel_delayed_work(struct delayed_work *dwork)
和内核定时器一样,延迟执行只会在超时的时候执行一次,如果要实现循环延迟,只需要在注册的函数中再次注册一个延迟执行函数。
schedule_delayed_work( work,msecs_to_jiffies(poll_interval));
相关文章
- STM32MP157 Linux系统移植开发篇18:Linux内核蓝牙驱动移植
- STM32MP157 Linux系统移植开发篇17:Linux内核摄像头驱动移植
- STM32MP157 Linux系统移植开发篇16:Linux内核音频驱动移植
- STM32MP157 Linux系统移植开发篇15:Linux内核7寸触摸屏驱动移植
- STM32MP157 Linux系统移植开发篇13:Linux内核5寸触摸屏驱动移植
- STM32MP157 Linux系统移植开发篇9:Linux内核eMMC驱动移植
- linux应用程序如何从用户空间进入内核空间?
- linux内核中i2c驱动中slave模式接口的调用
- arm linux利用alsa驱动并使用usb音频设备
- S5pv210下 HDMI 接口在 Linux 3.0.8 内核下的驱动解析
- linux ------ 硬连接和软连接(软连接也叫符号连接)
- 【嵌入式Linux驱动开发】二十一、Linux内核自带的KEY驱动探索
- 【嵌入式Linux驱动开发】十三、GPIO按键中断驱动程序编写
- 【正点原子MP157连载】第三十七章 Linux MISC驱动实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7
- 【正点原子MP157连载】第二章 Ubuntu系统入门-摘自【正点原子】【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7
- 【正点原子Linux连载】第五十五章 设备树下的platform驱动编写 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
- 【正点原子Linux连载】第二十四章在LCD上显示字符 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
- 【正点原子Linux连载】第二十二章 串口格式化函数移植实验-摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
- 【正点原子Linux连载】第十三章BSP工程管理实验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
- 【正点原子Linux连载】第十章C语言版LED灯实验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
- 【IMX6ULL】Linux中的platform驱动