linux内核线程和 kthread_worker

来源:互联网 发布:c语言面向对象编程 pdf 编辑:程序博客网 时间:2024/06/05 17:13

1. 内核线程       

      内核线程是直接由内核本身启动的进程。内核线程实际上是将内核函数委托为独立的进程,与系统中的其他进程并行执行,完成内核的委托任务,当然只能在内核空间中执行,不能访问用户空间资源。内核线程通常又称为守护进程。他们一般用于执行下列人物:
       1.  周期性的将修改的内存页与页来源设备同步,经常用到就是内存文件映射的同步就通过内核线程实现内存数据和磁盘文件数据的同步
       2. 在内存页很少的时候,将很少使用的内存页写入交换区
       3.  管理延时动作,计划任务等
       4. 实现系统的事务日志

      下面来看看内核线程的创建过程,内核线程的创建通过数据结构 struct thread_create_info,在kthread.c文件中
struct kthread_create_info{/* Information passed to kthread() from kthreadd. */int (*threadfn)(void *data);void *data;int node;/* Result passed back to kthread_create() from kthreadd. */struct task_struct *result;struct completion done;struct list_head list;};
@threadfn, data:这个参数就是内核线程创建后要执行的函数,也就是内核委托的任务,data成员为函数参数
@node:node表示这个函数要在哪个memory node上面分配内核线程栈
@result:内核线程的进程数据结构
@list: 用于管理要创建的内核线程

内核线程通过函数kthread_create_on_node创建。其实这个函数并没有真正的创建内核线程,只是初始化了一个kthread_create_info结构,并将其加入一个全局列表中。然后唤醒另外一个内核线程来创建真正的内核线程的task_struct。
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),   void *data,   int node,   const char namefmt[],   ...){struct kthread_create_info create;create.threadfn = threadfn;create.data = data;create.node = node;init_completion(&create.done);spin_lock(&kthread_create_lock);list_add_tail(&create.list, &kthread_create_list);spin_unlock(&kthread_create_lock);wake_up_process(kthreadd_task);wait_for_completion(&create.done);
wake_up_process(kthreadd_task)会唤醒kthreadd_task线程,该线程遍历kthread_create_list这个管理待创建的内核线程的链表。kthreadd_task线程会执行下面的函数:
int kthreadd(void *unused){struct task_struct *tsk = current;/* Setup a clean context for our children to inherit. */set_task_comm(tsk, "kthreadd");ignore_signals(tsk);set_cpus_allowed_ptr(tsk, cpu_all_mask);set_mems_allowed(node_states[N_HIGH_MEMORY]);current->flags |= PF_NOFREEZE | PF_FREEZER_NOSIG;for (;;) {set_current_state(TASK_INTERRUPTIBLE);if (list_empty(&kthread_create_list))  //如果kthread_create_list为空,则切换进程schedule();__set_current_state(TASK_RUNNING);spin_lock(&kthread_create_lock);while (!list_empty(&kthread_create_list)) {  //遍历kthread_create_liststruct kthread_create_info *create;create = list_entry(kthread_create_list.next,    struct kthread_create_info, list);list_del_init(&create->list);spin_unlock(&kthread_create_lock);create_kthread(create);     //创建进程spin_lock(&kthread_create_lock);}spin_unlock(&kthread_create_lock);}return 0;}
static void create_kthread(struct kthread_create_info *create){int pid;#ifdef CONFIG_NUMAcurrent->pref_node_fork = create->node;#endif/* We want our own signal handler (we take no signals by default). */pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);if (pid < 0) {create->result = ERR_PTR(pid);complete(&create->done);}}
在create_kthread中,kernel_thread将应用标志调用do_fork创建进程,并把do_fork返回的task_struct赋值给kthread_create_info的result成员。至此内核线程的task_struct表示创建完成。kernel_thread函数将设置寄存器的地址,执行kthread函数,因此,所有的内核线程就可以在调度器下调度运行。

2. kthread_worker

     没找到关于这个的资料,也不知道自己的理解对不对了。kthread_worker/kthread_work是一种内核工作的更好的管理方式,可以多个内核线程在同一个worker上工作,共同完成work的工作,有点像线程池的工作方式。下面看看关于这个的数据结构:
typedef void (*kthread_work_func_t)(struct kthread_work *work);struct kthread_worker {spinlock_tlock;struct list_headwork_list;struct task_struct*task;};struct kthread_work {struct list_headnode;kthread_work_func_tfunc;wait_queue_head_tdone;atomic_tflushing;intqueue_seq;intdone_seq;};
先讲讲基本的工作原理,worker表示工作者,也就是线程落,当然这个线程是可以动态附加的,task成员表示当前附加进程。work_list为kthread_work链表,线程从work_list取出work,然后执行 work 的 func 函数,函数的定义如第一行。 有点不明白的是这个函数只有一个kthread_work当作参数,下面会看到这个参数就是包含但钱func的kthread_work本身,只有这么一个参数如果要有和这个work特定参数怎么办。暂时还没理解。

下面看看工作方式, worker和work的初始化就是直接初始化锁,链表和等待队列就可以了:
int kthread_worker_fn(void *worker_ptr){struct kthread_worker *worker = worker_ptr;struct kthread_work *work;WARN_ON(worker->task);worker->task = current;repeat:set_current_state(TASK_INTERRUPTIBLE);/* mb paired w/ kthread_stop */if (kthread_should_stop()) {__set_current_state(TASK_RUNNING);spin_lock_irq(&worker->lock);worker->task = NULL;spin_unlock_irq(&worker->lock);return 0;}work = NULL;spin_lock_irq(&worker->lock);if (!list_empty(&worker->work_list)) {work = list_first_entry(&worker->work_list,struct kthread_work, node);list_del_init(&work->node);}spin_unlock_irq(&worker->lock);if (work) {__set_current_state(TASK_RUNNING);work->func(work);smp_wmb();/* wmb worker-b0 paired with flush-b1 */work->done_seq = work->queue_seq;smp_mb();/* mb worker-b1 paired with flush-b0 */if (atomic_read(&work->flushing))wake_up_all(&work->done);} else if (!freezing(current))schedule();try_to_freeze();goto repeat;}
这个函数就是内核线程要执行的主函数,worker->task = current 把当前内核线程附加到woker上工作。
if (!list_empty(&worker->work_list)) {work = list_first_entry(&worker->work_list,struct kthread_work, node);list_del_init(&work->node);}spin_unlock_irq(&worker->lock);
这一段从work_list中取出一个work,并在worker上加锁,表示worker正在工作,不能操作
if (work) {__set_current_state(TASK_RUNNING);work->func(work);smp_wmb();/* wmb worker-b0 paired with flush-b1 */work->done_seq = work->queue_seq;smp_mb();/* mb worker-b1 paired with flush-b0 */if (atomic_read(&work->flushing))wake_up_all(&work->done);} 
       在当前线程运行work中指定的函数func, work->flushing表示在当前work上阻塞的线程个数,如果个数不为0,则唤醒所有在work阻塞的进程。
       还有一个问题就是为什么那个current一定是内核线程呢,这里有个限制就是 kthread_worker_fn 函数一般作为 kthread_create() 或者 kthread_run()函数的 threadfn 参数运行,因此附加到worker上的线程肯定是内核线程了。还有就是,多个线程可以附加的同一个worker上面,即,将同一个worker结构传给 kthread_run 或者 kthread_create当作 threadfn的参数就可以了。但是一个时间点只有一个内核线程附加到 worker 上面,也就是 worker 的 task 成员。 当在worker上面没有附加线程时,也就是在worker上面工作的线程都不在运行时,这时 worker 只是一个收集内核 work 的容器。看下面的代码:
bool queue_kthread_work(struct kthread_worker *worker,struct kthread_work *work){bool ret = false;unsigned long flags;spin_lock_irqsave(&worker->lock, flags);if (list_empty(&work->node)) {list_add_tail(&work->node, &worker->work_list);work->queue_seq++;if (likely(worker->task))wake_up_process(worker->task);ret = true;}spin_unlock_irqrestore(&worker->lock, flags);return ret;}
这段代码基本没什么问题了。 第一篇博客纪念以下。(*^__^*)