linux kernel 中的推迟调用(defer callback)(四)

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. 完整源码

deferCallTest

https://gitee.com/solo-king/linux-kernel-base-usage/blob/master/flagstaff/deferCallTest.c
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值