Hasen的linux设备驱动开发学习之旅--时钟
/** * Author:hasen * 参考 :《linux设备驱动开发详解》 * 简介:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:时钟 * Date:2014-11-15 */一、内核定时器
1、内核定时器编程
软件意义上的定时器最终依赖硬件定时器来是实现,内核在时钟中断发生后执行检测各定时器是否到期,
到期后的定时器处理函数将作为软中断在底半部执行。实质上,时钟中断处理程序会唤起TIMER_SOFTIRQ
软中断,运行当前处理器上到期的所有定时器。
Linux设备驱动编程中,可以利用内核中提供的一组函数和数据结构来完成定时触发工作或者完成某周期
性的事务。这组函数和数据使得驱动工程师多数情况下不用关心具体的软件定时器究竟对应着怎样的内核和硬件
行为。
Linux内核所提供的用于操作定时器的数据结构和函数如下:
(1)timer_list
在Linux内核中,timer_list结构体的一个实例对应一个定时器。
struct timer_list { struct list_head entry ;/*定时器列表*/ unsigned long expires ; /*定时器到期时间(jiffies)*/ void (*function)(unsigned long );/*定时器处理函数*/ unsigned long data ;/*作为参数传入定时器处理函数*/ struct timer_base_s base ; }下面定义一个my_timer的定时器
struct timer_lsit my_timer ;(2)初始化定时器
void init_timer(struct timer_list *timer) ;上述init_timer()函数初始化timer_list的entry的next为NULL,并给base指针赋值。
TIMER_INITIALIZER(_function,_expires,_data)宏用于赋值定时器结构体的function、expires、data、
base这几个成员,这个宏的定义是:
#define TIMER_INITIALIZER(_function,_expires,_data){ .entry = {.prev = TIMER_ENTRY_STATIC} , .funciton = (_function), .expires = (_expires) , .data = (_data) , .base = &boot_tvec_bases , }DEFINE_TIMER(_name,_function,_espires,_data)宏是定义并初始化定时器成员的“快捷方式”。这个
宏定义如下:
#define DEFINE_TIMER(_name,_function,_expires,_data) struct timer_list _name = TIMER_INITIALIZER(_function,_expires,_data)此外,setup_timer()函数也可以用来初始化定时器并给其成员赋值,其代码如下:
static inline void setup_timer(struct timer_list *timer, void (*function)(unsigned long),unsigned long data) { timer->function = function ; timer->data = data ; init_timer(timer) ; }(3)增加定时器
void add_timer(struct timer_list *timer) ;上述函数用于注册内核定时器,将定时器接入到内核动态定时器链表中。
(4)删除定时器
int del_timer(struct timer_lsit *timer) ;上述函数用于删除定时器。
del_timer_sync()是del_timer()的同步版,在删除一个定时器时等待其被处理完,因此该函数的调用不
能发生在中断上下文。
(5)修改定时器的expire
int mod_timer(struct timer_list *timer,unsigned long expires) ;上述函数用于修改定时器的到期时间,在新的被传入的expires到来后才会执行定时器函数。
示例:内核定时器使用模板
/*xxx设备结构体*/ struct xx_dev{ struct cdev cdev ; ... timer_lsit xxx_timer ;/*设备要使用的定时器*/ } /*xxx驱动中的模函数*/ xxx_func1(...) { struct xxx_dev *dev = filp->private_data ; ... /*初始化定时器*/ init_timer(&dev->xxx_timer) ; dev->xxx_timer.function = &xxx_do_timer ; dev->xxx_timer.data = (unsigned long)dev ;/*设备结构体指针作为定时器处理函数参数*/ dev->xxx_timer.expires = jiffies + delay ; /*添加(注册)定时器*/ add_timer(&dev->xxx_timer) ; ... } /*xxx驱动中的某函数*/ xxx_func2(...) { ... /*删除定时器*/ del_timer(&dev->xxx_timer) ; ... } /*定时器处理函数*/ static void xxx_do_timer(unsigned long arg) { struct xxx_device *dev = (struct xxx_device*)(arg) ; ... /*调度定时器再执行*/ dev->xxx_timer.expires = jiffies + delay ; add_timer(&dev->xxx_timer) ; ... }定时器的到期时间往往是在目前的jiffies的基础上添加一个时延,若为Hz,则表示延迟1秒。
定时器处理函数中,在做完相应的工作后,往往会延后expires并将定时器再次添加到内核定时器链表
中,以便定时器能再次被触发。
2、内核中延迟的工作delayed_work
注意,对于周期性的任务,Linux还提供了一套封装好的快捷机制,其本质是利用工作队列和定时器实
现,这套机制就是delayed_work,delayed_work结构体的定义如下:
struct delayed_work{ struct work_struct work ; struct timer_list timer ; }; struct work_struct { atimic_long_t data ; #define WORK_STRUCT_PENDING 0 #define WORK_STRUCT_FLAG_MASK (3UL) #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK) struct list_head entry ; work_func_t func ; #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map ; #endif };我们可以通过如下的函数调度一个delayed_work在指定的延时后执行。
int schedule_delayed_work(struct delayed_work *work,unsigned long delay) ;当指定的delay到来时delayed_work结构体中work成员的work_func_t类型成员func()会被执行。
work_func_t类型定义为:
typedef void (*work_func_t) (struct work_sturct *work);其中delay参数的单位是jiffies,因此一种常见的与用法如下:
schedule_delayed_work(&work,msecs_to_jiffies(poll_interval)) ;其中的msecs_to_jiffies()用于将毫秒转化为jiffies。
如果要周期性的执行任务,通常会在delayed_work()函数中再次调用schedule_delayed_work(),周而复
始。
如下的函数用来取消delayed_work:
int cancel_delayed_work(struct delayed_work *work) ; int cancel_delayed_work_sync(struct delayed_work *work) ;实例:秒字符设备
下面是一个字符设备“second”(即“秒”)的驱动,它在被打开的时候初始化一个定时器并将其添加到
内核定时器链表,每秒输出依次当前的jiffies(为此,定时器处理函数中每次都要修改新的expires)。
#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/cdev.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h> #define SECOND_MAJOR 248 /*预设的second的主设备号*/ static int second_major = SECONG_MAJOR ; /*second设备结构体*/ struct second_dev{ struct cdev cdev ;/*cdev结构体*/ atomic_t counter ;/*一共经历了多少秒*/ struct timer_list s_timer ;/*设备要使用的定时器*/ } struct second_dev *second_devp ;/*设备结构体指针*/ struct void second_timer_handle(unsigned long arg) { mod_timer(&second_devp->s_timer,jiffies + Hz) ; atomic_inc(&second_devp->counter) ; printk(KERN_NOTICE "current jiffies is %d\n",jiffies) ; } /*文件打开函数*/ int second_open(struct inode *inode ,struct file *filp) { /*初始化定时器*/ init_timer(&second_devp->s_timer); second_devp->s_timer.function = &second_timer_handle ; second_devp->s_timer.expires = jiffies + Hz ; add_timer(&second_devp->s_timer) ;/*添加(注册)定时器*/ atomic_set(&second_devp->count,0) ; //计数清0 return 0 ; } /*文件释放函数*/ int second_release(struct inode *inode ,struct file *filp) { del_timer(&second_devp->s_timer) ; return 0 ; } /*读函数*/ static ssize_t second_read(struct file *filp ,char __user *buf, size_t count,loff_t *ppos) { int counter ; counter = atomic_read(&second_devp->counter) ; if(put_user(counter,(int *)buf)) return -EFAULT ; else return sizeof(unsigned int) ; } /*文件操作结构体*/ static const struct file_operations second_fops = { .owner = THIS_MODULE , .open = second_open , .release = second_release , .read = second_read , } ; /*初始化并注册cdev*/ static void second_setup_cdev(struct second_dev *dev,int index) { int err,devno = MKDEV(second_major,index) ; cdev_init(&dev->cdev,&second_fops) ; dev->cdev.owner = THIS_MODULE ; err = cdev_add(&dev->cdev,devno,1) ; if(err) printk(KERN_NOTICE,"Error %d adding LED%d",err,index) ; } /*设备驱动模块加载函数*/ int second_init(void) { int ret ; dev_t devno = MKDEV(second_major,0) ; /*申请设备号*/ if(second_major) ret = register_chrdev_region(devno,1,"second") ; else{ ret = alloc_chrdev_region(&devno,0,1,"second") ; second_major = MAJOR(devno) ; } if(ret < 0) return ret ; /*动态申请设备结构体的内存*/ second_devp = kmalloc(sizeof(struct second_dev),GFP_KERN) ; if(!second_devp){/*申请失败*/ ret = -ENOMEM ; goto fail_malloc ; } memset(second_devp,0,sizeof(struct second_dev)) ; second_setup_cdev(second_devp,0) ; fail_malloc: unregister_chrdev_region(devno,1) ; return ret ; } /*模块卸载函数*/ void second_exit(void) { cdev_del(&second_devp->cdev) ; /*注销cdev*/ kfree(second_devp) ;/*释放设备结构体内存*/ unregister_chrdev_reigon(MKDEV(second_major,0),1) ; } MODULE_AUTHOR("Hasen<[email protected]>") ; MODULE_LICENSE("Dual BSD/GPL"); module_param(second_major,int,S_IRUGO) ; module_init(second_init) ; module_exit(second_exit) ;在second的open()函数中,将启动定时器,此后每一秒会再次运行定时器处理函数,在second的
release()函数中,定时器被删除。
second_dev结构体中的原子变量counter用于秒计数,每次在定时器处理函数中将被atomic_inc()
调用原子的增1,second的read()函数会将这个值返回给用户空间。
下面是一个second的测试程序second_test.c
#include ... main() { int fd ; int counter = 0 ; int old_counter = 0 ; /*打开/dev/second设备文件*/ fd= open("/dev/second",O_RDONLY) ; if(fd != -1){ while(1){ read(fd,&counter,sizeof(unsigned int)) ;/*读取目前经历的秒数*/ if(counter != old_counter){ printf("seconds after open /dev/second :%d\n",counter) ; old_counter = counter ; } } }else{ printf("Device open failure\n") ; } }运行second_test之后,内核将不断地输出目前的jiffies值,而应用程序将不断地输出自打开
/dev/second以来的秒数。
二、内核延时
1、短延时
Linux内核中提供了如下的3个函数分别进行纳秒、微秒和毫秒延迟:
void ndelay(unsigned long nsecs) ; void udelay(unsigned long usecs) ; void mdelay(unsigned long msecs) ;上述延迟的实现原理本质上是忙等待,它根据CPU频率进行一定次数的循环,软件中进行这样的延迟:
void delay(unsigned int time) { while(time--) ; }ndelay()、udelay()和mdelay()函数的实现方式机理与此类似。内核在启动是,会运行一个延迟测试
程序(delay looop calibration),计算出lpj(loop per jiffy)。例如对于LDD6410电路板而言,内核启动时会
打印:
Calibrating delay loop... 530.84 BogoMIPS (lpj=1327104)毫秒时延(以及更大的秒时延)已经比较大了,在内核当中,最好不要直接使用mdelay()函数,这将无
谓地耗费CPU资源,对于毫秒级以上时延,内核提供了下述函数:
void msleep(unsigned int millisecs) ; unsigned long msleep_interruptible(unsigned int millisecs) ; void ssleep(unsigned int seconds) ;上述函数将使得调用它的进程睡眠参数指定的时间,msleep()、ssleep()不能被打断,而
msleep_interruptible()则可以被打断。
2、长延时
内核中进行延迟的一个很直观的方法是比较当前的jiffies和目标jiffies(设置为当前jiffies加上时间
间隔的jiffies),直到未来的jiffies打到目标的jiffies。
示例:使用忙等待先延迟100个jiffies再延迟2s
/*延迟100个jiffies*/ unsigned long delay = jiffies + 100 ; while(time_before(jiffies,delay)) ; /*延迟2s*/ unsigned long delay = jiffies + 2*Hz ; while(time_before(jiffies,delay)) ;与time_before()对应的还有一个time_after(),它们在内核中定义为(实际上只是将传入的未来时
间jiffies和被调用时的jiffies进行一个简单的比较):
#define time_after(a,b) (typecheck(unsigned long ,a)) && typecheck(unsigned long ,b) && ((long (b)-(long)(a)<0)) #define time_before(a,b) time_after(b,a)为了防止timer_before()和timer_after()的比较过程中编译优化器对jiffies的优化,内核将其定义
为volatile变量,这将保证它每次都被重新读取。
3、睡着延迟
睡着延迟无疑是比忙等待更好的方式,睡着延迟在等待时间到来之间进程处于睡眠状态,CPU资源
被其他进程使用。schedule_timeout()可以使当前任务睡眠指定的jiffies之后重新被调度执行,msleep()和
msleep_interruptible()在本质上都是依靠包含了schedule_timeout()的schedule_timeout_uninterruptible()和
schedule_timeout_interruptible()实现的。
void msleep(unsigned int msecs) { unsigned long timeout = msecs_to_jiffies(msecs) +1; while(timeout) timeout = schedule_timeout_uninterruptible(timeout) ; } unsigned long msleep_interruptible(unsigned int msecs) { unsigned long timeout = msecs_to_jiffies(msecs) + 1 ; while(timeout && !signal_pending(current)) timeout = schedule_timeout_interruptible(timeout) ; return jiffies_to_msecs(timeout) ; }实际上,schedule_timeout()的实现原理是向系统添加一个定时器,在定时器处理函数中唤醒参数对
应的进程。schedule_timeout_uninterruptible()和schedule_timeout_interruptible()函数的区别在于前者调用
schedule_timeout()之前置进程状态为TASK_UNINTERRUPTIBLE,后者置进程状态为TASK_INTERRUPTIBLE。
signed long __sched schedule_timeout_interruptible(signed long timeout) { __set_current_state(TASK_INTERRUPTIBLE) ; return schedule_timeout(timeout) ; } signed long __sched schedule_timeout_uninterruptible(signed long timeout) { __set_current_state(TASK_UNINTERRUPTIBLE) ; return schedule_timeout(timeout) ; }另外,下面两个函数可以将当前进程添加到等待队列中,从而在等待队列上睡眠。当超时发生时,进
程将被唤醒(后者可以在超时前被打断):
sleep_on_timeout(wait_queue_head_t *q,unsigned long timeout) ; interruptible_sleep_on_timeout(wait_queue_head_t *q,unsigned long timeout) ;总结
内核中的延时可以采用忙等待或者睡眠等待,为了充分利用CPU资源,是系统有更好的吞吐性能,在对
延迟时间的要求并不是很精确的情况下,睡眠等待通常是值得推荐的。而ndelay()、udelay()忙等待机制在驱动
中通常是为了配合硬件上的短时延迟要求。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。