Linux 内核定时器实验————复习到这
目錄
- Linux 時間管理和內(nèi)核定時器簡介
- 內(nèi)核時間管理簡介
- 內(nèi)核定時器簡介
- Linux 內(nèi)核短延時函數(shù)
- 硬件原理圖分析
- 實驗程序編寫
- 修改設(shè)備樹文件
- 定時器驅(qū)動程序編寫
- 編寫測試APP
- 運行測試
- 編譯驅(qū)動程序和測試APP
- 運行測試
定時器是我們最常用到的功能,一般用來完成定時功能,本章我們就來學(xué)習(xí)一下Linux 內(nèi)核提供的定時器API 函數(shù),通過這些定時器API 函數(shù)我們可以完成很多要求定時的應(yīng)用。Linux內(nèi)核也提供了短延時函數(shù),比如微秒、納秒、毫秒延時函數(shù),本章我們就來學(xué)習(xí)一下這些和時間有關(guān)的功能。
Linux 時間管理和內(nèi)核定時器簡介
內(nèi)核時間管理簡介
學(xué)習(xí)過UCOS 或FreeRTOS 的同學(xué)應(yīng)該知道,UCOS 或FreeRTOS 是需要一個硬件定時器提供系統(tǒng)時鐘,一般使用Systick 作為系統(tǒng)時鐘源。同理,Linux 要運行,也是需要一個系統(tǒng)時鐘的,至于這個系統(tǒng)時鐘是由哪個定時器提供的,筆者沒有去研究過Linux 內(nèi)核,但是在Cortex-A7 內(nèi)核中有個通用定時器,在《Cortex-A7 Technical ReferenceManua.pdf》的“9:Generic Timer”
章節(jié)有簡單的講解,關(guān)于這個通用定時器的詳細(xì)內(nèi)容,可以參考《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》的“chapter B8 The Generic Timer”章節(jié)。這個通用定時器是可選的,按照筆者學(xué)習(xí)FreeRTOS 和STM32 的經(jīng)驗,猜測Linux 會將這個通用定時器作為Linux 系統(tǒng)時鐘源(前提是SOC 得選配這個通用定時器)。具體是怎么做的筆者沒有深入研究過,這里僅僅是猜測!不過對于我們Linux 驅(qū)動編寫者來說,不需要深入研究這些具體的實現(xiàn),只需要掌握相應(yīng)的API 函數(shù)即可,除非你是內(nèi)核編寫者或者內(nèi)核愛好者。
Linux 內(nèi)核中有大量的函數(shù)需要時間管理,比如周期性的調(diào)度程序、延時程序、對于我們驅(qū)動編寫者來說最常用的定時器。硬件定時器提供時鐘源,時鐘源的頻率可以設(shè)置,設(shè)置好以后就周期性的產(chǎn)生定時中斷,系統(tǒng)使用定時中斷來計時。中斷周期性產(chǎn)生的頻率就是系統(tǒng)頻率,也叫做節(jié)拍率(tick rate)(有的資料也叫系統(tǒng)頻率),比如1000Hz,100Hz 等等說的就是系統(tǒng)節(jié)拍
率。系統(tǒng)節(jié)拍率是可以設(shè)置的,單位是Hz,我們在編譯Linux 內(nèi)核的時候可以通過圖形化界面設(shè)置系統(tǒng)節(jié)拍率,按照如下路徑打開配置界面:
選中“Timer frequency”,打開以后如圖50.1.1.1 所示:
從圖50.1.1.1 可以看出,可選的系統(tǒng)節(jié)拍率為100Hz、200Hz、250Hz、300Hz、500Hz 和1000Hz,默認(rèn)情況下選擇100Hz。設(shè)置好以后打開Linux 內(nèi)核源碼根目錄下的.config 文件,在此文件中有如圖50.1.1.2 所示定義:
圖50.1.1.2 中的CONFIG_HZ 為100,Linux 內(nèi)核會使用CONFIG_HZ 來設(shè)置自己的系統(tǒng)時鐘。打開文件include/asm-generic/param.h,有如下內(nèi)容:
第7 行定義了一個宏HZ,宏HZ 就是CONFIG_HZ,因此HZ=100,我們后面編寫Linux驅(qū)動的時候會常常用到HZ,因為HZ 表示一秒的節(jié)拍數(shù),也就是頻率。
大多數(shù)初學(xué)者看到系統(tǒng)節(jié)拍率默認(rèn)為100Hz 的時候都會有疑問,怎么這么小?100Hz 是可選的節(jié)拍率里面最小的。為什么不選擇大一點的呢?這里就引出了一個問題:高節(jié)拍率和低節(jié)拍率的優(yōu)缺點:
①、高節(jié)拍率會提高系統(tǒng)時間精度,如果采用100Hz 的節(jié)拍率,時間精度就是10ms,采用1000Hz 的話時間精度就是1ms,精度提高了10 倍。高精度時鐘的好處有很多,對于那些對時間要求嚴(yán)格的函數(shù)來說,能夠以更高的精度運行,時間測量也更加準(zhǔn)確。
②、高節(jié)拍率會導(dǎo)致中斷的產(chǎn)生更加頻繁,頻繁的中斷會加劇系統(tǒng)的負(fù)擔(dān),1000Hz 和100Hz的系統(tǒng)節(jié)拍率相比,系統(tǒng)要花費10 倍的“精力”去處理中斷。中斷服務(wù)函數(shù)占用處理器的時間增加,但是現(xiàn)在的處理器性能都很強大,所以采用1000Hz 的系統(tǒng)節(jié)拍率并不會增加太大的負(fù)載壓力。根據(jù)自己的實際情況,選擇合適的系統(tǒng)節(jié)拍率,本教程我們?nèi)坎捎媚J(rèn)的100Hz 系統(tǒng)節(jié)拍率。
Linux 內(nèi)核使用全局變量jiffies 來記錄系統(tǒng)從啟動以來的系統(tǒng)節(jié)拍數(shù),系統(tǒng)啟動的時候會將jiffies 初始化為0,jiffies 定義在文件include/linux/jiffies.h 中,定義如下:
76 extern u64 __jiffy_data jiffies_64; 77 extern unsigned long volatile __jiffy_data jiffies;第76 行,定義了一個64 位的jiffies_64。
第77 行,定義了一個unsigned long 類型的32 位的jiffies。jiffies_64 和jiffies 其實是同一個東西,jiffies_64 用于64 位系統(tǒng),而jiffies 用于32 位系統(tǒng)。
為了兼容不同的硬件,jiffies 其實就是jiffies_64 的低32 位,jiffies_64 和jiffies 的結(jié)構(gòu)如圖50.1.1.3 所示:
當(dāng)我們訪問jiffies 的時候其實訪問的是jiffies_64 的低32 位,使用get_jiffies_64 這個函數(shù)可以獲取jiffies_64 的值。在32 位的系統(tǒng)上讀取jiffies 的值,在64 位的系統(tǒng)上jiffes 和jiffies_64表示同一個變量,因此也可以直接讀取jiffies 的值。所以不管是32 位的系統(tǒng)還是64 位系統(tǒng),都可以使用jiffies。
前面說了HZ 表示每秒的節(jié)拍數(shù),jiffies 表示系統(tǒng)運行的jiffies 節(jié)拍數(shù),所以jiffies/HZ 就是系統(tǒng)運行時間,單位為秒。不管是32 位還是64 位的jiffies,都有溢出的風(fēng)險,溢出以后會重新從0 開始計數(shù),相當(dāng)于繞回來了,因此有些資料也將這個現(xiàn)象也叫做繞回。假如HZ 為最大值1000 的時候,32 位的jiffies 只需要49.7 天就發(fā)生了繞回,對于64 位的jiffies 來說大概需要5.8 億年才能繞回,因此jiffies_64 的繞回忽略不計。處理32 位jiffies 的繞回顯得尤為重要,Linux 內(nèi)核提供了如表50.1.1.1 所示的幾個API 函數(shù)來處理繞回。
如果unkown 超過known 的話,time_after 函數(shù)返回真,否則返回假。如果unkown 沒有超過known 的話time_before 函數(shù)返回真,否則返回假。time_after_eq 函數(shù)和time_after 函數(shù)類似,只是多了判斷等于這個條件。同理,time_before_eq 函數(shù)和time_before 函數(shù)也類似。比如我們要判斷某段代碼執(zhí)行時間有沒有超時,此時就可以使用如下所示代碼:
timeout 就是超時時間點,比如我們要判斷代碼執(zhí)行時間是不是超過了2 秒,那么超時時間點就是jiffies+(2*HZ),如果jiffies 大于timeout 那就表示超時了,否則就是沒有超時。第4~6 行就是具體的代碼段。第9 行通過函數(shù)time_before 來判斷jiffies 是否小于timeout,如果小于的話就表示沒有超時。
為了方便開發(fā),Linux 內(nèi)核提供了幾個jiffies 和ms、us、ns 之間的轉(zhuǎn)換函數(shù),如表50.1.1.2所示:
內(nèi)核定時器簡介
定時器是一個很常用的功能,需要周期性處理的工作都要用到定時器。Linux 內(nèi)核定時器采用系統(tǒng)時鐘來實現(xiàn),并不是我們在裸機篇中講解的PIT 等硬件定時器。Linux 內(nèi)核定時器使用很簡單,只需要提供超時時間(相當(dāng)于定時值)和定時處理函數(shù)即可,當(dāng)超時時間到了以后設(shè)置的定時處理函數(shù)就會執(zhí)行,和我們使用硬件定時器的套路一樣,只是使用內(nèi)核定時器不需要做一大堆的寄存器初始化工作。在使用內(nèi)核定時器的時候要注意一點,內(nèi)核定時器并不是周期性運行的,超時以后就會自動關(guān)閉,因此如果想要實現(xiàn)周期性定時,那么就需要在定時處理函數(shù)中重新開啟定時器。Linux 內(nèi)核使用timer_list 結(jié)構(gòu)體表示內(nèi)核定時器,timer_list 定義在文件include/linux/timer.h 中,定義如下(省略掉條件編譯):
struct timer_list { struct list_head entry; unsigned long expires; /* 定時器超時時間,單位是節(jié)拍數(shù)*/ struct tvec_base *base; void (*function)(unsigned long); /* 定時處理函數(shù)*/ unsigned long data; /* 要傳遞給function函數(shù)的參數(shù)*/ int slack; };要使用內(nèi)核定時器首先要先定義一個timer_list 變量,表示定時器,tiemr_list 結(jié)構(gòu)體的expires 成員變量表示超時時間,單位為節(jié)拍數(shù)。比如我們現(xiàn)在需要定義一個周期為2 秒的定時器,那么這個定時器的超時時間就是jiffies+(2HZ),因此expires=jiffies+(2HZ)。function 就是定時器超時以后的定時處理函數(shù),我們要做的工作就放到這個函數(shù)里面,需要我們編寫這個定時處理函數(shù)。
定義好定時器以后還需要通過一系列的API 函數(shù)來初始化此定時器,這些函數(shù)如下:
1、init_timer 函數(shù)
init_timer 函數(shù)負(fù)責(zé)初始化timer_list 類型變量,當(dāng)我們定義了一個timer_list 變量以后一定要先用init_timer 初始化一下。init_timer 函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
timer:要初始化定時器。
返回值:沒有返回值。
2、add_timer 函數(shù)
add_timer 函數(shù)用于向Linux 內(nèi)核注冊定時器,使用add_timer 函數(shù)向內(nèi)核注冊定時器以后,定時器就會開始運行,函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
timer:要注冊的定時器。
返回值:沒有返回值。
3、del_timer 函數(shù)
del_timer 函數(shù)用于刪除一個定時器,不管定時器有沒有被激活,都可以使用此函數(shù)刪除。
在多處理器系統(tǒng)上,定時器可能會在其他的處理器上運行,因此在調(diào)用del_timer 函數(shù)刪除定時器之前要先等待其他處理器的定時處理器函數(shù)退出。del_timer 函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
timer:要刪除的定時器。
返回值:0,定時器還沒被激活;1,定時器已經(jīng)激活。
4、del_timer_sync 函數(shù)
del_timer_sync 函數(shù)是del_timer 函數(shù)的同步版,會等待其他處理器使用完定時器再刪除,del_timer_sync 不能使用在中斷上下文中。del_timer_sync 函數(shù)原型如下所示:
函數(shù)參數(shù)和返回值含義如下:
timer:要刪除的定時器。
返回值:0,定時器還沒被激活;1,定時器已經(jīng)激活。
5、mod_timer 函數(shù)
mod_timer 函數(shù)用于修改定時值,如果定時器還沒有激活的話,mod_timer 函數(shù)會激活定時器!函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
timer:要修改超時時間(定時值)的定時器。
expires:修改后的超時時間。
返回值:0,調(diào)用mod_timer 函數(shù)前定時器未被激活;1,調(diào)用mod_timer 函數(shù)前定時器已被激活。
關(guān)于內(nèi)核定時器常用的API 函數(shù)就講這些,內(nèi)核定時器一般的使用流程如下所示:
Linux 內(nèi)核短延時函數(shù)
有時候我們需要在內(nèi)核中實現(xiàn)短延時,尤其是在Linux 驅(qū)動中。Linux 內(nèi)核提供了毫秒、微秒和納秒延時函數(shù),這三個函數(shù)如表50.1.3.1 所示:
硬件原理圖分析
本章使用通過設(shè)置一個定時器來實現(xiàn)周期性的閃爍LED 燈,因此本章例程就使用到了一個LED 燈,關(guān)于LED 燈的硬件原理圖參考參考8.3 小節(jié)即可。
實驗程序編寫
本實驗對應(yīng)的例程路徑為:開發(fā)板光盤-> 2、Linux 驅(qū)動例程-> 12_timer。
本章實驗我們使用內(nèi)核定時器周期性的點亮和熄滅開發(fā)板上的LED 燈,LED 燈的閃爍周期由內(nèi)核定時器來設(shè)置,測試應(yīng)用程序可以控制內(nèi)核定時器周期。
修改設(shè)備樹文件
本章實驗使用到了LED 燈,LED 燈的設(shè)備樹節(jié)點信息使用45.4.1 小節(jié)創(chuàng)建的即可。
定時器驅(qū)動程序編寫
新建名為“12_timer”的文件夾,然后在12_timer 文件夾里面創(chuàng)建vscode 工程,工作區(qū)命名為“timer”。工程創(chuàng)建好以后新建timer.c 文件,在timer.c 里面輸入如下內(nèi)容:
#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> /*************************************************************** Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : timer.c 作者 : 左忠凱 版本 : V1.0 描述 : Linux內(nèi)核定時器實驗 其他 : 無 論壇 : www.openedv.com 日志 : 初版V1.0 2019/7/24 左忠凱創(chuàng)建 ***************************************************************/ #define TIMER_CNT 1 /* 設(shè)備號個數(shù) */ #define TIMER_NAME "timer" /* 名字 */ #define CLOSE_CMD (_IO(0XEF, 0x1)) /* 關(guān)閉定時器 */ #define OPEN_CMD (_IO(0XEF, 0x2)) /* 打開定時器 */ #define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 設(shè)置定時器周期命令 */ #define LEDON 1 /* 開燈 */ #define LEDOFF 0 /* 關(guān)燈 *//* timer設(shè)備結(jié)構(gòu)體 */ struct timer_dev{dev_t devid; /* 設(shè)備號 */struct cdev cdev; /* cdev */struct class *class; /* 類 */struct device *device; /* 設(shè)備 */int major; /* 主設(shè)備號 */int minor; /* 次設(shè)備號 */struct device_node *nd; /* 設(shè)備節(jié)點 */int led_gpio; /* key所使用的GPIO編號 */int timeperiod; /* 定時周期,單位為ms */struct timer_list timer;/* 定義一個定時器*/spinlock_t lock; /* 定義自旋鎖 */ };struct timer_dev timerdev; /* timer設(shè)備 *//** @description : 初始化LED燈IO,open函數(shù)打開驅(qū)動的時候* 初始化LED燈所使用的GPIO引腳。* @param : 無* @return : 無*/ static int led_init(void) {int ret = 0;timerdev.nd = of_find_node_by_path("/gpioled");if (timerdev.nd== NULL) {return -EINVAL;}timerdev.led_gpio = of_get_named_gpio(timerdev.nd ,"led-gpio", 0);if (timerdev.led_gpio < 0) {printk("can't get led\r\n");return -EINVAL;}/* 初始化led所使用的IO */gpio_request(timerdev.led_gpio, "led"); /* 請求IO */ret = gpio_direction_output(timerdev.led_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}return 0; }/** @description : 打開設(shè)備* @param - inode : 傳遞給驅(qū)動的inode* @param - filp : 設(shè)備文件,file結(jié)構(gòu)體有個叫做private_data的成員變量* 一般在open的時候?qū)rivate_data指向設(shè)備結(jié)構(gòu)體。* @return : 0 成功;其他 失敗*/ static int timer_open(struct inode *inode, struct file *filp) {int ret = 0;filp->private_data = &timerdev; /* 設(shè)置私有數(shù)據(jù) */timerdev.timeperiod = 1000; /* 默認(rèn)周期為1s */ret = led_init(); /* 初始化LED IO */if (ret < 0) {return ret;}return 0; }/** @description : ioctl函數(shù),* @param - filp : 要打開的設(shè)備文件(文件描述符)* @param - cmd : 應(yīng)用程序發(fā)送過來的命令* @param - arg : 參數(shù)* @return : 0 成功;其他 失敗*/ static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {struct timer_dev *dev = (struct timer_dev *)filp->private_data;int timerperiod;unsigned long flags;switch (cmd) {case CLOSE_CMD: /* 關(guān)閉定時器 */del_timer_sync(&dev->timer);break;case OPEN_CMD: /* 打開定時器 */spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));break;case SETPERIOD_CMD: /* 設(shè)置定時器周期 */spin_lock_irqsave(&dev->lock, flags);dev->timeperiod = arg;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));break;default:break;}return 0; }/* 設(shè)備操作函數(shù) */ static struct file_operations timer_fops = {.owner = THIS_MODULE,.open = timer_open,.unlocked_ioctl = timer_unlocked_ioctl, };/* 定時器回調(diào)函數(shù) */ void timer_function(unsigned long arg) {struct timer_dev *dev = (struct timer_dev *)arg;static int sta = 1;int timerperiod;unsigned long flags;sta = !sta; /* 每次都取反,實現(xiàn)LED燈反轉(zhuǎn) */gpio_set_value(dev->led_gpio, sta);/* 重啟定時器 */spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod)); }/** @description : 驅(qū)動入口函數(shù)* @param : 無* @return : 無*/ static int __init timer_init(void) {/* 初始化自旋鎖 */spin_lock_init(&timerdev.lock);/* 注冊字符設(shè)備驅(qū)動 *//* 1、創(chuàng)建設(shè)備號 */if (timerdev.major) { /* 定義了設(shè)備號 */timerdev.devid = MKDEV(timerdev.major, 0);register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);} else { /* 沒有定義設(shè)備號 */alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT, TIMER_NAME); /* 申請設(shè)備號 */timerdev.major = MAJOR(timerdev.devid); /* 獲取分配號的主設(shè)備號 */timerdev.minor = MINOR(timerdev.devid); /* 獲取分配號的次設(shè)備號 */}/* 2、初始化cdev */timerdev.cdev.owner = THIS_MODULE;cdev_init(&timerdev.cdev, &timer_fops);/* 3、添加一個cdev */cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);/* 4、創(chuàng)建類 */timerdev.class = class_create(THIS_MODULE, TIMER_NAME);if (IS_ERR(timerdev.class)) {return PTR_ERR(timerdev.class);}/* 5、創(chuàng)建設(shè)備 */timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);if (IS_ERR(timerdev.device)) {return PTR_ERR(timerdev.device);}/* 6、初始化timer,設(shè)置定時器處理函數(shù),還未設(shè)置周期,所有不會激活定時器 */init_timer(&timerdev.timer);timerdev.timer.function = timer_function;timerdev.timer.data = (unsigned long)&timerdev;return 0; }/** @description : 驅(qū)動出口函數(shù)* @param : 無* @return : 無*/ static void __exit timer_exit(void) {gpio_set_value(timerdev.led_gpio, 1); /* 卸載驅(qū)動的時候關(guān)閉LED */del_timer_sync(&timerdev.timer); /* 刪除timer */ #if 0del_timer(&timerdev.tiemr); #endif/* 注銷字符設(shè)備驅(qū)動 */cdev_del(&timerdev.cdev);/* 刪除cdev */unregister_chrdev_region(timerdev.devid, TIMER_CNT); /* 注銷設(shè)備號 */device_destroy(timerdev.class, timerdev.devid);class_destroy(timerdev.class); }module_init(timer_init); module_exit(timer_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");第38~50 行,定時器設(shè)備結(jié)構(gòu)體,在48 行定義了一個定時器成員變量timer。
第60~82 行,LED 燈初始化函數(shù),從設(shè)備樹中獲取LED 燈信息,然后初始化相應(yīng)的IO。
第91~102 行,函數(shù)timer_open,對應(yīng)應(yīng)用程序的open 函數(shù),應(yīng)用程序調(diào)用open 函數(shù)打開/dev/timer 驅(qū)動文件的時候此函數(shù)就會執(zhí)行。此函數(shù)設(shè)置文件私有數(shù)據(jù)為timerdev,并且初始化定時周期默認(rèn)為1 秒,最后調(diào)用led_init 函數(shù)初始化LED 所使用的IO。
第111~137 行,函數(shù)timer_unlocked_ioctl,對應(yīng)應(yīng)用程序的ioctl 函數(shù),應(yīng)用程序調(diào)用ioctl函數(shù)向驅(qū)動發(fā)送控制信息,此函數(shù)響應(yīng)并執(zhí)行。此函數(shù)有三個參數(shù):filp,cmd 和arg,其中filp是對應(yīng)的設(shè)備文件,cmd 是應(yīng)用程序發(fā)送過來的命令信息,arg 是應(yīng)用程序發(fā)送過來的參數(shù),在本章例程中arg 參數(shù)表示定時周期。
一共有三種命令CLOSE_CMD,OPEN_CMD 和SETPERIOD_CMD,這三個命令分別為關(guān)閉定時器、打開定時器、設(shè)置定時周期。這三個命令的左右如下:
CLOSE_CMD:關(guān)閉定時器命令,調(diào)用del_timer_sync 函數(shù)關(guān)閉定時器。
OPEN_CMD:打開定時器命令,調(diào)用mod_timer 函數(shù)打開定時器,定時周期為timerdev 的timeperiod 成員變量,定時周期默認(rèn)是1 秒。
SETPERIOD_CMD:設(shè)置定時器周期命令,參數(shù)arg 就是新的定時周期,設(shè)置timerdev 的timeperiod 成員變量為arg 所表示定時周期指。并且使用mod_timer 重新打開定時器,使定時器以新的周期運行。
第140~144 行,定時器驅(qū)動操作函數(shù)集timer_fops。
第147~162 行,函數(shù)timer_function,定時器服務(wù)函數(shù),此函有一個參數(shù)arg,在本例程中arg 參數(shù)就是timerdev 的地址,這樣通過arg 參數(shù)就可以訪問到設(shè)備結(jié)構(gòu)體。當(dāng)定時周期到了以后此函數(shù)就會被調(diào)用。在此函數(shù)中將LED 燈的狀態(tài)取反,實現(xiàn)LED 燈閃爍的效果。因為內(nèi)核定時器不是循環(huán)的定時器,執(zhí)行一次以后就結(jié)束了,因此在161 行又調(diào)用了mod_timer 函數(shù)重
新開啟定時器。
第169~ 209 行,函數(shù)timer_init,驅(qū)動入口函數(shù)。在第205~207 行初始化定時器,設(shè)置定時器的定時處理函數(shù)為timer_function,另外設(shè)置要傳遞給timer_function 函數(shù)的參數(shù)為timerdev的地址。在此函數(shù)中并沒有調(diào)用timer_add 函數(shù)來開啟定時器,因此定時器默認(rèn)是關(guān)閉的,除非應(yīng)用程序發(fā)送打開命令。
第216~231 行,驅(qū)動出口函數(shù),在219 行關(guān)閉LED,也就是卸載驅(qū)動以后LED 處于熄滅狀態(tài)。第220 行調(diào)用del_timer_sync 函數(shù)刪除定時器,也可以使用del_timer 函數(shù)。
編寫測試APP
測試APP 我們要實現(xiàn)的內(nèi)容如下:
①、運行APP 以后提示我們輸入要測試的命令,輸入1 表示關(guān)閉定時器、輸入2 表示打開定時器,輸入3 設(shè)置定時器周期。
②、如果要設(shè)置定時器周期的話,需要讓用戶輸入要設(shè)置的周期值,單位為毫秒。
新建名為timerApp.c 的文件,然后輸入如下所示內(nèi)容:
第22~24 行,命令值。
第53~73 行,while(1)循環(huán),讓用戶輸入要測試的命令,然后通過第72 行的ioctl 函數(shù)發(fā)送給驅(qū)動程序。如果是設(shè)置定時器周期命令SETPERIOD_CMD,那么ioctl 函數(shù)的arg 參數(shù)就是用戶輸入的周期值。
運行測試
編譯驅(qū)動程序和測試APP
1、編譯驅(qū)動程序
編寫Makefile 文件,本章實驗的Makefile 文件和第四十章實驗基本一樣,只是將obj-m 變量的值改為timer.o,Makefile 內(nèi)容如下所示:
第4 行,設(shè)置obj-m 變量的值為timer.o。
輸入如下命令編譯出驅(qū)動模塊文件:
編譯成功以后就會生成一個名為“timer.ko”的驅(qū)動模塊文件。
2、編譯測試APP
輸入如下命令編譯測試timerApp.c 這個測試程序:
編譯成功以后就會生成timerApp 這個應(yīng)用程序。
運行測試
將上一小節(jié)編譯出來的timer.ko 和timerApp 這兩個文件拷貝到rootfs/lib/modules/4.1.15 目錄中,重啟開發(fā)板,進入到目錄lib/modules/4.1.15 中,輸入如下命令加載timer.ko 驅(qū)動模塊:
depmod //第一次加載驅(qū)動的時候需要運行此命令 modprobe timer.ko //加載驅(qū)動驅(qū)動加載成功以后如下命令來測試:
./timerApp /dev/timer輸入上述命令以后終端提示輸入命令,如圖50.4.2.1 所示:
輸入“2”,打開定時器,此時LED 燈就會以默認(rèn)的1 秒周期開始閃爍。在輸入“3”來設(shè)置定時周期,根據(jù)提示輸入要設(shè)置的周期值,如圖50.4.2.2 所示:
輸入“500”,表示設(shè)置定時器周期值為500ms,設(shè)置好以后LED 燈就會以500ms 為間隔,開始閃爍。最后可以通過輸入“1”來關(guān)閉定時器,如果要卸載驅(qū)動的話輸入如下命令即可:
總結(jié)
以上是生活随笔為你收集整理的Linux 内核定时器实验————复习到这的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数学建模2020B题穿越沙漠
- 下一篇: 通过CMD命令行创建和使用Android