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 
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 
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 
unsigned long mktime (unsigned int year, unsigned int mon,
unsigned int day, unsigned int hour,
unsigned int min, unsigned int sec);

//获取秒或者微妙级的时间
#include 
void do_gettimeofday(struct timeval *tv);

//timespec  返回timespec 结构的时间
#include 
struct timespec current_kernel_time(void);

三、延时执行

1、长延时
长延时时:忙等待不推荐、消耗CPU资源

让出处理器,下面的操作方法对驱动程序来说不安全。

while (time_before(jiffies, j1)) {
    schedule();
}  
#include 
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 
set_current_state(TASK_INTERRUPTIBLE); // 设置进程状态
schedule_timeout (delay);              // 延时delay 后,进程切换回当前进程

2、短延时
驱动程度处理呀硬件延时时,最多涉及几十个毫秒的等待。

软中断、忙等待,比传入的值微微延长

#include 
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 
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 
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);  
      ……  
}  

tasklet 可参考的文章:http://blog.csdn.net/ce123_zhouwei/article/details/7768941

六、工作队列

工作队列执行上下文是内核线程,因此可以调度和睡眠。(长时间,非原子)

自定义一个工作队列

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 : 原子执行,要快速执行完,上下文中不能睡眠, 一般可在中断中执行。
工作队列:非原子,长延时,可以睡眠,可在内核进程的上下文中执行。

你可能感兴趣的:(linux-driver)