【linux kernel】linux内核的kthreadd线程

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, &param);
		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;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iriczhao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值