Linux内核线程kernel thread详解( 二 )


Workqueue机制
因此在linux-2.6以后, 提供了更加方便的接口kthead_create和kthread_run, 同时将内核线程的创建操作延后, 交给一个工作队列workqueue 。
Linux中的workqueue机制就是为了简化内核线程的创建 。通过kthread_create并不真正创建内核线程, 而是将创建工作create work插入到工作队列helper_wq中, 随后调用workqueue的接口就能创建内核线程 。并且可以根据当前系统CPU的个数创建线程的数量,使得线程处理的事务能够并行化 。workqueue是内核中实现简单而有效的机制,他显然简化了内核daemon的创建,方便了用户的编程.
工作队列(workqueue)是另外一种将工作推后执行的形式.工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行 。最重要的就是工作队列允许被重新调度甚至是睡眠 。
2号进程kthreadd
但是这种方法依然看起来不够优美, 我们何不把这种创建内核线程的工作交给一个特殊的内核线程来做呢?
于是linux-2.6.22引入了kthreadd进程, 并随后演变为2号进程, 它在系统初始化时同1号进程一起被创建(当然肯定是通过kernel_thread), 并随后演变为创建内核线程的真正建造师, 它会循环的是查询工作链表static LIST_HEAD(kthread_create_list);中是否有需要被创建的内核线程, 而我们的通过kthread_create执行的操作, 只是在内核线程任务队列kthread_create_list中增加了一个create任务, 然后会唤醒kthreadd进程来执行真正的创建操作
内核线程会出现在系统进程列表中, 但是在ps的输出中进程名command由方括号包围, 以便与普通进程区分 。
如下图所示, 我们可以看到系统中, 所有内核线程都用[]标识, 而且这些进程父进程id均是2, 而2号进程kthreadd的父进程是0号进程
使用ps -eo pid,ppid,command

Linux内核线程kernel thread详解

文章插图
 
kernel_thread
kernel_thread是最基础的创建内核线程的接口, 它通过将一个函数直接传递给内核来创建一个进程, 创建的进程运行在内核空间, 并且与其他进程线程共享内核虚拟地址空间
kernel_thread的实现经历过很多变革
早期的kernel_thread执行更底层的操作, 直接创建了task_struct并进行初始化,
引入了kthread_create和kthreadd 2号进程后, kernel_thread的实现也由统一的_do_fork(或者早期的do_fork)托管实现
早期实现
早期的内核中, kernel_thread并不是使用统一的do_fork或者_do_fork这一封装好的接口实现的, 而是使用更底层的细节,它内部调用了更加底层的arch_kernel_thread创建了一个线程,但是这种方式创建的线程并不适合运行,因此内核提供了daemonize函数 。
extern void daemonize(void);
主要执行如下操作
  1. 该函数释放其父进程的所有资源,不然这些资源会一直锁定直到线程结束 。
  2. 阻塞信号的接收
  3. 将init用作守护进程的父进程
我们将了这么多kernel_thread, 但是我们并不提倡我们使用它, 因为这个是底层的创建内核线程的操作接口, 使用kernel_thread在内核中执行大量的操作, 虽然创建的代价已经很小了, 但是对于追求性能的linux内核来说还不能忍受
因此我们只能说kernel_thread是一个古老的接口, 内核中的有些地方仍然在使用该方法, 将一个函数直接传递给内核来创建内核线程
新版本的实现
于是linux-3.x下之后, 有了更好的实现, 那就是
延后内核的创建工作, 将内核线程的创建工作交给一个内核线程来做, 即kthreadd 2号进程
但是在kthreadd还没创建之前, 我们只能通过kernel_thread这种方式去创建,
同时kernel_thread的实现也改为由_do_fork(早期内核中是do_fork)来实现
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
return _do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
(unsigned long)arg, NULL, NULL, 0);
}
kthread_create
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
void *data,
int node,
const char namefmt[], ...);
#define kthread_create(threadfn, data, namefmt, arg...)
kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)
创建内核更常用的方法是辅助函数kthread_create,该函数创建一个新的内核线程 。最初线程是停止的,需要使用wake_up_process启动它 。
kthread_run
/**
* kthread_run - create and wake a thread.
* @threadfn: the function to run until signal_pending(current).


推荐阅读