1. 程式人生 > >linux 驅動——時間、延時及延緩操作

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 : 原子執行,要快速執行完,上下文中不能睡眠, 一般可在中斷中執行。
工作佇列:非原子,長延時,可以睡眠,可在核心程序的上下文中執行。