linux驅動之內核定時器驅動設計

2021-01-12 電子產品世界
我的環境:

Fedora 14 內核版本為2.6.38.1

開發板:ARM9 TQ2440

移植內核版本:linux-2.6.30.4

定時器在linux內核中主要是採用一個結構體實現的。但是需要注意定時器是一個只運行一次的對象,也就是當一個定時器結束以後,還需要重現添加定時器。但是可以採用mod_timer()函數動態的改變定時器到達時間。

這個驅動主要實現內核定時器的基本操作。內核定時器主要是是通過下面的結構體struct timer_list實現。需要的頭文件包括#include,但是在實際開發過程中不需要包含該頭文件,因為在sched.h中包含了該頭文件。

struct timer_list {

struct list_head entry;

unsigned long expires;

void (*function)(unsigned long);

unsigned long data;

struct tvec_base *base;

#ifdef CONFIG_TIMER_STATS

void *start_site;

char start_comm[16];

int start_pid;

#endif

#ifdef CONFIG_LOCKDEP

struct lockdep_map lockdep_map;

#endif

};

定時器的實現主要是該結構體的填充和部分函數的配合即可完成。其中紅色的部分是最主要的幾個元素,1、expires主要是用來定義定時器到期的時間,通常採用jiffies這個全局變量和HZ這個全局變量配合設置該元素的值。比如expires = jiffies + n*HZ,其中jiffies是自啟動以來的滴答數,HZ是一秒種的滴答數。

2、function可以知道是一個函數指針,該函數就是定時器的處理函數,類似我們在中斷中的中斷函數,其實定時器和中斷有很大的相似性。定時器處理函數是自己定義的函數。

3、data通常是實現參數的傳遞,從function的參數類型可以知道,data可以作為定時器處理函數的參數。

其他的元素可以通過內核的函數來初始化。

初始化函數為:

init_timer(struct timer_list * timer);

或者直接DEFINE_TIMER宏實現定義和初始化操作。

#define DEFINE_TIMER(_name, _function, _expires, _data)

struct timer_list _name =

TIMER_INITIALIZER(_function, _expires, _data)

添加定時器到內核的函數:

void add_timer(struct timer_list *timer)

{

BUG_ON(timer_pending(timer));

mod_timer(timer, timer->expires);

}

刪除定時器函數,如果定時器的定時時間還沒有到達,那麼才可以刪除定時器:

int del_timer(struct timer_list *timer)

修改定時器的到達時間,該函數的特點是,不管定時器是否到達時間,都會重現添加一個定時器到內核。所以可以在定時處理函數中可以調用該函數修改需要重新定義的到達時間。

int mode_timer(struct timer_list *timer,unsigned long expires)

int mod_timer(struct timer_list *timer, unsigned long expires)

{

/*

* This is a common optimization triggered by the

* networking code - if the timer is re-modified

* to be the same thing then just return:

*/

if (timer->expires == expires && timer_pending(timer))

return 1;

/*注意調用的條件,也就是說明當前的定時器為鍊表的最後一個*/

return __mod_timer(timer, expires, false);

}

static inline int

__mod_timer(struct timer_list *timer, unsigned long expires, bool pending_only)

{

struct tvec_base *base, *new_base;

unsigned long flags;

int ret;

ret = 0;

timer_stats_timer_set_start_info(timer);

BUG_ON(!timer->function);

base = lock_timer_base(timer, &flags);

if (timer_pending(timer)) {

detach_timer(timer, 0);

ret = 1;

} else {

if (pending_only)

goto out_unlock;

}

debug_timer_activate(timer);

new_base = __get_cpu_var(tvec_bases);

if (base != new_base) {

/*

* We are trying to schedule the timer on the local CPU.

* However we cant change timers base while it is running,

* otherwise del_timer_sync() cant detect that the timers

* handler yet has not finished. This also guarantees that

* the timer is serialized wrt itself.

*/

if (likely(base->running_timer != timer)) {

/* See the comment in lock_timer_base() */

timer_set_base(timer, NULL);

spin_unlock(&base->lock);

base = new_base;

spin_lock(&base->lock);

timer_set_base(timer, base);

}

}

timer->expires = expires;

internal_add_timer(base, timer);

out_unlock:

spin_unlock_irqrestore(&base->lock, flags);

return ret;

}

static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)

{

unsigned long expires = timer->expires;

unsigned long idx = expires - base->timer_jiffies;

struct list_head *vec;

if (idx < TVR_SIZE) {

int i = expires & TVR_MASK;

vec = base->tv1.vec + i;

} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {

int i = (expires >> TVR_BITS) & TVN_MASK;

vec = base->tv2.vec + i;

} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {

int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;

vec = base->tv3.vec + i;

} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {

int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;

vec = base->tv4.vec + i;

} else if ((signed long) idx < 0) {

/*

* Can happen if you add a timer with expires == jiffies,

* or you set a timer to go off in the past

*/

vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);

} else {

int i;

/* If the timeout is larger than 0xffffffff on 64-bit

* architectures then we use the maximum timeout:

*/

if (idx > 0xffffffffUL) {

idx = 0xffffffffUL;

expires = idx + base->timer_jiffies;

}

i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;

vec = base->tv5.vec + i;

}

/*

* Timers are FIFO:

*/

/*添加到鍊表的最後,這說明mod_timer實現了重新註冊一個定時器的操作*/

list_add_tail(&timer->entry, vec);

}

從上面的分析可以看出,mod_timer的實現過程比較複雜,但是基本上說明了mod_timer函數重新註冊定時器的操作過程。

一般而言定時器的基本操作主要是上面的幾個函數。

我的基於內核定時器的驅動函數如下,參考了宋寶華的Linux設備驅動開發詳解(第二版)。




技術專區

相關焦點

  • Linux驅動技術(七) _內核定時器與延遲工作
    內核定時器軟體上的定時器最終要依靠硬體時鐘來實現,簡單的說,內核會在時鐘中斷發生後檢測各個註冊到內核的定時器是否到期,如果到期
  • 「正點原子FPGA連載」第三十二章Linux內核定時器實驗
    1)摘自【正點原子】領航者 ZYNQ 之linux驅動開發指南2)實驗平臺:正點原子領航者ZYNQ開發板3)平臺購買地址:https://item.taobao.com/item.htm?這個通用定時器是可選的,按照筆者學習FreeRTOS和STM32的經驗,猜測Linux會將這個通用定時器作為Linux系統時鐘源(前提是SOC得選配這個通用定時器)。具體是怎麼做的筆者沒有深入研究過,這裡僅僅是猜測!不過對於我們Linux驅動編寫者來說,不需要深入研究這些具體的實現,只需要掌握相應的API函數即可,除非你是內核編寫者或者內核愛好者。
  • 基於華邦W90P710處理器的Linux內核應用及串口驅動的實現
    滋CLinux-2.4.20內核,支持4組通用異步接收發送口(UART),下面基於華邦W90P710的串口驅動詳細分析串口驅動的實現方法,實現嵌入式設備通過串口對外通信。設備驅動程序為應用程式屏蔽了硬體的細節,這樣在應用程式看來,硬體設備只是一個設備文件,應用程式可以像操作普通文件一樣對硬體設備進行操作。同時,設備驅動程序是內核的一部分[3]。圖1所示為設備驅動程序接口流程圖。
  • 內核不斷升級 如何學習linux設備驅動
    【IT168技術】面對不斷升級的linux內核、GNU開發工具、linux環境下的各種圖形庫,很多linux應用程式開發人員和linux設備驅動開發人員即興奮,又煩躁。興奮的是新的軟體軟體、工具給我提供了更強大的功能,煩躁的是適應新軟體的特性、搭建新環境是一項非常繁瑣的事情。
  • kali linux 升級內核 內核頭文件 nvidia驅動
    1.linux升級內核apt-get updateapt-get upgradeapt-get dist-upgradereboot12342.方法二apt update && apt full-upgrade13.安裝內核頭文件
  • Linux內核學習:簡單的字符設備驅動
    學習Linux內核最好的入門方式之一是從字符設備驅動開始模仿(來自於《奔跑吧 Linux內核——入門篇》)。對於我們日常生活中存在的大量設備,如攝像頭,USB充電器,藍牙,Wi-Fi等,這些設備在電氣特性和實現原理均不相同,對Linux系統來說如何抽象和描述他們呢?Linux很早就根據設備共同特徵將其劃分為三大類型:1,字符設備;塊設備;網絡設備。
  • linux內核字符設備驅動到platform驅動架構改造過程
    關注」技術簡說「(13站同名),帶你由淺入深學習linux內核源碼。linux內核開發100講免費教程,每周二、四晚9點準時更新,敬請收看。進我主頁點」視頻「欄目即可觀看。改造前的字符設備驅動源碼:include <linux/moduleparam.h>include <linux/fs.h>include <linux/poll.h>include <linux/slab.h>define OK (0)34;hello_open
  • Linux內核網絡設備驅動
    ,當驅動被加載時,內核會調用這個函數。每個 PCI 驅動註冊了一個 probe() 方法,內核會對每個設備依次調用其驅動的 probe 方法,一旦找到一個合適的驅動,就不會再為這個設備嘗試其他驅動。很多驅動都需要大量代碼來使得設備 ready,具體做的事情各有差異。
  • Linux內核驅動程序的配置
    實例引導:S3C2440 處理器的RTC 與 LED 驅動配置。obj-n 時,則不編譯 s3c2410-rtc.c.一般而言,驅動工程師在內核原始碼的 drviers 目錄的相應子目錄中增加新設備驅動的原始碼,並增加或修改 Kconfig 配置腳本和Makefile 腳本,完全仿照上述過程執行即可。
  • PWM在ARM Linux中的原理和蜂鳴器驅動實例開發
    有振蕩源的通電就可以發聲,沒有振蕩源的需要脈衝信號驅動才能發聲。_TCFG0);//將值tcfg0寫入定時器配置寄存器0中clk_p=clk_get(NULL,"pclk");pclk=clk_get_rate(clk_p);//從系統平臺時鐘隊列中獲取pclk的時鐘頻率,在include/linux/clk.h中定義tcnt=(pclk/50/16)/cmd;//計算定時器0的輸出時鐘頻率(pclk/{prescaler0 +
  • 嵌入式Linux設備驅動開發之:實驗內容——test驅動
    本文引用地址:http://www.eepw.com.cn/article/257106.htm1.實驗目的該實驗是編寫最簡單的字符驅動程序,這裡的設備也就是一段內存,實現簡單的讀寫功能,並列出常用格式的Makefile以及驅動的加載和卸載腳本。讀者可以熟悉字符設備驅動的整個編寫流程。
  • Android Linux 內核介紹
    已經有一些的文章介紹Android內核了,本系列篇將從Linux內核的角度來分析Android的內核,希望給初學者提夠有用的信息。本章將簡單的介紹 Android內核的全貌,起到一個拋磚引玉的作用。從下一篇開始將詳細介紹每一個Android內核驅動程序及其作用。
  • Linux環境下的USB攝像頭驅動開發
    本文從Linux內核的USB核心模塊出發,遵循Video4Linux接口標準,採用urb策略與內存映射的方式以提高數據讀取速度,設計開發了基於Linux環境下的USB攝像頭驅動,並在ARM9實驗平臺上對該驅動程序進行了測試與分析。
  • arm驅動linux內核中斷編程
    Tip:(flag是可以通過或的方式同時使用的)devname:設置中斷名稱,通常是設備驅動程序的名稱 在cat /proc/interrupts中可以看到此名稱。dev_id:在中斷共享時會用到,一般設置為這個設備的設備結構體或者不使用時為NULL。
  • 基於Linux系統的觸控螢幕驅動方案
    Linux設備驅動的實現只需根據內核提供的一組相關數據結構和驅動接口標準,完成關鍵數據結構初始化和回調函數的編寫。對字符設備驅動內核提供cdev數據結構和file_operatiONs結構體及操作方法,實現字符設備驅動只需完成cdev的初始化、file_operations中操作函數的實現並向內核註冊。
  • Linux2.6內核驅動移植參考
    作者:晏渭川 隨著Linux2.6的發布,由於2.6內核做了教的改動,各個設備的驅動程序在不同程度上要 進行改寫。為了方便各位Linux愛好者我把自己整理的這分文檔share出來。該文當列舉 了2.6內核同以前版本的絕大多數變化,可惜的是由於時間和精力有限沒有詳細列出各個 函數的用法。
  • Nvidia 390.77 Linux圖形驅動程序發布,改進了與最新內核的兼容性
    ,以增加與最新Linux內核的兼容性並修復各種錯誤。雖然Nvidia 390.77專有圖形驅動程序並不是主要的版本,但它與最新的Linux內核具有更好的兼容性。不過,Nvidia並沒有提到是否可以用即將推出的Linux 4.18內核系列或最近發布的Linux 4.17版本來編譯其專有的顯示驅動程序。
  • S3C2440驅動篇—看門狗驅動分析
    Linux-2.6.32.2內核自帶S3C2440看門狗驅動,只需要配置一下就可以使用。驅動原始碼位於drivers/watchdog/s3c2410_wdt.c,由於驅動使用了平臺設備,有關平臺設備學習參考上一篇文章。
  • 從串口驅動到Linux驅動模型,想轉Linux的必會!
    並不是linux下的串口驅動。引入此圖旨在讓讀者感性的認識到串口控制臺的功能是什麼。下面正式開始對串口打開。發送。接收函數的分析。這裡向前引用一個函數。就是linux內核中幾種2440晶片通用的串口發送函數s3c24xx_serial_start_tx。
  • SEP6200平臺上Linux內核的USB OTG驅動設計
    首先介紹了OTG標準中的對話請求協議(SRP)和主機交換協議(HNP),然後制定並設計了基於USB控制晶片USB3343的硬體模塊方案,最後根據Linux內核中已有的USB驅動架構完成了USB OTG設備驅動的設計,並最終實現了SEP6200嵌入式平臺USB Host和Device角色的自由轉換功能。