linux进程的工作机制,Linux工作队列实现机制

Linux内核的原子操作

工作项在初始化的时候会调用WORK_DATA_INIT()宏来将work_struct的data域初始化成0,所有这里!test_and_set_bit(WORK_STRUCT_PENDING, work_data_bits(work))结果为1。

static void__queue_work(struct cpu_workqueue_struct *cwq, struct work_struct *work)

{

unsigned long flags;

spin_lock_irqsave(&cwq->lock, flags);

insert_work(cwq, work, &cwq->worklist);

spin_unlock_irqrestore(&cwq->lock, flags);

}

static voidinsert_work(struct cpu_workqueue_struct *cwq,

struct work_struct *work, struct list_head *head)

{

trace_workqueue_insertion(cwq->thread, work);

set_wq_data(work, cwq);//设置work_struct的pending未决标志

/*

* Ensure that we get the right work->data if we see the

* result of list_add() below, see try_to_grab_pending().

*/

smp_wmb();//多处理器的相关动作

list_add_tail(&work->entry, head);//工作项加入链表

wake_up(&cwq->more_work);//唤醒等待在该等待队列头上的所有等待队列项

}

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

static voidrun_workqueue(struct cpu_workqueue_struct *cwq)

{

spin_lock_irq(&cwq->lock);

while (!list_empty(&cwq->worklist)) {

struct work_struct *work= list_entry(cwq->worklist.next,

struct work_struct, entry);

work_func_t f = work->func;//取出工作项函数

#ifdef CONFIG_LOCKDEP

/*

* It is permissible to free the struct work_struct

* from inside the function that is called from it,

* this we need to take into account for lockdep too.

* To avoid bogus "held lock freed" warnings as well

* as problems when looking into work->lockdep_map,

* make a copy and use that here.

*/

struct lockdep_map lockdep_map = work->lockdep_map;

#endif

trace_workqueue_execution(cwq->thread, work);

cwq->current_work = work;

list_del_init(cwq->worklist.next);//从链表中删除工作项节点

spin_unlock_irq(&cwq->lock);

BUG_ON(get_wq_data(work) != cwq);

work_clear_pending(work);

lock_map_acquire(&cwq->wq->lockdep_map);

lock_map_acquire(&lockdep_map);

f(work);

//执行对应的工作项函数,将work_struct结构体指针作为参数传递进去

lock_map_release(&lockdep_map);

lock_map_release(&cwq->wq->lockdep_map);

if (unlikely(in_atomic() || lockdep_depth(current) > 0)) {

printk(KERN_ERR "BUG: workqueue leaked lock or atomic: "

"%s/0x%08x/%d/n",

current->comm, preempt_count(),

task_pid_nr(current));

printk(KERN_ERR "    last function: ");

print_symbol("%s/n", (unsigned long)f);

debug_show_held_locks(current);

dump_stack();

}

spin_lock_irq(&cwq->lock);

cwq->current_work = NULL;

}

spin_unlock_irq(&cwq->lock);

}

我们在新建工作项的时候,需要将工作函数的参数设置成work_struct结构体指针,例如:

static void sitronix_ts_work(struct work_struct *work);

INIT_WORK(&priv->work, sitronix_ts_work);

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

虽然以上内容是通过创建系统默认的工作队列keventd_wq和工作者线程events/n来分析了其创建过程,提交工作项过程和提交工作后唤醒工作者线程之后的所做的动作。

其实我们自己也可以使用这些接口来创建独立的工作队列和工作者线程来专门为特定的任务服务,例如在和linux的睡眠和唤醒架构中就使用这种方式,@ kernel/kernel/power/wakelock.c

core_initcall(wakelocks_init)在wakelocks_init()函数中有创建两个工作队列和其对于的工作者线程:

sys_sync_work_queue = create_singlethread_workqueue("fs_sync");

suspend_work_queue = create_singlethread_workqueue("suspend");

early suspend的时候调用:@ kernel/kernel/power/earlysuspend.c

static DECLARE_WORK(early_sys_sync_work, early_sys_sync);

queue_work(sys_sync_work_queue, &early_sys_sync_work);

static DECLARE_WORK(early_suspend_work, early_suspend);

queue_work(suspend_work_queue, &early_suspend_work);

suspend的时候调用:@ kernel/kernel/power/wakelock.c

static DECLARE_WORK(suspend_work, suspend);

queue_work(suspend_work_queue, &suspend_work);

下面来看一看延时执行的工作项是如何提交的,这里和上面共同的部分不讨论,只讨论如何实现的延时执行,其余部分是相同的。

delayed_work结构体的定义:@ kernel/include/linux/workqueue.h

structdelayed_work{

struct work_struct work;

structtimer_listtimer;

//对work_struct结构体进行了封装,添加了一个timer_list结构体

};

#define DECLARE_DELAYED_WORK(n, f)/

struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f)

#define __DELAYED_WORK_INITIALIZER(n, f) {/

.work = __WORK_INITIALIZER((n).work, (f)),/

.timer = TIMER_INITIALIZER(NULL, 0, 0),/

}//初始化work_struct结构体和前文方式一样,这里需要多初始化timer域。

@ kernel/include/linux/timer.h

#define TIMER_INITIALIZER(_function, _expires, _data) {/

.entry = { .prev = TIMER_ENTRY_STATIC },/

.function = (_function),/

.expires = (_expires),/

.data = (_data),/

.base = &boot_tvec_bases,/

__TIMER_LOCKDEP_MAP_INITIALIZER(/

__FILE__ ":" __stringify(__LINE__))/

}

通常情况下使用的定义一个定时器也是调用该宏来初始化:#define DEFINE_TIMER(_name, _function, _expires, _data)/

struct timer_list _name =/

TIMER_INITIALIZER(_function, _expires, _data)

提交一个延时执行的工作项使用函数:

intschedule_delayed_work(struct delayed_work *dwork,  unsigned long delay)

{

return queue_delayed_work(keventd_wq, dwork, delay);

}// delay -单位是jiffies,或者传递0的话,就是立即执行和schedule_work()一样了

// @ kernel/kernel/timer.c文件中有实现一些time to jiffies的函数:

// msecs_to_jiffied()、usecs_to_jiffies()等

intqueue_delayed_work(struct workqueue_struct *wq,

struct delayed_work *dwork, unsigned long delay)

{

if (delay == 0)//如果传递进来的delay是0,那么走立即执行的通路

return queue_work(wq, &dwork->work);

returnqueue_delayed_work_on(-1, wq, dwork, delay);

}

intqueue_delayed_work_on(int cpu, struct workqueue_struct *wq,

struct delayed_work *dwork, unsigned long delay)

{

int ret = 0;

struct timer_list *timer = &dwork->timer;

struct work_struct *work = &dwork->work;

// test_and_set_bit()设置特定位并传回该位原来的值

//如果未决位为0,设置pending未决位后返回0

if (!test_and_set_bit(WORK_STRUCT_PENDING, work_data_bits(work))) {

BUG_ON(timer_pending(timer));

BUG_ON(!list_empty(&work->entry));

timer_stats_timer_set_start_info(&dwork->timer);

/* This stores cwq for the moment, for the timer_fn */

set_wq_data(work, wq_per_cpu(wq, raw_smp_processor_id()));

timer->expires = jiffies + delay;//到时时间阀值

timer->data = (unsigned long)dwork;//向定时执行函数传递的参数

timer->function = delayed_work_timer_fn;//定时执行函数

if (unlikely(cpu >= 0))

add_timer_on(timer, cpu);

else

add_timer(timer);//向系统添加一个timer

ret = 1;

}

return ret;

}

static voiddelayed_work_timer_fn(unsigned long __data)

{

struct delayed_work *dwork = (struct delayed_work *)__data;

struct cpu_workqueue_struct *cwq = get_wq_data(&dwork->work);

struct workqueue_struct *wq = cwq->wq;

__queue_work(wq_per_cpu(wq, smp_processor_id()), &dwork->work);

}

看到函数__queue_work()是不是觉得很眼熟呢?没错,延时执行的工作项走的提交路线和正常提交工作项在该函数之前不一样,后面后市一样了。换句话说,提交延时工作项,只是延时提交了而已,并不是立即提交给工作者线程,让其工作者线程延时来执行。

其余函数介绍:

voidflush_workqueue(struct workqueue_struct *wq);

此函数刷新指定工作队列,他会一直等待,知道该工作队列中所有工作项都已完成。

voidflush_scheduled_work(void);

和上面函数类似,只是刷新默认工作队列:keventd_wq。

void flush_delayed_work(struct delayed_work *dwork);

等待一个delayed_work执行完。

int flush_work(struct work_struct *work);

等待一个work执行完。

如何取消提交的延时工作项?

cancel_work_sync(struct work_struct *work);

该函数取消已排在工作队列中的未决work,返回true。如果work的callback已经在运行了,那么该函数将会阻塞到其执行完毕。

static inline int__cancel_delayed_work(struct delayed_work *work)

{

int ret;

ret = del_timer(&work->timer);

if (ret)

work_clear_pending(&work->work);

return ret;

}

// if it returns 0 the timer function may be running and the queueing is in progress.

static inline intcancel_delayed_work(struct delayed_work *work)

{

int ret;

ret = del_timer_sync(&work->timer);//阻塞直到定时函数执行完

if (ret)

work_clear_pending(&work->work);

return ret;

}

//同上

三、工作队列新老版本比较

这篇网文已有详细的说明,请参考。

博客分析2

工作队列(work queue)是另外一种将工作推后执行的形式,它和tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。

那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。

工作、工作队列和工作者线程

如前所述,我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events,自己也可以创建自己的工作者线程。

表示工作的数据结构

工作用中定义的work_struct结构表示:

structwork_struct{

unsigned longpending; /*这个工作正在等待处理吗?*/

structlist_head entry; /*连接所有工作的链表*/

void(*func) (void*); /*要执行的函数*/

void*data; /*传递给函数的参数*/

void*wq_data; /*内部使用*/

struct timer_listtimer; /*延迟的工作队列所用到的定时器*/

};

这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。

3.创建推后的工作

要使用工作队列,首先要做的是创建一些需要推后完成的工作。可以通过DECLARE_WORK在编译时静态地建该结构:

DECLARE_WORK(name, void (*func) (void *), void *data);

这样就会静态地创建一个名为name,待执行函数为func,参数为data的work_struct结构。

同样,也可以在运行时通过指针创建一个工作:

INIT_WORK(struct work_struct *work, woid(*func) (void*), void *data);

这会动态地初始化一个由work指向的工作。

4.工作队列中待执行的函数

工作队列待执行的函数原型是:

void work_handler(void *data)

这个函数会由一个工作者线程执行,因此,函数会运行在进程上下文中。默认情况下,允许响应中断,并且不持有任何锁。如果需要,函数可以睡眠。需要注意的是,尽管该函数运行在进程上下文中,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射。通常在系统调用发生时,内核会代表用户空间的进程运行,此时它才能访问用户空间,也只有在此时它才会映射用户空间的内存。

5.对工作进行调度

现在工作已经被创建,我们可以调度它了。想要把给定工作的待处理函数提交给缺省的events工作线程,只需调用

schedule_work(&work);

work马上就会被调度,一旦其所在的处理器上的工作者线程被唤醒,它就会被执行。

有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。在这种情况下,可以调度它在指定的时间执行:

schedule_delayed_work(&work, delay);

这时,&work指向的work_struct直到delay指定的时钟节拍用完以后才会执行。

6.工作队列的简单应用

#include#include#includestaticstructworkqueue_struct*queue=NULL;staticstructwork_struct work;staticvoidwork_handler(structwork_struct*data){printk(KERN_ALERT"workhandler function./n");}staticint__init test_init(void){queue=create_singlethread_workqueue("helloworld"); /*创建一个单线程的工作队列*/if(!queue)gotoerr;INIT_WORK(&work,work_handler);schedule_work(&work);return0;err:return-1;}staticvoid__exit test_exit(void){destroy_workqueue(queue);}MODULE_LICENSE("GPL");module_init(test_init);module_exit(test_exit);0b1331709591d260c1c78e86d0c51c18.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值