1.概览
推迟调用和延时的执行模型还是有些相似的,他们都是推迟某一些任务的执行,例如使用msleep时,其后面的代码也会在其返回后才能进行。但在我看来,他们的本质区别在于,推迟的这部分任务是交给第三方做还是自己亲力亲为的完成,因为使用延时方法推迟任务时,当前调用进程也就是什么也不做了,但对于推迟调用的方式是将任务抛出来,自己该干嘛还是干嘛。tasklets对于设备驱动开发者很少使用,故略过。
2. Timer–定时器
定时器的概念也很好理解,为任务设置一个具体时间点让其被执行。在内核中也同样,只是这个时间更加偏爱使用jiffies。
2.1 timer_list
结构体timer_list在kernel中则代表一个定时器,下面是其定义
//include\linux\timer.h
struct timer_list {
struct hlist_node entry;
unsigned long expires;
void (*function)(struct timer_list *);
...
};
参数说明如下
entry:
内核使用全局双向链表来管理timer。
expires:
超期时间,相对于设置时的jiffies而言,例如项延迟一秒的伪代码如下
timer_list->expires = jiffies + HZ
function:
对应超时所需要处理的任务,注意在回调这个任务的上下文为原子上下文,这意味着里面不能使用任何会造成休眠相关的代码。其中入参就是本timer_list。可以配合container_of来获取timer所在数据结构的数据指针。
2.2 add_timer
该函数用于将初始化完成的timer_list挂到全局定时器中去,只有在此调用过后该timer才会被处理。其定义如下
/**
* add_timer - start a timer
* @timer: the timer to be added
*
* The kernel will do a ->function(@timer) callback from the
* timer interrupt at the ->expires point in the future. The
* current time is 'jiffies'.
*
* The timer's ->expires, ->function fields must be set prior calling this
* function.
*
* Timers with an ->expires field in the past will be executed in the next
* timer tick.
*/
//kernel\time\timer.c
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->expires);
}
入参也就是上面初始化后的timer_list的指针,没什么好说的。
2.3 mod_timer
此函数用于修改已存在的timer_list的超期时间。定义如下
/**
* mod_timer - modify a timer's timeout
* @timer: the timer to be modified
* @expires: new timeout in jiffies
*
* mod_timer() is a more efficient way to update the expire field of an
* active timer (if the timer is inactive it will be activated)
*
* mod_timer(timer, expires) is equivalent to:
*
* del_timer(timer); timer->expires = expires; add_timer(timer);
*
* Note that if there are multiple unserialized concurrent users of the
* same timer, then mod_timer() is the only safe way to modify the timeout,
* since add_timer() cannot modify an already running timer.
*
* The function returns whether it has modified a pending timer or not.
* (ie. mod_timer() of an inactive timer returns 0, mod_timer() of an
* active timer returns 1.)
*/
//kernel\time\timer.c
int mod_timer(struct timer_list *timer, unsigned long expires)
{
return __mod_timer(timer, expires, 0);
}
如果被修改的timer_list还没有超期,那么就相当于修改了timer_list->expiresd的值。但如果timer_list已经被处理过,那么其作用流程相当于
a.del_timer(timer);
b.timer->expires = expires;
c.add_timer(timer);
可见被处理过的timer_list会被在此放到带处理队列以期下次执行。
2.4 del_timer
当设置了一个timer_list并已经调用add_timer注册后,此时又想删除时就调用该接口。接口定义如下
/**
* del_timer - deactivate a timer.
* @timer: the timer to be deactivated
*
* del_timer() deactivates a timer - this works on both active and inactive
* timers.
*
* The function returns whether it has deactivated a pending timer or not.
* (ie. del_timer() of an inactive timer returns 0, del_timer() of an
* active timer returns 1.)
*/
//kernel\time\timer.c
int del_timer(struct timer_list *timer)
{
struct timer_base *base;
unsigned long flags;
int ret = 0;
debug_assert_init(timer);
if (timer_pending(timer)) {
base = lock_timer_base(timer, &flags);
ret = detach_if_pending(timer, base, true);
raw_spin_unlock_irqrestore(&base->lock, flags);
}
return ret;
}
入参没什么好说的,返回值0代表被删除的timer还未超期,1代表被删除的timer已经超期处理了。
2.5 综合应用示例
下面的示例实现的功能为,设置一个新的timer_list,并设置其超期间隔为1s,在超期任务中再次延长其超期间隔为1s,如此直至第11次超期回调后删除该timer_list。基本代码如下
#define DEL_TIMER_MAX 10
void timer_one_callback(struct timer_list * tim)
{
int ret = 0;
if(timer_one.callCount > DEL_TIMER_MAX) {
ret = del_timer(tim);
DEFER_CALL_TEST_INFO("Del timer. [callCount]%d.[ret]%d", timer_one.callCount, ret);
return;
} else {
DEFER_CALL_TEST_INFO("Expires the one timer.[callCount]%d", timer_one.callCount++);
mod_timer(tim, jiffies + HZ);
}
}
kernel_timer_test
struct timer_list *timerTmp = &timer_one.timer;
timerTmp->expires = jiffies + HZ;//1s
timerTmp->flags = 0;
timerTmp->function = timer_one_callback;
add_timer(timerTmp);
执行结果如下
board:/ # insmod /data/deferCallTest.ko
[20751.903661] deferCallTest:timer_one_callback,33:Expires the one timer.[callCount]0
[20752.927771] deferCallTest:timer_one_callback,33:Expires the one timer.[callCount]1
[20753.951638] deferCallTest:timer_one_callback,33:Expires the one timer.[callCount]2
[20754.975499] deferCallTest:timer_one_callback,33:Expires the one timer.[callCount]3
[20755.999623] deferCallTest:timer_one_callback,33:Expires the one timer.[callCount]4
[20757.023622] deferCallTest:timer_one_callback,33:Expires the one timer.[callCount]5
[20758.047619] deferCallTest:timer_one_callback,33:Expires the one timer.[callCount]6
[20759.071780] deferCallTest:timer_one_callback,33:Expires the one timer.[callCount]7
[20760.095779] deferCallTest:timer_one_callback,33:Expires the one timer.[callCount]8
[20761.119773] deferCallTest:timer_one_callback,33:Expires the one timer.[callCount]9
[20762.143609] deferCallTest:timer_one_callback,33:Expires the one timer.[callCount]10
[20763.167778] deferCallTest:timer_one_callback,30:Del timer. [callCount]11.[ret]0
3.workqueue–工作队列
工作队列是你任何时候,想抛出目前不太想做的任务,可见工作队列和timer在现象上如此的相似,但既然存在那么就是有对应的使用场景的,下面是我认为的两个主要的不同点。
a.工作队列不像timer对时间那样需要一个确切的执行节点,工作队列一般是在想用的时候再将任务包装成一个work(work_struct)入列。
b.工作队列中的回调函数则是在内核进程的上下文执行,所以在其回调中是允许调用可能会造成进程休眠的接口的。
3.1 workqueue_struct
既然是工作队列,那么肯定得有队列。该结构体就是描述队列的头的,其后一般跟着一个个work_struct也就是一个个将要被执行的任务。其定义如下
//kernel\workqueue.c
struct workqueue_struct {
...
struct list_head list; /* PR: list of all workqueues */
...
};
此处省略对理解使用没有帮助的成员,list用来连接work_struct。
3.2 work_struct
该数据结构用来描述一个任务,也就是被推迟的代码。其定义如下
// kernel\include\linux\workqueue.h
typedef void (*work_func_t)(struct work_struct *work);
struct work_struct {
struct list_head entry;
work_func_t func;
};
entry用于和工作队列进行连接。func则是在该work被调度执行时允许的函数,其入参就是本wiork。
3.3 使用全局工作队列来处理work_struct
挂在全局队列上的work_struct可能来自于各个驱动各个进程,如果想要自己的work_struct被处理的快点建议使用独立的工作队列来处理。下面是全局工作队列的使用示例
3.43.1 代码示例
static void global_work_callback(struct work_struct *work)
{
struct my_works *mw = container_of(work, struct my_works, global_work);
DEFER_CALL_TEST_INFO("[global_callback_count]%d", mw->global_callback_count++);
}
INIT_WORK(&works.global_work, global_work_callback);
schedule_work(&works.global_work)
代码实现非常的简单,创建一个 work_struct 设置其回调函数为global_work_callback,并将其压入全局工作队列中等待执行。global_work_callback则只对被调用次数做计数。
执行结果如下
[76512.088402] deferCallTest:global_work_callback,85:[global_callback_count]0
3.3.2 INIT_WORK
INIT_WORK为帮助宏,用于简化work_struct的初始化,其定义如下
#define INIT_WORK(_work, _func) \
__INIT_WORK((_work), (_func), 0)
(_work)->func = (_func); \
该函数主要初始化入参_work中的func。
3.3.3 schedule_work
用于将一个work_struct添加到全局工作队列中,其定义如下
//kernel/include/linux/workqueue.h
static inline bool schedule_work(struct work_struct *work)
{
return queue_work(system_wq, work);
}
3.4 使用独立的工作队列来处理work_struct
这种方式相对上面使用全局工作队列的好处也很明显,资源独享带来的当然是更快的响应。
3.4.1 create_workqueue
用于创建独立的工作队列头,其定义如下
//kernel/include/linux/workqueue.h
struct workqueue_struct *alloc_workqueue(const char *fmt,
unsigned int flags,
int max_active, ...);
#define create_workqueue(name) \
alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))
入参直接使用字符串即可,返回值则是类型为workqueue_struct的指针,用于挂载work_struct数据结构。
3.4.2 queue_work
用于将指定的work_struct压入指定的工作队列中,以供执行。其定义如下
//kernel/include/linux/workqueue.h
static inline bool queue_work(struct workqueue_struct *wq,
struct work_struct *work)
{
return queue_work_on(WORK_CPU_UNBOUND, wq, work);
}
入参很简单,返回true代表成功压入,返回false代表该work_struct已存在于被压入的工作队列中。
3.4.3 代码示例
处理使用自定义的工作队列,程序意图和3.3.1的完全一致,在此就不赘述了,直接贴代码
static struct workqueue_struct* myWq;
workqueue_test
myWq = create_workqueue("flagstaffWorkqueue");
INIT_WORK(&works.internal_work, internal_work_callback);
queue_work(myWq, &works.internal_work)
defer_call_test_drv_exit
destroy_workqueue(myWq);
执行结果如下
[76512.088391] deferCallTest:internal_work_callback,95:[internal_callback_count]0
4. 完整源码
https://gitee.com/solo-king/linux-kernel-base-usage/blob/master/flagstaff/deferCallTest.c