6 min read

linux进程管理-kthread

linux进程管理-kthread
Photo by Mohammad Rahmani / Unsplash

0、背景

kthread是指代特殊的在内核态运行的线程,其创建、管理的逻辑与普通集成有所区别,但在调度上还是与其他进程或者线程是类似的。

1、创建

内核线程的创建主要是通过kthread_create,另外kthread_run也可创建线程,区别是在创建的同时还会直接运行这个线程。

可以看到kthread_create的实际原型是kthread_create_on_node

#define kthread_create(threadfn, data, namefmt, arg...) \
	kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)

最终会调用到__kthread_create_on_node进行创建,而实际上创建kthread的工作实际上是一个由kthreadd_task线程完成

struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
						    void *data, int node,
						    const char namefmt[],
						    va_list args)
{
	DECLARE_COMPLETION_ONSTACK(done);
	struct task_struct *task;
	struct kthread_create_info *create = kmalloc(sizeof(*create), 
						     GFP_KERNEL); // 创建一个ktherad_create数据上下文

	if (!create)
		return ERR_PTR(-ENOMEM);
  /*填充create结构体*/
	create->threadfn = threadfn; // kthread的实际逻辑函数指针
	......
	spin_lock(&kthread_create_lock);
	list_add_tail(&create->list, &kthread_create_list); // 把创建好的create结构体加入到链表上
	spin_unlock(&kthread_create_lock);

	wake_up_process(kthreadd_task); // 唤醒创建kthread的线程
	......
	if (unlikely(wait_for_completion_killable(&done))) { // 使用wait_for循环等待创建工作结束
		......
		wait_for_completion(&done);
	}
	task = create->result;
free_create:
	kfree(create);
	return task;
}

而这个进程是在init的创建流程中被创建的,实际是真正执行的原型是kthreadd

	noinline void __ref __noreturn rest_init(void)
{
  ......
	numa_default_policy();
	pid = kernel_thread(kthreadd, NULL, NULL, CLONE_FS | CLONE_FILES);
	rcu_read_lock();
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	rcu_read_unlock();
  ......
}

我们可以看到kthreadd的主要工作就是遍历kthread_create_list链表创建线程。

int kthreadd(void *unused)
{
	......
	set_cpus_allowed_ptr(tsk, housekeeping_cpumask(HK_TYPE_KTHREAD)); // 设置cpu亲核性
	set_mems_allowed(node_states[N_MEMORY]); // 设置内存节点
	......
	for (;;) {
		set_current_state(TASK_INTERRUPTIBLE); // 先把自身设置成休眠
		if (list_empty(&kthread_create_list)) // 如果没有需要创建的线程,就让出调度
			schedule();
		__set_current_state(TASK_RUNNING); // 有线程需要创建则重新设置成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;
}

创建的实际逻辑比较简单,就是通过fork.c里通过kernel_thread创建线程。

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, create->full_name,
			    CLONE_FS | CLONE_FILES | SIGCHLD); // 通过fork.c中的kernel_thread创建线程
	if (pid < 0) {
		/* Release the structure when caller killed by a fatal signal. */
		struct completion *done = xchg(&create->done, NULL);

		kfree(create->full_name);
		if (!done) {
			kfree(create);
			return;
		}
		create->result = ERR_PTR(pid);
		complete(done); // 填充create->done结构体
	}
}

2、kthread()

2.1-int kthread(void *_create)

kthread()函数是由kernel_thread在创建线程时绑定的,内核线程创建完会一直则执行ret = threadfn(data);

static int kthread(void *_create)
{
	/* Copy data: it's on kthread's stack */
	struct kthread_create_info *create = _create;
	int (*threadfn)(void *data) = create->threadfn; // kthread实际业务逻辑
	void *data = create->data;
	struct kthread self; // kthread特别的数据结构
	int ret;
  
  self = to_kthread(current); // 填充kthread结构体

	/* OK, tell user we're spawned, wait for stop or wakeup */
	__set_current_state(TASK_UNINTERRUPTIBLE); // 先休眠
	......
	if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
		__kthread_parkme(&self);
		ret = threadfn(data); // 开始执行具体ktread逻辑
	}
	/* we can't just return, we must preserve "self" on stack */
	do_exit(ret);
}

2.2-struct kthread

struct kthread是一个内核线程专有的数据结果,这里会保存内核线程需要操作的基本数据结构

struct kthread {
	unsigned long flags; // 线程的状态(如运行、休眠等)
	unsigned int cpu; // 线程绑定的cpu
	int result; // 线程的return值
	int (*threadfn)(void *); // 线程实际执行逻辑函数地址
	void *data; // 存储或传递给线程函数的数据
	struct completion parked; // 用来记录线程暂停的结构体
	struct completion exited; // 用来记录线程退出的结构体
#ifdef CONFIG_BLK_CGROUP
	struct cgroup_subsys_state *blkcg_css; // 指向cgroup状态的指针,用于块设备控制组相关的功
#endif
	/* To store the full name if task comm is truncated. */
	char *full_name; // 线程名称
};

3、唤醒&退出

3.1-唤醒

由于很多内核线程都是阶段运行的,所以部分内核线程会主动进入休眠状态,然后等待其他线程唤醒。

唤醒的方式比较常见,就是通过wake_up_process来唤醒进程。

3.2-退出

3.2.1-正常退出

如上述分析,kthread正常退出后会执行kthread_exit(ret);

static int kthread(void *_create)
{
	......
	/* OK, tell user we're spawned, wait for stop or wakeup */
	__set_current_state(TASK_UNINTERRUPTIBLE);
	create->result = current;
	complete(&create->done);
	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 */
	kthread_exit(ret);
}

kthread_exit中则会填充done相关的结构体

void __noreturn kthread_exit(long result)
{
	struct kthread *kthread = to_kthread(current);
	kthread->result = result;
	do_exit(0);
}

kthread->exited的结构体最终是由mm_release完成的,调用流程为:do_exit->exit_mm->exit_mm_release->mm_release,其中操作如下:

static void mm_release(struct task_struct *tsk, struct mm_struct *mm)
{
	uprobe_free_utask(tsk);
	......
	/*
	 * All done, finally we can wake up parent and return this mm to him.
	 * Also kthread_stop() uses this completion for synchronization.
	 */
	if (tsk->vfork_done)
		complete_vfork_done(tsk); // 通过操作vfork_done地址来填充结构体
}

上边的vfork_done是在初始化过程中,指向exited结构体的

bool set_kthread_struct(struct task_struct *p)
{
	......
	init_completion(&kthread->exited);
	init_completion(&kthread->parked);
	p->vfork_done = &kthread->exited;
	......
}

3.2.2-kthread_stop

另外,我们也可以主动调用kthread_stop()来终止一个循环线程,但是同时也要再线程的循环中增加kthread_should_stop()的检查。

int kthread_stop(struct task_struct *k)
{
	struct kthread *kthread;
	int ret;

	trace_sched_kthread_stop(k);

	get_task_struct(k);
	kthread = to_kthread(k); // 获取线程的kthread结构体
	set_bit(KTHREAD_SHOULD_STOP, &kthread->flags); // 增加should_stop flag
	kthread_unpark(k); // 取消park flag
	set_tsk_thread_flag(k, TIF_NOTIFY_SIGNAL); // 提前取消signal peding
	wake_up_process(k); // 唤醒线程
	wait_for_completion(&kthread->exited); // 等待线程完成
	ret = kthread->result; // 把return存入结构体中
	put_task_struct(k); // 解引用

	trace_sched_kthread_stop_ret(ret);
	return ret;
}

其中比较关键的操作是wait_for_completion,他会执行一个没有timeout的循环等待,等待进程退出。

void __sched wait_for_completion(struct completion *x)
{
	wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE);
}
EXPORT_SYMBOL(wait_for_completion);

static long __sched
wait_for_common(struct completion *x, long timeout, int state)
{
	return __wait_for_common(x, schedule_timeout, timeout, state);
}

static inline long __sched
do_wait_for_common(struct completion *x,
		   long (*action)(long), long timeout, int state)
{
	if (!x->done) {
		DECLARE_SWAITQUEUE(wait);

		do {
			if (signal_pending_state(state, current)) { // 有信号排队的时候退出循环
				timeout = -ERESTARTSYS;
				break;
			}
			__prepare_to_swait(&x->wait, &wait);
			__set_current_state(state); // 把本线程标记为不可中断的状态,防止误调度
			raw_spin_unlock_irq(&x->wait.lock);
			timeout = action(timeout); // 不会重新唤醒,应为timeout是MAX_SCHEDULE_TIMEOUT
			raw_spin_lock_irq(&x->wait.lock);
		} while (!x->done && timeout); // 等待kthread的done标记为置1,由do_exit完成
		__finish_swait(&x->wait, &wait);
		if (!x->done)
			return timeout;
	}
	if (x->done != UINT_MAX)
		x->done--;
	return timeout ?: 1;
}