linux内核的kthreadd线程分析
文章目录
【注】
(1)本文代码片段出自:linux内核版本4.1.15
(2)本文不对进程和线程加以区别(即在本文中二者有着统一概念)
一、kthreadd线程的创建
与init
进程一样,在rest_init()
函数中将调用kthread_init()
函数创建kthreadd
线程,如下代码:
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
二、kthreadd线程入口分析
kthreadd
线程的线程入口为kthreadd
(/kernel/kthread.c),如下定义:
int kthreadd(void *unused)
{
struct task_struct *tsk = current;
//该函数会清除当前运行的可执行文件的所有trace,以便启动一个新的trace。
set_task_comm(tsk, "kthreadd");
//忽略tsk的信号
ignore_signals(tsk);
//该行代码允许kthreadd在任何CPU上运行
set_cpus_allowed_ptr(tsk, cpu_all_mask);
//设置由alloc_lock保护的内存空间
set_mems_allowed(node_states[N_MEMORY]);
//设置kthreadd线程不应该被冻结
current->flags |= PF_NOFREEZE;
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (list_empty(&kthread_create_list))
schedule();
__set_current_state(TASK_RUNNING);
spin_lock(&kthread_create_lock);
while (!list_empty(&kthread_create_list)) {
struct 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;
}
上述第3行代码:使用current
获取线程控制块。current
定义如下(/include/asm-generic/current.h):
#define get_current() (current_thread_info()->task)
#define current get_current()
上述代码中16-36行代码,for(;;)
是kthreadd
的核心功能。使用set_current_state(TASK_INTERRUPTIBLE);
将当前线程设置为TASK_INTERRUPTIBLE状态,如果当前没有要创建的线程(这一步由kthread_create_list
实现),则主动调用schedule()
执行调度,让出CPU,这部分由17-19行代码实现。否则,kthreadd
将处于唤醒状态,那么就会执行对应的线程创建操作,这部分功能由23~34行代码实现。
上述代码中,出现了kthread_create_list
这个待创建线程链表,定义如下:
static LIST_HEAD(kthread_create_list);
第26~27行代码,使用:
create = list_entry(kthread_create_list.next,
struct kthread_create_info, list);
从链表中取得 kthread_create_info 结构的地址。
第31行代码使用create_kthread()
创建create
代表的内核线程。定义如下(/kernel/kernel.c):
static void create_kthread(struct kthread_create_info *create)
{
int pid;
#ifdef CONFIG_NUMA
current->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) {
/* If user was SIGKILLed, I release the structure. */
struct completion *done = xchg(&create->done, NULL);
if (!done) {
kfree(create);
return;
}
create->result = ERR_PTR(pid);
complete(done);
}
}
在create_kthread()
函数中创建线程同样由kernel_thread()
函数完成:
pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
可见新创建的线程的入口是kthread
,下文将继续分析该线程函数。
三、新创建的内核线程入口函数kthread分析
该函数定义在(/kernel/kthead.c)中:
static int kthread(void *_create)
{
//拷贝数据
//将_create代表的kthread_create_info赋值给create
struct kthread_create_info *create = _create;
//设置线程执行的函数指针
int (*threadfn)(void *data) = create->threadfn;
void *data = create->data;
struct completion *done;
struct kthread self;
int ret;
self.flags = 0;
self.data = data;
init_completion(&self.exited);
init_completion(&self.parked);
current->vfork_done = &self.exited;
/* If user was SIGKILLed, I release the structure. */
done = xchg(&create->done, NULL);
if (!done) {
kfree(create);
do_exit(-EINTR);
}
/* OK, tell user we're spawned, wait for stop or wakeup */
/* 创建的新的内核线程被置为TASK_UNINTERRUPTIBLE,需要显式的被唤醒才能运行 */
__set_current_state(TASK_UNINTERRUPTIBLE);
create->result = current;
complete(done);
//运行到此处,线程已经创建完毕,调用schedule执行调度,主动让出CPU,唤醒的是调用kthread_create函数的进程。
schedule();
//当本线程(创建的线程)被唤醒后,将继续执行后续代码
ret = -EINTR;
if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
__kthread_parkme(&self);
ret = threadfn(data);
}
/* we can't just return, we must preserve "self" on stack */
do_exit(ret);
}
创建新 thread 的进程恢复运行 kthread_create() 并且返回新创建线程的任务描述符
新创建的线程由于执行了schedule()
调度,此时并没有执行。直到使用wake_up_process(p)
;唤醒新创建的线程。当线程被唤醒后, 会接着执行threadfn(data)(即:对应线程的真正线程函数):
if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
__kthread_parkme(&self);
//执行创建线程对应的线程函数,传入的参数为data
ret = threadfn(data);
}
四、总结与补充
(4-1)kthread_create()函数
通过以上代码分析,可见最重要的是kthread_create_list
这个全局链表。当使用kthread_create()
函数创建线程时,最终都会将线程相关资源添加到kthread_create_list
链表中。如下代码(/include/linux/kthread.h):
#define kthread_create(threadfn, data, namefmt, arg...) \
kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)
由create_kthread()
可知,通过kthread_create()
入口进来的内核线程创建路径都具有统一的线程函数kthread()
。
而linux内核的2号线程kthreadd
正好负责内核线程的调度和管理。所以说创建的内核线程都是直接或间接的以kthread
为父进程。
(4-2)kthread_create与kernel_thread的区别
在(init/main.c)中,1号init
线程和2号kthreadd
线程都是通过kernel_thread()
函数创建的,那么kernel_thead()
后续会调用do_fork()
实现线程相关的创建操作。kernel_thread()
函数与kthread_create()
创建的内核线程有什么区别呢?
(1)kthread_create()
创建的内核线程有干净的上下文环境,适合用于驱动模块或用户空间程序创建内核线程,而不会把某些内核信息暴露给用户空间程序。
(2)二者创建的进程的父进程不同:kthread_create()
创建的进程的父进程被指定为kthreadd
, 而kernel_thread()
创建的进程可以是init
或其他内核线程。
(4-3)kthread_run()函数
kthread_run()
函数用于创建并唤醒一个线程,其本质上是调用kthread_create()
创建一个线程,并使用wake_up_process()
唤醒该线程。定义如下:
#define kthread_run(threadfn, data, namefmt, ...) \
({ \
struct task_struct *__k \
= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
if (!IS_ERR(__k)) \
wake_up_process(__k); \
__k; \
})
(4-4)linux内核创建线程的整体过程
综上,linux内核创建线程的整个过程为:
(1)创建kthread_create_info
结构,为其分配空间,指定线程函数,线程相关描述数据等。
(2)将线程的kthread_create_info
结构添加到kthread_create_list
全局的线程创建链表中,并唤醒2号kthreadd
线程。
(3)2号kthreadd
线程将从kthread_create_list
全局的线程创建链表中取出每一个kthread_create_info
结构,然后调用create_kthread()
函数创建一个线程函数为kthread
的线程。在kthread
线程函数中将执行创建线程指定的线程函数。
五、附录
【附录一】
kthread_create_on_cpu()
创建一个绑定了CPU的线程:
/**
* kthread_create_on_cpu - Create a cpu bound kthread
* @threadfn: the function to run until signal_pending(current).
* @data: data ptr for @threadfn.
* @cpu: The cpu on which the thread should be bound,
* @namefmt: printf-style name for the thread. Format is restricted
* to "name.*%u". Code fills in cpu number.
*
* Description: This helper function creates and names a kernel thread
* The thread will be woken and put into park mode.
*/
struct task_struct *kthread_create_on_cpu(int (*threadfn)(void *data),
void *data, unsigned int cpu,
const char *namefmt)
{
struct task_struct *p;
p = kthread_create_on_node(threadfn, data, cpu_to_node(cpu), namefmt,
cpu);
if (IS_ERR(p))
return p;
set_bit(KTHREAD_IS_PER_CPU, &to_kthread(p)->flags);
to_kthread(p)->cpu = cpu;
/* Park the thread to get it out of TASK_UNINTERRUPTIBLE state */
kthread_park(p);
return p;
}
【附录二】
kthread_create_on_node()
函数将操作kthread_create_list
链表。kthread_create_on_node()
函数的功能是:创建kthread,并将其添加到 kthread_create_list
线程创建链表中,并返回对应的task_struct
。
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
void *data, int node,
const char namefmt[],
...)
{
DECLARE_COMPLETION_ONSTACK(done);
struct task_struct *task;
struct kthread_create_info *create = kmalloc(sizeof(*create),
GFP_KERNEL);
if (!create)
return ERR_PTR(-ENOMEM);
create->threadfn = threadfn;
create->data = data;
create->node = node;
create->done = &done;
spin_lock(&kthread_create_lock);
/*注意这个全局链表kthread_create_list, 所用通过kthread_create创建的内核线程都会挂在这*/
list_add_tail(&create->list, &kthread_create_list);
spin_unlock(&kthread_create_lock);
/*这是最重要的地方,从代码看是唤醒了kthreadd_task这个进程,该进程就是内核中 的1号进程kthreadd 。因为kthreadd_task在rest_init()中通过find_task_by_pid_ns(pid, &init_pid_ns);进行了linux内核的早期赋值 */
wake_up_process(kthreadd_task);
/*
* Wait for completion in killable state, for I might be chosen by
* the OOM killer while kthreadd is trying to allocate memory for
* new kernel thread.
*/
if (unlikely(wait_for_completion_killable(&done))) {
/*
* If I was SIGKILLed before kthreadd (or new kernel thread)
* calls complete(), leave the cleanup of this structure to
* that thread.
*/
if (xchg(&create->done, NULL))
return ERR_PTR(-EINTR);
/*
* kthreadd (or new kernel thread) will call complete()
* shortly.
*/
wait_for_completion(&done);
}
task = create->result;
if (!IS_ERR(task)) {
static const struct sched_param param = { .sched_priority = 0 };
va_list args;
va_start(args, namefmt);
vsnprintf(task->comm, sizeof(task->comm), namefmt, args);
va_end(args);
/*
* root may have changed our (kthreadd's) priority or CPU mask.
* The kernel thread should not inherit these properties.
*/
sched_setscheduler_nocheck(task, SCHED_NORMAL, ¶m);
set_cpus_allowed_ptr(task, cpu_all_mask);
}
kfree(create);
return task;
}
kthread_create_on_node()
函数的本质则是创建kthread_create_info
结构,并将其添加到kthread_create_list
全局链表中(/kernel/kthread.h):
struct kthread_create_info
{
/* 从kthreadd传递给kthread()的信息 */
int (*threadfn)(void *data);
void *data;
int node;
/* 从kthreadd返回给kthread_create()的结果 */
struct task_struct *result;
struct completion *done;
struct list_head list;
};