Hasen的linux裝置驅動開發學習之旅--時鐘

來源:互聯網
上載者:User

標籤:核心   linux   驅動開發   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) ; //計數清0return 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裝置驅動開發學習之旅--時鐘

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.