内核通过定时器中断来跟踪事件流。时钟中断由系统定时硬件以周期性的间隔产生,间隔值由内核根据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: 是原子上下文,在处理函数中,不能睡眠
关键函数
#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 : 原子执行,要快速执行完,上下文中不能睡眠, 一般可在中断中执行。
工作队列:非原子,长延时,可以睡眠,可在内核进程的上下文中执行。