1. 程式人生 > 實用技巧 >Linux 驅動框架---驅動中的時間相關

Linux 驅動框架---驅動中的時間相關

核心中的時間

Linux 系統核心對於時間的管理依賴於硬體,硬體按一定的週期產生中斷這個理的週期由核心的一個配置值HZ決定,而在系統啟動時會將定時器配置為HZ值指定的頻率產生中斷;同時核心和維護一個64位(X86和X64都是64位)的計數器變數jiffies(jiffies_64)。在系統啟動時這個值為0之後每次發生一次定時器中斷這個值就會加1 ,所以他代表系統執行的嘀嗒次數。在核心中使用這個變數只需要引用標頭檔案include<linux/jiffies.h>這個標頭檔案又經常被linux/sched.h包含。

常用的比較兩個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) #define time_after_eq(a,b) \ (typecheck(unsigned long, a) && \ typecheck(unsigned long, b) && \ ((
long)(a) - (long)(b) >= 0)) #define time_before_eq(a,b) time_after_eq(b,a)

在使用者控制元件表示時間常常用兩個結構體來表示但是精度不同,標頭檔案為#include <linux/time.h>。

表示秒和納秒的

struct timespec {
time_t    tv_sec; /* seconds */
long    tv_nsec; /* nanoseconds */
};

表示秒和微妙

struct timeval {
time_t        tv_sec; /* seconds */
    suseconds_t    tv_usec; 
/* microseconds */ };

同時還提供了使用者控制元件的時間和jiffies的相互轉化的工具函式

unsigned long timespec_to_jiffies(struct timespec *value);
void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);
unsigned long timeval_to_jiffies(struct timeval *value);
void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);

這個介面也可以說明在使用者空間struct timespec 和struct timeval 也是常常被用來表示相對時間而不是真實時間(壁鐘時間)的。在32作業系統上直接訪問jiffies_64 是無法保證原子性的,所以核心需要提供介面

#include <linux/jiffies.h>
u64 get_jiffies_64(void);

當需要測量的時間非常精確時只有硬體提供特殊的機制時才能實現否則,時間的精度會有上限。像X86的平臺就提供了一個64為的暫存器用來記錄CPU的時鐘級精度的時間刻度。除此之外核心內部還有一個全域性的時間計數器xtime它是struct timeval 型別的變數,記錄從標準時間基準1970-01-01 00:00:00到當前的相對秒數值。它有中斷地板部分來維護,以為內底半部執行時間具有不確定性所以同時核心還維護了一個xtim的更新時刻值他是和jiffies同類型的變數,每次跟新xtime時都把當前的jiffies賦值給他。

核心中的延時

短延時

#include <linux/delay.h>
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);

這三個延遲函式均是忙等待函式,在延時過程中無法執行其他任務。毫秒級(或者更長)延遲這種方法不涉及忙等待,通常如果我們能夠容忍比所請求更長的延遲,則應當使用schedule_timeout、sleep或者ssleep;

void msleep(unsigned int msecs)
unsigned long msleep_interruptible(unsigned int msecs)
void ssleep(unsigned int seconds)

長延時

第一種實現方式

#define cpu_relax()            barrier()
while (time_before(jiffies, j1))
    cpu_relax();

這個方式是忙等待的cpu_relax通常是什麼都不做,如果在這之前禁用了搶佔並關閉中斷則系統核心就停止運行了,除了重啟別無他法。這樣的延時對系統性的負面影響非常大所以有了下面的版本。這時候系統執行其他處理或執行程序0進入低能耗。

while (time_before(jiffies, j1)) {
    schedule();

核心等待

#include <linux/wait.h>
//在一個佇列上等待某些事件直到超時或事件發生
long wait_event_timeout(wait_queue_head_t q, condition, long timeout);
//在一個佇列上等待某些事件直到超時或事件發生或者中斷髮生。
long wait_event_interruptible_timeout(wait_queue_head_t q, condition, long timeout);
/*這些函式在給定佇列上睡眠, 但是它們在超時(以 jiffies 表示)到後返回。如果超時,函式返回 0;
所以我盟可以定義一個等待佇列頭,沒有人往這個頭上新增事件,則這個上面的函式就程式設計對應的單純延時的函式。同時核心也提供了這種需
求的介面,以實現程序在超時到期時被喚醒而又不等待特定事件避免多餘宣告一個等待佇列頭。
#include <linux/sched.h>
signed long schedule_timeout(signed long timeout);
/*timeout 是要延時的 jiffies 數。除非這個函式在給定的 timeout 流失前返回,否則返回值是 0 。
schedule_timeout 要求呼叫者首先設定當前的程序狀態。為獲得一個不可中斷的延遲, 可使用 TASK_UNINTERRUPTIBLE 代替。
如果你忘記改變當前程序的狀態, 呼叫 schedule_time 如同呼叫 shcedule,建立一個不用的定時器。一個典型呼叫如下:*/
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout (delay);

核心中的定時器

核心中用來描述一個定時器例項物件的資料結構定義如下,其中expires為到期時間,data為定時器處理介面傳入的引數,function就是定時器到期時執行的處理介面,其餘的介面都是核心管理定時器需要的輔助成員。

struct timer_list {
    /*
     * All fields that change during normal runtime grouped to the
     * same cacheline
     */
    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
};

常見的使用方法時定義或動態申請一個定時資料結構,然後初始化定時器,之後就是將定時器新增到核心定時器管理的連結串列中,定時器就會在到期時間執行指定的處理介面。

初始化定時器

//初始化定時器 帶標誌設定
#define __TIMER_INITIALIZER(_function, _expires, _data, _flags) 
{ \
        .entry = { .prev = TIMER_ENTRY_STATIC },    \
        .function = (_function),            \
        .expires = (_expires),                \
        .data = (_data),                \
        .base = (void *)((unsigned long)&boot_tvec_bases + (_flags)), \
        .slack = -1,                    \
        __TIMER_LOCKDEP_MAP_INITIALIZER(        \
            __FILE__ ":" __stringify(__LINE__))    \
    }
//初始化定時器 不帶flags設定
#define TIMER_INITIALIZER(_function, _expires, _data)        \
    __TIMER_INITIALIZER((_function), (_expires), (_data), 0)
//定義並初始化 帶 TIMER_DEFERRABLE標識
#define TIMER_DEFERRED_INITIALIZER(_function, _expires, _data)    \
    __TIMER_INITIALIZER((_function), (_expires), (_data), TIMER_DEFERRABLE)
//定義並初始化 標誌位全0
#define DEFINE_TIMER(_name, _function, _expires, _data)        \
    struct timer_list _name =                \
        TIMER_INITIALIZER(_function, _expires, _data)
#define init_timer(timer)                        \
    __init_timer((timer), 0)

#define __init_timer(_timer, _flags)                    \
    init_timer_key((_timer), (_flags), NULL, NULL)

void init_timer_key(struct timer_list *timer, unsigned int flags,
            const char *name, struct lock_class_key *key)
{
    debug_init(timer);
    do_init_timer(timer, flags, name, key);
}
#definesetup_timer(timer,fn,data)\ __setup_timer((timer),(fn),(data),0)

新增定時器到核心

void add_timer(struct timer_list 8 timer);

定義好定時器只有加入到核心定時器管理連結串列上才能被核心定時器執行緒管理,從而在超時後執行。也有需求需要在定時器新增後刪除定時器則只需要呼叫del_timer()介面即可

int del_timer(struct timer_list * timer)

新增好之後又想修改超時時間可以嗎,當然是可以的,如下

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

這就是Linux核心定時器的簡單使用記錄 部分,後面瞭解一下定時器的標識flags引數的具體可取值和特性。

//從核心的處理過程來看這個標誌在處理時沒有進行任何特殊處理
#define
TIMER_DEFERRABLE 0x1LU // 中斷安全,意味著執行這個定時器處理介面要關閉中斷
#define TIMER_IRQSAFE 0x2LU //掩碼 #define TIMER_FLAG_MASK 0x3LU

核心處理timer過程:

static inline void __run_timers(struct tvec_base *base)
{
    struct timer_list *timer;

    spin_lock_irq(&base->lock);
    if (catchup_timer_jiffies(base)) {
        spin_unlock_irq(&base->lock);
        return;
    }
    while (time_after_eq(jiffies, base->timer_jiffies)) {
        struct list_head work_list;
        struct list_head *head = &work_list;
        int index = base->timer_jiffies & TVR_MASK;

        /*
         * Cascade timers:
         */
        if (!index &&
            (!cascade(base, &base->tv2, INDEX(0))) &&
                (!cascade(base, &base->tv3, INDEX(1))) &&
                    !cascade(base, &base->tv4, INDEX(2)))
            cascade(base, &base->tv5, INDEX(3));
        ++base->timer_jiffies;
        list_replace_init(base->tv1.vec + index, head);
        while (!list_empty(head)) {
            void (*fn)(unsigned long);
            unsigned long data;
            bool irqsafe;

            timer = list_first_entry(head, struct timer_list,entry);
            fn = timer->function;
            data = timer->data;
            irqsafe = tbase_get_irqsafe(timer->base);

            timer_stats_account_timer(timer);

            base->running_timer = timer;
            detach_expired_timer(timer, base);
            //處理TIMER_IRQSAFE
            if (irqsafe) {
                spin_unlock(&base->lock);
                call_timer_fn(timer, fn, data);
                spin_lock(&base->lock);
            } else {
                spin_unlock_irq(&base->lock);
                call_timer_fn(timer, fn, data);
                spin_lock_irq(&base->lock);
            }
        }
    }
    base->running_timer = NULL;
    spin_unlock_irq(&base->lock);
}

以上就是核心定時器的使用簡單瞭解內容,具體的實現有時間了放在核心透視部分深入看看。

//處理TIMER_IRQSAFE