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()忙等待机制在驱动
中一般是为了配合硬件上的短时延迟要求。
Hasen的linux设备驱动开发学习之旅--时钟的更多相关文章
- 《Linux设备驱动开发具体解释(第3版)》(即《Linux设备驱动开发具体解释:基于最新的Linux 4.0内核》)网购链接
<Linux设备驱动开发具体解释:基于最新的Linux 4.0内核> china-pub spm=a1z10.3-b.w4011-10017777404.30.kvceXB&i ...
- Linux 设备驱动开发 —— platform设备驱动应用实例解析
前面我们已经学习了platform设备的理论知识Linux 设备驱动开发 —— platform 设备驱动 ,下面将通过一个实例来深入我们的学习. 一.platform 驱动的工作过程 platfor ...
- Linux设备驱动开发环境的搭建(转)
经过两周的摸索,终于对Linux设备驱动开发有了个初步的认识,下面对Linux设备驱动开发环境的搭建做个小结,以方便自己以后查询,同时也能给同道的初学者一点帮助. 刚接触Linux设备驱动时,初学者往 ...
- 《Linux设备驱动开发具体解释(第3版)》进展同步更新
本博实时更新<Linux设备驱动开发具体解释(第3版)>的最新进展. 2015.2.26 差点儿完毕初稿. 本书已经rebase到开发中的Linux 4.0内核,案例多数基于多核CORTE ...
- Linux设备驱动开发详解
Linux设备驱动开发详解 http://download.csdn.net/detail/wuyouzi067/9581380
- 《Linux设备驱动开发详解(第2版)》配套视频登录51cto教育频道
http://edu.51cto.com/course/course_id-379-page-1.html http://edu.51cto.com/course/course_id-379-page ...
- 转:Linux设备驱动开发(1):内核基础概念
一.linux设备驱动的作用 内核:用于管理软硬件资源,并提供运行环境.如分配4G虚拟空间等. linux设备驱动:是连接硬件和内核之间的桥梁. linux系统按个人理解可按下划分: 应用层:包括PO ...
- Linux设备驱动开发详解-Note(11)--- Linux 文件系统与设备文件系统(3)
Linux 文件系统与设备文件系统(3) 成于坚持,败于止步 sysfs 文件系统与 Linux 设备模型 1.sysfs 文件系统 Linux 2.6 内核引入了 sysfs 文件系统,sysfs ...
- linux spi驱动开发学习-----spidev.c和spi test app
一.spidev.c文件 看一个设备驱动的方法: module_init标识的入口初始化函数spidev_init,(module_exit标识的出口函数) 设备与设备驱动匹配时候调用的probe方法 ...
随机推荐
- 玩转oracle学习第七天
1.上节回想 2.PL/SQL的进阶 3.oracle的视图 4.oracle的触发器 目标: 1.掌握PL/SQL的高级使用方法(能编写分页过程模块,下订单过程模块.. . ) 2.会处理or ...
- angular学习笔记(二十七)-$http(5)-使用$http构建RESTful架构
在angular中有一个特别为RESTful架构而定制的服务,是在$http的基础上进行了封装. 但是为了学习,我们先看看用直接$http是如何构建RESTful架构的: 假设有一个银行卡的列表.需要 ...
- angular学习笔记(十五)-module里的'服务'
本篇介绍angular中的模块:module 在笔记(二)http://www.cnblogs.com/liulangmao/p/3711047.html里已经讲到过模块,这篇主要讲模块的 '服务' ...
- nyoj311 完全背包 经典背包问题
完全背包 时间限制:3000 ms | 内存限制:65535 KB 难度:4 描述 直接说题意,完全背包定义有N种物品和一个容量为V的背包,每种物品都有无限件可用.第i种物品的体积是c,价值是w. ...
- gulp-uglify的使用
gulp教程之gulp-uglify 简介: 使用gulp-uglify压缩javascript文件,减小文件大小. 1.安装nodejs/全局安装gulp/项目安装gulp/创建package.js ...
- LeetCode: Swap Nodes in Pairs 解题报告
Swap Nodes in Pairs Given a linked list, swap every two adjacent nodes and return its head. For exam ...
- js photoswipe 相册使用 移动pc端均可
http://photoswipe.com/ 官网 这里使用的是最新 4.1.1版本 http://photoswipe.com/documentation/getting-started.html ...
- html5标签video(播放器)学习笔记(二)-基本操作
html5标签video(播放器)学习笔记(二)-基本操作 subying 发布时间: 2014/12/01 23:59 阅读: 13008 收藏: 21 点赞: 3 评论: 0 摘要 本文介绍了ht ...
- linux的一些软件基本安装
买了个腾讯云的服务器,开始玩起来了,先装环境吧. JAVA安装 安装个java yum -y install java-1.7.0-openjdk* 查看java版本 java -version 可以 ...
- 【Unity笔记】第三人称相机跟随
第三人称,摄像机跟在角色后上方. void Update () { myCamera.position = transform.position + , ); myCamera.LookAt(tran ...