您當前位置:
首頁 >
互聯網 > 《Linux Device Drivers》 第七章 時間、延時及延緩操作――note
《Linux Device Drivers》 第七章 時間、延時及延緩操作――note
來源:程序員人生 發布時間:2014-10-13 02:03:27 閱讀次數:3095次
- 度量時間差
- 內核通過定時器中斷來跟蹤時間流
- 時鐘中斷由系統定時硬件以周期性的間隔產生,這個間隔由內核根據HZ的值設定,在常見的x86 PC平臺上,默認定義為1000
- <linux/param.h>
- <linux/timex.h>
- jiffies_64
- unsigned long jiffies
- 使用jiffies計數器
- <linux/jiffies.h>
- int time_after(unsigned long a, unsigned long b);
- int time_before(unsigned long a, unsigned long b);
- int time_after_eq(unsigned long a, unsigned long b);
- int time_before_eq(unsigned long a, unsigned long b);
- 通常只需要包含<linux/sched.h>
- diff = (long)t2 -(long)t1
- msec = diff * 1000 / HZ
- <linux/times.h
- 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);
- u64 get_jiffies_64(void);
- <linux/types.h>
- proc/interrupts
- 處理器特定的寄存器
- 最有名的計數器寄存器就是TSC
- <asm/msr.h>
- rdtsc(low32, high32);
- rdtscl(low32);
- rdtscl1(var64);
- <linux/timex.h>
- cycles_t get_cycles(void);
- define rdtscl(dest) __asm__ __volatile__(“mfs0 %0,$9; nop” : “=r” (dest))
- 獲取當前時間
- 內核提供將墻鐘時間轉換為jiffies值的函數
- <linux/time.h>
- unsigned long mktime(unsigned int year, unsigned int mon, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec);
- void do_gettimeofday(struct timeval *tv);
- struct timespec current_kernel_time(void);
- 延遲執行
- 長延遲
- 忙等待
- while (time_before(jiffies, j1)) cpu_relax();
- 讓出處理器
- 在不需要CPU時主動釋放CPU
- <linux/sched.h>
- while (time_before(jiffies, j1)) schedule();
- 超時
- <linux/wait.h>
- long wait_event_timeout(wait_queue_head_t q, condition c, long timeout);
- long wait_event_interruptible_timeout(wait_queue_head_t q, condition c, long timeout);
- timeout值表示的是要等的jiffies值,而不是絕對時間值
- 如果超時到期,兩個函數返回0;如果進程由其他事件喚醒,則返回剩余的延遲實現
- <linux/sched.h>
- signed long schedule_timeout(signed long timeout);
- set_current_state(TASK_INTERRUPTIBLE);
- schedule_timeout(delay);
- 短延遲
- <linux/delay.h>
- void ndelay(unsigned long nsecs);
- void udelay(unsigned long usecs);
- void mdelay(unsigned long msecs);
- 這三個延遲函數均是忙等待函數
- unsigned long msleep_interruptible(unsigned int millisecs);
- void ssleep(unsigned int seconds);
- 內核定時器
- 一個內核定時器是一個數據結構,它告訴內核在用戶定義的時間點使用用戶定義的參數來執行一個用戶定義的函數
- 內核定時器常常是作為“軟件中斷”的結果而運行的
- 如果處于進程上下文之外,則必須遵守如下規則
- 不允許訪問用戶空間
- current指針在原子模式下是沒有任何意義的,也是不可用的
- 不能執行休眠或調度
- <asm/hardirq.h>
- in_interrupt()
- in_atomic()
- 任務可以將自己注冊以在稍后的時間重新運行
- 即使在單處理器系統上,定時器也會是競態的潛在來源
- 定時器API
- <linux/timer.h>
- struct timer_list
- unsigned long expires;
- void (*function)(unsigned long);
- unsigned long data;
- void init_timer(struct timer_list *time);
- struct timer_list TIMER_INITIALIZER(_function, _expires, _data);
- void add_timer(struct timer_list *timer);
- int del_timer(struct timer_list *timer);
- expires字段表示期望定時器執行的jiffies值
- int mod_timer(struct timer_list *timer, unsigned long expires);
- int del_timer_sync(struct timer_list *timer);
- int timer_pending(const struct timer_list *timer);
- 內核定時器的實現
- 內核定時器的實現要滿足如下需求及假定
- 定時器的管理必須盡可能做到輕量級
- 其設計必須在活動定時器大量增加時具有很好的伸縮性
- 大部分定時器會在最多幾秒或者幾分鐘內到期,而很少存在長期延遲的定時器
- 定時器應該在注冊它的同一CPU上運行
- 不管何時內核代碼注冊了一個定時器,其操作最終會由internal_add_timer(定義在kernel/timer.c)執行
- 級聯表的工作方式如下
- 如果定時器在接下來的0~255的jiffiew中到期,由該定時器就會被添加到256個鏈表中的一個(取決于expires字段的低8位值)
- 如果定時器在較遠的未來到期(但在16384個jiffies之前),則該定時器會被添加到64個鏈表之一(取決于expires字段的9~14位)
- 對更遠將來的定時器,相同的技巧用于15~20位、21~26位以及27~31位
- 如果定時器的expires字段代表了更遠的未來,則利用延遲0xfffffff做散列運算,而在過去時間內到期的定時器會在下一個定時器滴答時被調度
- 當__run_times被激發時,它會執行當前定時器滴答上的所有掛起的定時器
- tasklet
- 中斷管理中大量使用了這種機制
- 始終在中斷期間運行,始終會在調度它的同一CPU運行,接收一個unsigned long參數
- 不能要求tasklet在某個給定時間執行
- <linux/interrupt.h>
- struct tasklet_struct
- void (*func)(unsigned long);
- unsigned long data;
- void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);
- DECLARE_TASKLET(name, func, data);
- DECLARE_TASKLET_DISABLED(name, func, data);
- 有意思的特性
- 一個tasklet可以稍后被禁止或者重新啟用;只有啟用的次數和禁止的次數相同時,tasklet才會被執行
- 和定時器類似,tasklet可以注冊自己本身
- tasklet可被調度以在通常的優先級或者高優先級執行
- 如果系統負荷不重,則tasklet會立即得到執行,但始終不會晚于下一個定時器滴答
- 一個tasklet可以和其他tasklet并發,但對自身來講是嚴格串行處理的
- void tasklet_disable(struct tasklet_struct *t);
- void tasklet_disable_nosync(struct tasklet_struct *t);
- void tasklet_enable(struct tasklet_struct *t);
- void tasklet_schedule(struct tasklet_struct *t);
- void tasklet_hi_schedule(struct tasklet_struct *t);
- void tasklet_kill(struct tasklet_struct *t);
- tasklet的實現在kernel/softirq.c中
- 工作隊列
- 與tasklet區別
- tasklet在軟件中斷上下文中運行,因此,所有的tasklet代碼都必須是原子的。相反,工作隊列函數在一個特殊內核進程的上下文中運行,因此它們具有更好的靈活性。尤其是,工作隊列函數可以休眠
- tasklet始終運行在被初始提交的同一處理器上,但這只是工作隊列的默認方式
- 內核代碼可以請求工作隊列函數的執行延遲給定的時間間隔
- 工作隊列函數可具有更長的延遲并且不必原子化
- <linux/workqueue.h>
- struct workqueue_struct
- struct workqueue_struct *create_workqueue(const char *name);
- struct workqueue_struct *create_singlethread_workqueue(const char *name);
- struct work_struct
- DECLARE_WORK(name, void (*function)(void*), void *data);
- INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
- PREPARE_WORK(struct work_struct *work, void (*function)(void *), void *data);
- int queue_work(struct workqueue_struct *queue, struct work_struct *work);
- int queue_delayed_work(struct workqueue_struct *queue, struct work_struct *work, unsigned long delay);
- 以上兩個函數返回值為非零時意味著給定的work_struct結構已經等待在該隊列中
- int cancel_delayed_work(struct work_struct *work);
- void flush_workqueue(struct workqueue_struct *queue);
- void destroy_workqueue(struct workqueue_struct *queue);
- 共享隊列
- int schedule_work(struct work_struct *work);
- void flush_scheduled_work(void)
小結:1. Linux基于時鐘中斷跟蹤,系統時間流。
2.定時器、任務必須遵循在原子上下文,定時器可以指定將來的調度時間,任務無法指定執行的時間。
3.工作隊列,調度的函數可以休眠。
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
------分隔線----------------------------
------分隔線----------------------------