linux 驅動——時間、延時及延緩操作
一、jiffies
核心通過定時器中斷來跟蹤事件流。時鐘中斷由系統定時硬體以週期性的間隔產生,間隔值由核心根據HZ設定。
一般為HZ的範圍為50~1200。
jiffies_64為64位變數,在時鐘中斷沒發生一次時,值增加一,用來計數從系統引導到當前時刻的時間節拍。jiffies 是unsigned long 型,32位系統為jiffies_64的低32位,64位系統是與jiffies_64相同。
由此,jiffies 的時間解析度較低,為ms 級數,關於 jiffies的操作時間間隔不能低。
jiffies 操作示例
j = jiffies; /* read the current value */
stamp_1 = j + HZ; /* 1 second in the future */
stamp_half = j + HZ/2; /* half a second */
stamp_n = j + n * HZ / 1000; /* n milliseconds */
#include <linux/jiffies.h>
int time_after(unsigned long a, unsigned long b); // a 比 b 時間靠後,返回真
int time_before(unsigned long a, unsigned long b); // a 比 b 時間靠前,返回真
int time_after_eq(unsigned long a, unsigned long b);// a 比 b 時間靠後或相等,返回真
int time_before_eq(unsigned long a, unsigned long b);// a 比 b 時間靠前或相等,返回真
使用者時間與核心時間的轉換
#include <linux/time.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);
二、獲取當前時間
核心中的獲取當前時間的函式
//將牆上時間轉化為jiffies
#include <linux/time.h>
unsigned long mktime (unsigned int year, unsigned int mon,
unsigned int day, unsigned int hour,
unsigned int min, unsigned int sec);
//獲取秒或者微妙級的時間
#include <linux/time.h>
void do_gettimeofday(struct timeval *tv);
//timespec 返回timespec 結構的時間
#include <linux/time.h>
struct timespec current_kernel_time(void);
三、延時執行
1、長延時
長延時時:忙等待不推薦、消耗CPU資源
讓出處理器,下面的操作方法對驅動程式來說不安全。
while (time_before(jiffies, j1)) {
schedule();
}
#include <linux/wait.h>
wait_queue_head_t wait;
init_waitqueue_head (&wait);
wait_event_interruptible_timeout(wait, 0, delay);
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);
wait_event_timeout, wait_event_interruptible_timeout 由兩種方式使程序重新執行:1 是使用wake_up 類似的函式,2、是時間到期。在延時時,第一種是不期望的情況。 所以使用下列方式
長延時可採用的方案
#include <linux/sched.h>
set_current_state(TASK_INTERRUPTIBLE); // 設定程序狀態
schedule_timeout (delay); // 延時delay 後,程序切換回當前程序
2、短延時
驅動程度處理呀硬體延時時,最多涉及幾十個毫秒的等待。
軟中斷、忙等待,比傳入的值微微延長
#include <linux/delay.h>
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs); // 微妙級採用的函式
void mdelay(unsigned long msecs); // 毫秒級採用的函式
非忙等待,毫秒級
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds)
四、核心定時
核心定時器: 在某個時間點,排程執行某個任務,到達之前不會阻塞當前程序時,使用。
注意:
1、定時器執行的任務中,可以將此定時器註冊,以在稍後的時間重新執行,因為每個timer_list 結構都會在執行之前從活動定時器連結串列中移走,這樣就可以立即鏈入其他連結串列。在輪詢時使用。
2、 通過定時器訪問的資料結構應該針對併發訪問進行保護。
3、一個自己註冊的定時器,始終會在同一CPU上執行。
定時API
#include <linux/timer.h>
struct timer_list {
/* ... */
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
};
void init_timer(struct timer_list *timer);
struct timer_list TIMER_INITIALIZER(_function, _expires, _data);
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
int mod_timer(struct timer_list *timer, unsigned long expires);
//修改節拍數,等同於, del_timer(timer), timer->expires = expires, add_timer 三個操作
_function 為定時器呼叫的函式,_expires 為定時器延時的節拍數, _data為引數傳遞的引數(unsigned long 型別)
如前面提到的每次呼叫_function 時, timer_list 結構都會在執行之前從活動定時器連結串列中移走。如要在_function 重新應用 定時器則需要
timer->expires = jiffies+ fn(HZ);
add_timer(struct timer_list * timer);
或者
mod_timer(timer, expires);
定時器實現的規則
1、輕量級
2、大量增加時,具有很好的伸縮性
3、長延時少
4、應該註冊在同一CPU上執行
五、tasklet
tasklet 是核心中斷管理中常用的機制,它的執行上下文是軟中斷,執行機制通常是頂部返回的時候。
tasklet: 是原子上下文,在處理函式中,不能睡眠
關鍵函式
#include <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); //定義名為name,的tasklet, 並繫結函式,傳遞引數 data
DECLARE_TASKLET_DISABLED(name, func, data);
void tasklet_schedule(struct tasklet_struct *t); // 排程tasklet
驅動模板
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);
void xxx_do_tasklet(unsigned long)
{
……
}
irqreturn_t xxx_interrupt(int irq,void *dev_id,struct pt_regs *regs)
{
……
tasklet_schedule(&xxx_tasklet);
……
}
int _init xxx_init(void)
{
……
result=request_irq(xxx_irq,xxx_interrupt,SA_INTERRUPT,”xxx”,NULL)
……
}
void _exit xxx_exit(void)
{
……
free_irq(xxx_irq,xxx_irq_interrupt);
……
}
六、工作佇列
工作佇列執行上下文是核心執行緒,因此可以排程和睡眠。(長時間,非原子)
自定義一個工作佇列
struct workqueue_struct *create_workqueue(const char *name); //在每個CPU上建立工作執行緒
struct workqueue_struct *create_singlethread_workqueue(const char *name); //建立單個執行緒
建立工作任務
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); //為延時delay 節拍數後執行,
取消工作佇列的入口,或者取消正在執行的工作佇列,一般一起使用
int cancel_delayed_work(struct work_struct *work);
void flush_workqueue(struct workqueue_struct *queue);
釋放工作佇列資源
void destroy_workqueue(struct workqueue_struct *queue);
共享佇列
一般情況下驅動不需要自己定義一個工作佇列,這時可以使用工作佇列的
此時呼叫下列函式
schedule_work(&jiq_work);
int schedule_delayed_work(struct work_struct *work, unsigned long delay);
INIT_DELAYED_WORK 是在INIT_WORK的基礎上定義一個定時器,
INIT_WORK(struct work_struct *work, void (*function)(void *), void *data);
INIT_DELAYED_WORK(struct work_struct *work, void (*function)(void *), void *data);
#define INIT_WORK(_work, _func) /
do { /
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); /
INIT_LIST_HEAD(&(_work)->entry); /
PREPARE_WORK((_work), (_func)); /
} while (0)
#define INIT_DELAYED_WORK(_work, _func) /
do { /
INIT_WORK(&(_work)->work, (_func)); /
init_timer(&(_work)->timer); /
} while (0)
INIT_WORK(),schedule_work() // 一起使用
INIT_DELAY_WORK(), schedule_delayed_work() //一起使用
當然在關聯函式中,也可以呼叫schedule_delayed_work()從而實現定時輪詢
tasklet 與 工作佇列的區別:
tasklet : 原子執行,要快速執行完,上下文中不能睡眠, 一般可在中斷中執行。
工作佇列:非原子,長延時,可以睡眠,可在核心程序的上下文中執行。