Hasen的linux设备驱动开发学习之旅--时钟

时间:2023-03-08 23:38:37
Hasen的linux设备驱动开发学习之旅--时钟
/**
* Author:hasen
* 參考 :《linux设备驱动开发具体解释》
* 简单介绍:android小菜鸟的linux
* 设备驱动开发学习之旅
* 主题:时钟
* Date:2014-11-15
*/

一、内核定时器

            1、内核定时器编程

            软件意义上的定时器终于依赖硬件定时器来是实现。内核在时钟中断发生后运行检測各定时器是否到期,

到期后的定时器处理函数将作为软中断在底半部运行。实质上,时钟中断处理程序会唤起TIMER_SOFTIRQ

软中断,执行当前处理器上到期的全部定时器。

Linux设备驱动编程中。能够利用内核中提供的一组函数和数据结构来完毕定时触发工作或者完毕某周期

性的事务。这组函数和数据使得驱动project师多数情况下不用关心详细的软件定时器到底相应着如何的内核和硬件

行为。

            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<hasen.dc@gmail.com>") ;
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()忙等待机制在驱动

中一般是为了配合硬件上的短时延迟要求。