Linux下的延時/定時機制


2020-05-18

關鍵字:timer_list定時器、jiffies機制


 

1、jiffies

 

Linux內核一般都通過 jiffies 來獲取系統的當前時間。

 

jiffies 是一個被定義在 <linux/jiffies.h> 中的 unsigned long 型的變量。這個變量的值由內核自動設定,它表示的是自最近一次系統啟動到當前的時間間隔。

 

jiffies 機制是一個完全獨立且兼容性極好的計時系統。任何Linux系統都有 jiffies 。因此,在大多數情況下它可以被設備驅動等程序作為時間參考工具,即使它的精度並沒有那么高。jiffies 的精度只能到達 ms 級,如果您的設備驅動需要更高精度的計時,那可能得另尋它法了,通常是直接讀取相應寄存器來實現。不過這樣一來,代碼的兼容性就會差很多了。但在絕大多數情況下,jiffies 的精度都足夠了。

 

在Linux內核中,最簡單粗暴的延時方式就是“忙等待”。

 

它說白了就是設定一個終止時間點,然后一直去檢測系統時鍾當前又沒有到達這個終止時間點來判定是否執行下一步操作。這種方式雖然會浪費掉很多CPU的運算力,但不可否認的是它的編寫也相當簡單。一種比較常見的方式就是檢測jiffies來延時。如下代碼所示:

unsigned long end_time = jiffies + HZ * 5; //表示在當前時間點5秒后為終止時間點。
while(time_before(jiffies, end_time)); //jiffies未到end_time的話會一直在這個循環中打轉。time_before()函數位於jiffies.h中。

do_anything_you_wanna_do(); //時間到!

對於以上代碼需要解釋的下的就是第一行的那個 HZ 。HZ是一個被定義在 <asm/param.h> 中的宏。它的值會根據相應平台的不同而不同。總之這個值就表示當前系統在1秒鍾時間里可以跳躍的jiffies數量。 總之記住 jiffies + HZ 表示1秒鍾以后的 jiffes 變量的值就行了。

 

不過總得來說,上面這種寫法是一種相當糟糕的實現方式。嚴重的情況下它會導致程序出現“假死”的現象。因此,沒有特別的需求,不要使用這種方式來延時。

 

2、短延時

 

在內核程序開發中。短期延時通常直接使用內核提供的幾個延時函數即可。如:

#include <linux/delay.h>
void ndelay(unsigned long nsecs); //納秒延時 void udelay(unsgined long usecs); //微秒延時 void mdelay(unsigned long msecs); //毫秒延時

這里得注意一下,一般“納秒延時”是做不到的。只能說是起個參考作用罷了。其實嚴格來講,這三個延時都只能起到個參考作用而已。並且還有個很重要的:這三個延時函數是“忙等待型”。即在等待延時到期的過程中,CPU會在那死等而不會讓出調度權。

 

與之相對應的還有幾個延時時間更長的,且不屬於“忙等待型”的延時函數:

#include <linux/delay.h>
void msleep(unsigned int ms);
void ssleep(unsigned int seconds);
unsigned long msleep_interruptible(unsigned int ms);

前面兩個函數的延時是不可中斷的。如果希望在延時等待過程中進程能被相應中斷喚醒,則可以使用第三個函數。更具體的用法,還得各位同學自行實踐掌握了。

 

 

3、定時器

 

老實說,Linux內核里的定時器還真不少。

 

定時器的優點就不用多說了,既准確又可以不阻塞當前進程。這里要講的定時器是一個基於 jiffies 時間基准的定時器。

 

這個定時器位於 <linux/timer.h> 中,它的原型定義如下所示:

struct timer_list {
    struct list_head entry;
    unsigned long expires;
    struct tvec_base *base;
    void (*function)(unsigned long);
    unsigned long data;

    int slack;
#ifdef CONFIG_TIMER_STATS
    int start_pid;
    void *start_site;
    char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};

當然,需要我們關心的成員其實就三個:

1、expires

一個 jiffies 值。表示本次定時將於指定的 jiffies 值時到時。

2、data

一個 unsigned long 型的變量。當定時到時時這個變量將會作為參數傳遞到回調函數中去。unsigned long 和指針的長度是一致的,由此你肯定能知道這個參數的作用有多大了。

3、function指針函數

定時器到時回調函數。

 

與大多數結構體類型的使用類似,timer_list 定時器的使用無非就如下三個步驟:

1、申請內存;

kmalloc()等可以在內核中分配內存的函數均可搞定。

2、初始化;

<linux/timer.h> 中有個宏定義 "#define init_timer(timer)",將上一步申請到的內存的地址傳遞進去即可。這個操作相當於 memset() / bzero()。

其次就是設置 expires , data , function 了。

3、啟動定時;

<linux/timer.h>  中的函數 add_timer() 即可搞定。

4、處理定時中斷;

在第 2 步中注冊的回調函數中執行你的邏輯代碼。

5、銷毀。

del_timer() 函數。

 

話不多說,直接上一個實例感受一下,以下代碼的功能是加載了驅動程序后即啟動一個定時器,並在定時器到時以后自動重新啟動以實現循環定時計數的功能:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/timer.h>
#include <linux/slab.h>

static struct timer_list *timer;

void timer_response(unsigned long data)
{
    printk("%s()\n", __FUNCTION__);
    printk("data:%ld\n", data);

    if(timer != NULL)
    {
        /*
            重啟方式一
        */
//        timer->data = jiffies;
//        mod_timer(timer, timer->data + HZ * 3); //直接調用這個函數修改掉 expires 值就會自動觸發下一輪定時的。

        /*
            重啟方式二
        */
        timer->data = jiffies;
        timer->expires = timer->data + HZ * 3; 
        add_timer(timer);
    }
}

static int __init mymd_init()
{
    printk("%s()\n", __FUNCTION__);

    printk("-------- timer_list demo running --------\n");
    timer = (struct timer_list *)kmalloc(sizeof(struct timer_list), GFP_KERNEL); //Must malloc first.
    init_timer(timer);
    printk("timer_list address:0x%p\n", timer);
    timer->expires = jiffy + HZ * 5;
    timer->data = jiffy;
    timer->function = timer_response;

    add_timer(timer);

    
    return 0;
}

static void __exit mymd_exit()
{
   printk("%s()\n", __FUNCTION__);

   if(timer != NULL){
       printk("freeing timer_list object!\n");
       int fret = del_timer(timer);
       printk("timer_list free ret:%d\n", fret);
   }
}

module_init(mymd_init);
module_exit(mymd_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("chorm");

 

關於定時到時響應函數這里提一點額外的知識:定時到時響應函數嚴格來講是一種“軟中斷”。在軟中斷中編程需要注意有以下幾點規則不可觸犯,否則會讓你的程序在運行時產生意料之外的結果:

1、不允許訪問用戶空間的內存。

2、current指針是無效的。

3、不能執行休眠或調度操作。包括但不限於:udelay() , mdelay() , msleep() , GFP_KERNEL式的kmalloc() , 信號量等。

 

另外,在 <linux/hardirq.h> 中定義了一個宏定義可以查詢當前是屬於“進程上下文”還是“中斷上下文”。中斷上下文即指上面提到的“軟中斷”環境,進程上下文則是普通環境。這個查詢宏定義的原型如下:

#define in_interrupt()        (irq_count())

這個宏定義會返回一個 int 型值。返回值 0 表示當前在“進程上下文”中,非 0 表示處於“中斷上下文”中。

 

另外,關於定時中斷類似的實現方式還有 tasklet 與 workqueue 兩種。這兩種機制的相關知識可以在筆者的另一篇博文中找到,這里就不再贅述了。

https://www.cnblogs.com/chorm590/p/12294386.html

 


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM