本文共 3564 字,大约阅读时间需要 11 分钟。
在内核开发中,工作队列(workqueue)是一种非常重要的机制,用于异步处理任务。通过工作队列,可以将任务提交给特定的内核线程进行处理,这在内核模块开发中尤为常见。以下将详细讲解如何在内核中创建事件线程,并将work_struct添加到工作队列中。
在内核中,提交一个work_struct到工作队列,可以通过调用queue_work
函数实现。queue_work
函数接受两个参数:workqueue_struct *wq
和work_struct *work
。wq
是工作队列的实例,而work
则是待处理的任务。
int queue_work(struct workqueue_struct *wq, struct work_struct *work){ int ret; ret = queue_work_on(get_cpu(), wq, work); put_cpu(); return ret;}
queue_work_on
函数是queue_work
的核心部分,它负责将work_struct
添加到指定的工作队列中。get_cpu()
和put_cpu()
函数用于获取和释放当前处理CPU,这在多核环境中非常重要。
在内核中,默认会创建一个名为keventd_wq
的工作队列,这个队列用于处理事件相关的任务。keventd_wq
是通过create_workqueue
函数创建的。
void __init init_workqueues(void){ alloc_cpumask_var(&cpu_populated_map, GFP_KERNEL); cpumask_copy(cpu_populated_map, cpu_online_mask); singlethread_cpu = cpumask_first(cpu_possible_mask); cpu_singlethread_map = cpumask_of(singlethread_cpu); hotcpu_notifier(workqueue_cpu_callback, 0); keventd_wq = create_workqueue("events"); BUG_ON(!keventd_wq);}
create_workqueue
函数通过__create_workqueue_key
来创建工作队列实例。工作队列的创建涉及到CPU的初始化和线程的创建。singlethread
标志决定了是否为每个CPU创建一个独立的线程。
#define create_workqueue(name) __create_workqueue((name), 0, 0, 0)#define __create_workqueue(name, singlethread, freezeable, rt) \ __create_workqueue_key((name), (singlethread), (freezeable), (rt), NULL, NULL)struct workqueue_struct *__create_workqueue_key(const char *name, int singlethread, int freezeable, int rt, struct lock_class_key *key, const char *lock_name){ // ... 代码省略 ... if (singlethread) { cwq = init_cpu_workqueue(wq, singlethread_cpu); err = create_workqueue_thread(cwq, singlethread_cpu); start_workqueue_thread(cwq, -1); } else { // ... 代码省略 ... } return cwq;}
init_cpu_workqueue
函数初始化CPU的工作队列,create_workqueue_thread
函数创建线程,并将线程绑定到指定的CPU上。start_workqueue_thread
函数则启动线程,开始处理任务。
当queue_work
函数将work_struct
添加到工作队列后,insert_work
函数会将任务添加到队列中,并触发相关的处理流程。
static void insert_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); smp_wmb(); list_add_tail(&work->entry, head); wake_up(&cwq->more_work);}
set_wq_data
函数将work_struct
的数据与工作队列关联起来,smp_wmb
函数用于保证内核的可见性,这在多核环境中非常重要。list_add_tail
函数将任务添加到工作队列的末尾,wake_up
函数则通知相关的等待队列,确保任务能够被处理。
work_pending
是一个内核标志位,用于检查是否有任务需要处理。它通过检查TIF_NEED_RESCHED
标志位来判断。
#define _TIF_NEED_RESCHED (1 << TIF_NEED_RESCHED)#define TIF_NEED_RESCHED 3 /* rescheduling necessary */work_pending: testb $_TIF_NEED_RESCHED, %cl jz work_notifysig work_resched: call schedule LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY) TRACE_IRQS_OFF movl TI_flags(%ebp), %ecx andl $_TIF_WORK_MASK, %ecx jz restore_all testb $_TIF_NEED_RESCHED, %cl jnz work_reschedwork_notifysig: movl %esp, %eax xorl %edx, %edx call do_notify_resume jmp resume_userspace_sigEND(work_pending)
work_resched
函数会调用schedule
函数重新调度当前进程,确保任务能够按时处理。
do_notify_resume
函数负责通知用户空间,确保用户线程能够继续执行。这通过TI_flags
来实现,检查是否有需要重新调度的任务。
#define resume_userspace_sig resume_userspaceENTRY(resume_userspace) LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY) TRACE_IRQS_OFF movl TI_flags(%ebp), %ecx andl $_TIF_WORK_MASK, %ecx jne work_pending jmp restore_all
do_notify_resume
函数通过resume_userspace
返回,用户线程可以继续执行任务。
在内核中,通过queue_work
函数将work_struct
提交到工作队列keventd_wq
,可以创建并处理事件线程。默认的工作队列keventd_wq
为每个CPU创建一个独立的线程,处理任务。了解工作队列的实现机制,有助于更好地开发和优化内核模块。
转载地址:http://ymgfk.baihongyu.com/