概述
概述
对于嵌入式开发,经常会遇到一些定时、延时以及周期调度的情况,所以定时器是必不可少的一种资源。
相对于裸机开发,我们使用定时器只需先选择时钟源,然后设置分频系数和计数值,配置好中断后,就可以静静的等待定时中断的到来即可。
linux操作定时器同裸机基本一致,同样先选择时钟源(操作系统指定,工程师无需配置),然后设置系统频率(也是节拍率),最后指定定时时间即可。
注意
1. 系统时钟频率的配置方法,在linux内核目录中,运行make menuconfig,使用图形界面,选择Kernel Features -> Timer frequency (<choice> [=y])设置。
2. 高节拍率可以提高系统时间精度,但随之而来会使得系统中断更加频繁,增大系统的负担,所以用户进行配置时需要根据实际情况而定。
接口
HZ
内核全局常量,ARM架构默认HZ=100,表示一秒钟定时器硬件给CPU发送100次定时器中断,每发送一次中断的时间间隔为10ms。
通过make menuconfig通过界面配置。
jiffies_64和jiffies
内核全局常量,用来记录系统从启动以来的系统节拍数,系统启动的时候会初始化为 0,每发生一次硬件定时器中断加1。
注:jiffies表示jiffies_64的低32位。
/* jiffies_64和jiffies的定义,在linux/jiffy.h文件中 */ /* * The 64-bit value is not atomic - you MUST NOT read it * without sampling the sequence number in jiffies_lock. * get_jiffies_64() will do this for you as appropriate. */ extern u64 __jiffy_data jiffies_64; extern unsigned long volatile __jiffy_data jiffies;
当变量jiffies和jiffies_64计数溢出后,数据又会从0开始,这个时候需要判断时间是否绕回。linux提供以下接口用来判断时间是否绕回。
/* 判断时间是否绕回的函数,在linux/jiffy.h文件中 */ #define time_after(a,b) (typecheck(unsigned long, a) && typecheck(unsigned long, b) && ((long)((b) - (a)) < 0)) #define time_before(a,b) time_after(b,a) #define time_after_eq(a,b) (typecheck(unsigned long, a) && typecheck(unsigned long, b) && ((long)((a) - (b)) >= 0)) #define time_before_eq(a,b) time_after_eq(b,a) /* Same as above, but does so with platform independent 64bit types. * These must be used when utilizing jiffies_64 (i.e. return value of * get_jiffies_64() */ #define time_after64(a,b) (typecheck(__u64, a) && typecheck(__u64, b) && ((__s64)((b) - (a)) < 0)) #define time_before64(a,b) time_after64(b,a) #define time_after_eq64(a,b) (typecheck(__u64, a) && typecheck(__u64, b) && ((__s64)((a) - (b)) >= 0)) #define time_before_eq64(a,b) time_after_eq64(b,a)
例:判断某段代码执行时间是否超过5s。
/* time_after使用示例 */ unsigned long timeout = jiffies + 5*HZ; /* 以下有一堆的代码,CPU执行这些代码也需要时间的,此时jiffies会10ms加1次 */ ... if (time_after(jiffies, timeout)) /* 超时 处理 */ else /* 没超时 处理 */
另外,为了方便开发, 内核提供jiffies和 ms、us、ns之间的转换函数。
/* jiffies与ms、us、ns之间的转换,在linux/jiffy.h文件中 */ extern unsigned int jiffies_to_msecs(const unsigned long j); extern unsigned int jiffies_to_usecs(const unsigned long j); static inline u64 jiffies_to_nsecs(const unsigned long j); extern unsigned long msecs_to_jiffies(const unsigned int m); extern unsigned long usecs_to_jiffies(const unsigned int u); extern unsigned long nsecs_to_jiffies(u64 n);
timer_list
内核使用timer_list结构体表示内核定时器。定义在文件include/linux/timer.h中
/* 描述定时器资源的结构, linux/timer.h */ struct timer_list { struct list_head entry; unsigned long expires; /* 超时时间、单位是节拍数 */ struct tvec_base *base; void (*function)(unsigned long); /* 超时函数 */ unsigned long data; /* 传递给超时函数的参数 */ ... };
init_timer
内核使用init_timer函数负责初始化timer_list类型变量。
/** * 初始化定时器,仅完成申请功能 * @timer: 定时器对象 */ #define init_timer(timer) __init_timer((timer), 0)
注意:init_timer仅仅完成了资源申请等功能,对于timer_list的其余三个重要属性需要工程师手动初始化,示例如下。
/** * 初始化定时器示例 * @timer: 定时器对象 */ struct timer_list timer; /* 定义对象 */ init_timer(&timer); /* 这里仅仅完成申请 */ /* 额外需要工程师初始化关键的三个字段 */ unsigned long g_data = 250; timer.function = timer_timer_function; /* 超时处理函数 */ timer.expires = jiffies + 2*HZ; /* 指定超时时间 */ timer.data = (unsigned long)&g_data; /* 传递参数,无参数时可省略 */ /* 根据用户需求编写超时处理函数,注意不能进行休眠操作 */ void timer_timer_function(unsigned long data) { data = (unsigned long)&g_data; ... }
add_timer
内核使用add_timer函数向内核注册定时器。
/** * 向内核注册定时器,若设置了超时时间,直接启动定时器 * @timer: 定时器对象 */ extern void add_timer(struct timer_list *timer);
del_timer和del_timer_sync
内核使用del_timer和del_timer_sync函数删除定时器,del_timer_sync使用在SMP架构下。
/** * 删除定时器,不管定时器有没有被激活,均可删除 * @timer: 定时器对象 * @0,定时器还没被激活; 1,定时器已经激活 */ extern int del_timer(struct timer_list * timer); /** * 删除定时器,SMP中使用,删除时需要等待其它处理器使用完定时器才能删除 * @timer: 定时器对象 * @0,定时器还没被激活; 1,定时器已经激活 */ #ifdef CONFIG_SMP extern int del_timer_sync(struct timer_list *timer); #else # define del_timer_sync(t) del_timer(t) #endif
mod_timer
内核使用mod_timer函数修改定时器超时时间,在定时器还没有激活的情况下,mod_timer函数还会激活定时器。
/** * 修改定时值,如果定时器还没有激活的话,mod_timer函数会激活定时器 * @timer: 定时器对象 expires:修改后的超时时间 * @0,定时器还没被激活; 1,定时器已经激活 */ extern int mod_timer(struct timer_list *timer, unsigned long expires);
mdelay/ndelay/udelay
内核使用mdelay/ndelay/udelay三个函数用于短延时,其中mdelay和ndelay均是基于udelay实现,而udelay的实现由体系决定和实现。
注意:以上三个延时函数是忙等待机制,延时过程中,不会让出CPU使用权。
/** * 短延时,毫秒、微妙、纳秒级的延时,定义在linux/delay.h中 * @n/x: 延时时间 */ #ifndef mdelay #define mdelay(n) ( (__builtin_constant_p(n) && (n)<=MAX_UDELAY_MS) ? udelay((n)*1000) : ({unsigned long __ms=(n); while (__ms--) udelay(1000);})) #endif #ifndef ndelay static inline void ndelay(unsigned long x) { udelay(DIV_ROUND_UP(x, 1000)); } #define ndelay(x) ndelay(x) #endif
msleep/ssleep
msleep和ssleep用于更长的延时,此时的延时不再采用忙等待机制,程序调用msleep和ssleep后,让出CPU权,当然延时有一定的误差。
注意:msleep和msleep_interruptible区别在于,前者不可中断,后者可中断唤醒,当msleep_interruptible被唤醒后,返回初始请求睡眠周期中剩余的毫秒数。
/** * 休眠延时,毫秒、秒级的延时,定义在linux/delay.h中 * @msecs/seconds: 延时时间 * @msleep_interruptible返回值为初始请求睡眠周期中剩余的毫秒数 */ void msleep(unsigned int msecs); unsigned long msleep_interruptible(unsigned int msecs); static inline void ssleep(unsigned int seconds);
示例
★一直以来,C语言一直被定义为面向过程语言,主要是因为其缺少一些面向对象的语法(class类),但是我们在构建大型程序的时候一定要具备面向对象的思想来构建。
★C语言实现面向对象的思路大多是通过结构体和函数指针的方式,本示例中将定时器处理单独抽象为一个模块(timer.c/timer.h),使用结构体struct class_timer定义为类名。
属性:定时器。
行为:添加、删除、修改。
★示例仅用于展示定时器的应用,采用正点原子的阿尔法开发板进行验证。
★包含定时器头文件timer.h和源文件timer.c、驱动源文件timer_drv.c和编译规则文件Makefile(均已验证通过)。
timer.h
/** * @Filename : timer.h * @Revision : $Revision: 1.00 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 定时器类定义 **/ #ifndef __TIMER_H__ #define __TIMER_H__ #include <linux/init.h> #include <linux/module.h> #include <linux/uaccess.h> #include <linux/timer.h> #include <linux/slab.h> /* 定时器类资源定义 */ struct class_timer { struct timer_list list; void (*del)(struct class_timer *timer); /* 删除定时器 */ void (*mod)(struct class_timer *timer, const unsigned int msec); /* 修改定时器定时时间 */ void (*start)(struct class_timer *timer, const unsigned int msec); /* 启动定时器,初始化定时时间 */ }; /** * 创建定时器对象,成功返回类地址,失败返回NULL * @func: 超时函数,定时时间到执行 */ struct class_timer *timer_create(void (*func)(unsigned long)); /** * 销毁定时器对象 * @timer: 对象 */ void timer_destroy(struct class_timer *timer); #endif
timer.c
/** * @Filename : timer.c * @Revision : $Revision: 1.00 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 定时器类定义 **/ #include "timer.h" /** * 删除定时器 * @timer: 定时器对象 */ static void _del(struct class_timer *timer) { del_timer(&timer->list); } /** * 启动定时器,初始化定时时间 * @timer: 定时器对象,msec:初始定时时间,毫秒为单位 */ static void _start(struct class_timer *timer, const unsigned int msec) { timer->list.expires = jiffies + msecs_to_jiffies(msec); /* 设定超时时间 */ add_timer(&timer->list); /* 添加定时器 */ } /** * 定时器定时时间修改 * @timer: 定时器对象,msec:修改的定时时间,毫秒为单位 */ static void _mod(struct class_timer *timer, const unsigned int msec) { /* 修改定时器:mod_timer=del_timer+expires...+add_timer */ mod_timer(&timer->list, jiffies + msecs_to_jiffies(msec)); } /** * 创建定时器对象,成功返回类地址,失败返回NULL * @func: 超时函数,定时时间到执行 */ struct class_timer *timer_create(void (*func)(unsigned long)) { struct class_timer *timer = NULL; if ((timer = kmalloc(sizeof(struct class_timer), GFP_KERNEL)) == NULL) { printk(KERN_ERR "Allocation of timer class failedn"); return NULL; } init_timer(&timer->list); /* 初始化定时器 */ timer->list.function = func; /* 指定一个超时处理函数 */ timer->del = _del; timer->mod = _mod; timer->start = _start; return timer; } /** * 销毁定时器对象 * @timer: 对象 */ void timer_destroy(struct class_timer *timer) { timer->del(timer); kfree(timer); } EXPORT_SYMBOL_GPL(timer_create); EXPORT_SYMBOL_GPL(timer_destroy); MODULE_LICENSE("GPL"); MODULE_AUTHOR("feng"); /* 模块的作者 */ MODULE_VERSION ("1.00"); /* 模块版本号 */
timer_drv.c
/** * @Filename : timer_drv.c * @Revision : $Revision: 1.00 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 定时器操作示例 **/ #include <linux/init.h> #include <linux/module.h> #include "timer.h" struct class_timer *feng_timer; /** * @定时器的超时处理函数 * @data: 无效 */ static void _timer_function(unsigned long data) { static unsigned int time = 0; time++; printk("the time is: %d s..n", time); feng_timer->mod(feng_timer, 1000); /* 修改定时器,延时1s */ } /** * @模块入口函数 */ static int __init timer_drv_init(void) { /* 构造定时器对象 */ if ((feng_timer = timer_create(_timer_function)) == NULL) printk("timer class error...n"); feng_timer->start(feng_timer, 1000); /* 修改定时器,延时1s */ printk("timer class ok...n"); return 0; } /** * @模块出口函数 */ static void __exit timer_drv_exit(void) { timer_destroy(feng_timer); /* 销毁定时器对象 */ } module_init(timer_drv_init); module_exit(timer_drv_exit); MODULE_LICENSE("GPL"); /* 调用modinfo xx(模块名)查看 */ MODULE_AUTHOR("feng"); /* 模块的作者 */ MODULE_VERSION ("1.00"); /* 模块版本号 */ /* MODULE_DESCRIPTION("xxxxx"); 模块描述 */ /* MODULE_ALIAS("xxx"); 模块别名 */
Makefile
#根文件所在目录 ROOTFS_DIR = /home/feng/atomic/rootfs #交叉编译工具链 CROSS_COMPILE = arm-linux-gnueabihf- CC = $(CROSS_COMPILE)gcc #目标文件名 TAR_NAME = timer #应用程序名字 APP_NAME = $(TAR_NAME) #驱动目录路径 DRV_DIR = $(ROOTFS_DIR)/home/drv DRV_DIR_LIB = $(ROOTFS_DIR)/lib/modules/4.1.15 #动态库目录路径 LIB_DIR = $(ROOTFS_DIR)/home/lib #应用程序目录路径 APP_DIR = $(ROOTFS_DIR)/home/app #KERNELRELEASE由内核makefile赋值 ifeq ($(KERNELRELEASE), ) #内核路径 KERNEL_DIR =/home/feng/atomic/resource/linux-imx-rel_imx_4.1.15_2.1.0_ga #当前文件路径 CURR_DIR = $(shell pwd) all: #编译模块 make -C $(KERNEL_DIR) M=$(CURR_DIR) modules #编译应用程序 #-$(CC) -o $(APP_NAME) $(APP_NAME).c main.c clean: #清除模块文件 make -C $(KERNEL_DIR) M=$(CURR_DIR) clean #清除应用文件 #-rm $(APP_NAME) install: #拷贝模块文件 #cp -raf $(TAR_KEY_NAME)_drv.ko $(TAR_KEY_NAME)_dev.ko $(DRV_DIR) #cp -raf keyin.ko wq.ko timer.ko $(DRV_DIR_LIB) cp -raf *.ko $(DRV_DIR_LIB) #拷贝应用文件 #-cp -raf $(APP_NAME) $(APP_DIR) else #指定编译什么文件 obj-m += $(TAR_NAME)_drv.o timer.o #obj-m += $(TAR_NAME).o endif
结论
1、进入模块目录,执行make命令编译模块;然后执行make install命令,拷贝模块到目标机指定目录。
feng:timer$ make #编译模块 make -C /home/feng/atomic/resource/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/mnt/hgfs/Share/linux/atomic/driver/timer modules make[1]: 进入目录“/home/feng/atomic/resource/linux-imx-rel_imx_4.1.15_2.1.0_ga” CC [M] /mnt/hgfs/Share/linux/atomic/driver/timer/timer.o Building modules, stage 2. MODPOST 2 modules CC /mnt/hgfs/Share/linux/atomic/driver/timer/timer.mod.o LD [M] /mnt/hgfs/Share/linux/atomic/driver/timer/timer.ko CC /mnt/hgfs/Share/linux/atomic/driver/timer/timer_drv.mod.o LD [M] /mnt/hgfs/Share/linux/atomic/driver/timer/timer_drv.ko make[1]: 离开目录“/home/feng/atomic/resource/linux-imx-rel_imx_4.1.15_2.1.0_ga” #编译应用程序 #-arm-linux-gnueabihf-gcc -o timer timer.c main.c feng:timer$ make install #拷贝模块文件 #cp -raf _drv.ko _dev.ko /home/feng/atomic/rootfs/home/drv #cp -raf keyin.ko wq.ko timer.ko /home/feng/atomic/rootfs/lib/modules/4.1.15 cp -raf *.ko /home/feng/atomic/rootfs/lib/modules/4.1.15 #拷贝应用文件 #-cp -raf timer /home/feng/atomic/rootfs/home/app feng:timer$
2、在目标机上执行modprobe命令加载模块。
注意:在模块加载之前,需要先调用depmod命令,生成模块依赖文件。
/ # depmod / # modprobe timer_drv.ko timer class ok... / # the time is: 1 s.. the time is: 2 s.. the time is: 3 s.. the time is: 4 s.. the time is: 5 s.. the time is: 6 s.. the time is: 7 s.. the time is: 8 s.. the time is: 9 s.. the time is: 10 s..
3、在目标机上执行modprobe -r命令卸载模块。
/ # modprobe -r timer_drv.ko / # lsmod Module Size Used by Tainted: G / #
4、综上、示例展示了定时器的应用,实现内核每秒打印时间提示信息。
往期 · 推荐
浅谈linux - 字符设备框架
帮你自动化办公的python-自动提取pdf指定页(项目概述)
也没想象中那么神秘的数据结构-一种通用化的双向链表设计(底层源码)
也没想象中那么神秘的数据结构-一环扣一环的“链表”(双向链表)
我用C语言玩对象,偷偷关注着你的观察者模式(基类设计)
关注
更多精彩内容,请关注微信公众号:不只会拍照的程序猿,本人致力分享linux、设计模式、C语言、嵌入式、编程相关知识,也会抽空分享些摄影相关内容,同样也分享大量摄影、编程相关视频和源码,另外你若想要本文章源码请关注公众号:不只会拍照的程序猿,后台回复:linux驱动源码。
最后
以上就是殷勤奇异果为你收集整理的浅谈linux - 内核时间的处理的全部内容,希望文章能够帮你解决浅谈linux - 内核时间的处理所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复