1. 程式人生 > >Hasen的linux設備驅動開發學習之旅--時鐘

Hasen的linux設備驅動開發學習之旅--時鐘

interval 好的 mask 再次 ask 中斷處理程序 eas 中斷 基礎

/**
 * 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<[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()忙等待機制在驅動
中一般是為了配合硬件上的短時延遲要求。

Hasen的linux設備驅動開發學習之旅--時鐘