1. 程式人生 > 其它 >jiffies與HZ、定時器、原子操作

jiffies與HZ、定時器、原子操作

技術標籤:驅動入門linux嵌入式驅動程式

jiffies與HZ、定時器、原子操作


前言

核心中定時器有兩種,簡單定時器和高精度定時器,
簡單定時器:以全域性變數jiffies衡量
高精度定時器:ktime 的特殊資料型別表示 有絕對時間和相對時間概念
原子操作:在多程序(執行緒)的作業系統中不能被其它程序(執行緒)打斷的操作就叫原子操作,檔案的原子操作是指操作檔案時的不能被打斷的操作。原子操作是不可分割的,在執行過程中不會被任何其它任務或事件中斷。


一、Jiffies與HZ

1、jiffies:

是一個變數,每次當定時器中斷髮生時,核心內部通過一個64位的變數jiffies_64做加一計數。驅動程式開發者通常訪問的是jiffies變數,它是jiffies_64的低32位。
jiffies是unsigned long型的變數,該變數被宣告為volatile(直接從對應的記憶體當中提取 ),這樣可避免編譯器對訪問該變數的語句的優化(由於訪問暫存器的速度要快過RAM,所以編譯器一般都會作減少存取外部RAM的優化 ) 。

2、HZ:

HZ 即表示每秒鐘產生的定時中斷次數
HZ就代表1秒

在 Linux 2.6 中,系統時鐘每 10ms中斷一次(時鐘頻率,用 HZ 巨集表示,定義為 100,即每秒中斷 100 次,這個時間單位稱為一個 jiffie(如10ms)HZ=jiffies+100次

3、時間與jiffies轉換

將以秒為單位的時間轉化為jiffies:seconds * Hz
將jiffies轉化為以秒為單位的時間:jiffies / Hz

4、jiffies的迴繞

因為jiffies為unsigned long 的資料型別 防止因迴繞產生錯誤的方法是將它轉換為 long (有符號的補碼可以進行比較)

避免迴繞API:
在這裡插入圖片描述

二、計時、延遲與睡眠

1、計時

提供兩套時間計數的方法
①、timeval

一、使用者空間的timeval:
struct timeval {
   time_t    tv_sec;   /* 秒 */
  suseconds_t   tv_usec; /* 毫秒 */
}; 獲取當前時間: void do_gettimeofday(struct timeval *tv); jiffies值和timeval之間轉換: unsigned long timeval_to_jiffies(struct timeval *value)void jiffies_to_timeval(unsigned long jiffies,struct timeval *value)

②、timespec

二、使用者空間的timespec:
struct timespec {
   time_t     tv_sec;   /* 秒 */
   long   tv_nsec;    /* 納秒 */
};

獲取當前時間:
struct timespec current_kernel_time(void);

jiffies值和timespec之間轉換:
unsigned long timespec_to_jiffies(struct timespec *value)void jiffies_to_timespec(unsigned long jiffie, struct timespec *value)

2、延遲

延時實際上是忙等待,cpu空跑一段程式碼
①、長延時

while(time_before(jiffies,end_time)){//等待超時
 	schedule()//休眠,讓出CPU,核心排程其他程序
}

signed long schedule_timeout(signed long timeout);

典例
set_current_state(TASK_INTERRUPTIBLE)schedule_timeout(delay)

②、短延時
這三個延遲函式均是忙等待函式,因而在延遲過程中,時間片未到時,核心不會排程其他程序,即無法執行其他任務。表象上看,就是系統很卡,CPU資源被一直耗用這。所以 要特別慎用此類函式。

void ndelay(unsigned long nsecs)/*延時納秒*/
void udelay(unsigned long usecs)/*延時微妙*/
void mdelay(unsigned long msecs)/*延時毫秒*/


3、睡眠

這2個延遲函式是不使用忙等待的延遲函式,因而在延遲過程中,呼叫該函式的程序會主動呼叫schedule,即讓出CPU,核心排程其他程序執行。

void msleep(unsigned int millisecs)unsigned long msleep_interruptible(unsigned int millisecs)

三、定時器

定時器的使用很簡單。你只需要執行一些初始化工作,設定一個超時時間,指定超時發生後執行的函式,然後啟用定時器就可以了。
定時器有兩種 簡單定時器和高精度定時器
定時器並不是週期執行,它在超時後就自行銷燬

1、簡單定時器

簡單定時器結構體

定時器由結構timer_list表示,在<linux/timer.h>中:

struct timer_list{
              struct list_head entry;    /*定時器連結串列入口*/
              unsigned long expires;    /*以jiffies單位的定時值*/
              spin_lock lock;       /*保護定時器的鎖*/
              void (*func)(unsigned long); /* 定時器處理函式*/ 
              unsigned long data;        /*引數*/  
              struct tvec_t_base_s *base;   /*定時內部值,使用者不要使用*/
};

使用定時步驟:

1、定義定時器:struct timer_list my_timer;
2、初始化定時器:init_timer(&my_timer);
3、定義定時器中斷處理函式:
void my_timer_fuc(unsigned long data);
my_timer.function = my_timer_fuc;
my_timer.data = 0;
data引數使你可以利用同一個處理函式註冊多個定時器,只需要通過引數就能區別對待它們。如果不需要引數,可以簡單的傳遞0(或任何其他值)給處理函式。
----以上三個步驟可以用以下直接代替
static struct timer_list my_timer =TIMER_INITIALIZER(_function, _expires, _data);
4、啟用定時器
mod_timer(&my_tiner, jiffies+new_delay );
第2個引數表示超時時間,它是以節拍為單位的絕對計數值 ,只要節拍計數大於或等於指定的超時時,核心就開始執行定時器處理函式,然後停止計時。
5、停止定時器
①、del_timer(&my_timer);
被啟用或未被啟用的定時器都可以使用該函式。如果定時器還未被啟用,該函式返回0;否則返回1。
②、del_timer_sync(&my_timer);
同步等待可能在其他處理器上執行的定時器處理程式都退出、不能在中斷上下文中使用

2、高精度定時器

儘管簡單計時器 API 簡單有效,但它並不能提供實時應用程式所需的準確性。為此,核心引入了精確度更高的計時器。
注意:時間不是用 jiffies 表示的,而是以一種名為 ktime 的特殊資料型別表示。這種表示方法隱藏了在這個粒度上有效管理時間的一些細節。hrtimer API 正式確認(formalize)了絕對時間相對時間之間的區別,要求呼叫者指定型別。

①、初始化定時器:
void hrtimer_init( struct hrtimer *time, clockid_t clock_id, enum hrtimer_mode mode );
其中,第二個引數clock_id有兩種取值,如下:
CLOCK_REALTIME:這種型別的時鐘可以反映wall clock time,用的是絕對時間,當系統的時鐘源被改變,或者系統管理員重置了系統時間之後,這種型別的時鐘可以得到相應的調整,也就是說,系統時間影響這種型別的timer。
CLOCK_MONOTONIC:用的是相對時間,他的時間是通過jiffies值來計算的。該時鐘不受系統時鐘源的影響,只受jiffies值的影響。
建議使用:CLOCK_MONOTONIC這種時鐘更加穩定,不受系統時鐘的影響。如果想反映wall clock time,就使用CLOCK_REALTIME。
②、 啟動定時器 :
int hrtimer_start(struct hrtimer *timer, ktime_t time, const enum hrtimer_mode mode);
③、取消計時器:
int hrtimer_cancel(struct hrtimer *timer);
如果計時器已經發出,那麼它將等待回撥函式結束
int hrtimer_try_to_cancel(struct hrtimer *timer);
如果計時器已經發出,它將返回失敗

四、原子操作

所謂原子操作,就是該操作絕不會在執行完畢前被任何其他任務或事件打斷,也就說,它的最小的執行單位,不可能有比它更小的執行單位。
原子操作需要硬體的支援,因此是架構相關的,其API和原子型別的定義都定義在核心原始碼樹的include/asm/atomic.h檔案中,它們都使用匯編語言實現,因為C語言並不能實現這樣的操作。

1、atomic_t資料型別

原子操作主要用於實現資源計數,很多引用計數(refcnt)就是
通過原子操作實現的。原子型別定義如下
typedef struct 
{ 
	volatile int counter;
 } atomic_t;
volatile修飾字段告訴gcc不要對該型別的資料做優化處理,
對它的訪問都是對記憶體的訪問,而不是對暫存器的訪問。 

2、操作API

void atomic_read(atomic_t * v);  
該函式對原子型別的變數進行原子讀操作,它返回原子型別的變數v的值。 
void atomic_set(atomic_t * v, int i); 
該函式設定原子型別的變數v的值為i。 
void atomic_add(int i, atomic_t *v); 
該函式給原子型別的變數v增加值i。 
void atomic_sub(int i, atomic_t *v);
該函式從原子型別的變數v中減去i。 
int atomic_sub_and_test(int i, atomic_t *v);  
該函式從原子型別的變數v中減去i,並判斷結果是否為0,如果為0,返回真,否則返回假。 
void atomic_inc(atomic_t *v); 
該函式對原子型別變數v原子地增加1void atomic_dec(atomic_t *v); 
該函式對原子型別的變數v原子地減1int atomic_dec_and_test(atomic_t *v);  
該函式對原子型別的變數v原子地減1,並判斷結果是否為0,如果為0,返回真,否則返回假。 
int atomic_inc_and_test(atomic_t *v); 
該函式對原子型別的變數v原子地增加1,並判斷結果是否為0,如果為0,返回真,否則返回假。 
int atomic_add_negative(int i, atomic_t *v); 
該函式對原子型別的變數v原子地增加I,並判斷結果是否為負數,如果是,返回真,否則返回假。 
int atomic_add_return(int i, atomic_t *v);  
該函式對原子型別的變數v原子地增加i,並且返回指向v的指標。 
int atomic_sub_return(int i, atomic_t *v);    
該函式從原子型別的變數v中減去i,並且返回指向v的指標。 
int atomic_inc_return(atomic_t * v);   
該函式對原子型別的變數v原子地增加1並且返回指向v的指標。 
int atomic_dec_return(atomic_t * v);     
該函式對原子型別的變數v原子地減1並且返回指向v的指標。