1. 程式人生 > >Linux核心執行緒(kthread)建立過程

Linux核心執行緒(kthread)建立過程

我們在核心中建立並執行核心執行緒,直接呼叫kthread_run巨集就可以實現。其原型為:

/**
 * kthread_run - create and wake a thread.
 * @threadfn: the function to run until signal_pending(current).
 * @data: data ptr for @threadfn.
 * @namefmt: printf-style name for the thread.
 *
 * Description: Convenient wrapper for kthread_create() followed by
 * wake_up_process().  Returns the kthread or ERR_PTR(-ENOMEM).
 */
#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;								   \
})

可以看到,kthread_run首先使用kthread_create建立核心執行緒,然後呼叫wake_up_process喚醒建立的執行緒。

kthread_run是一個巨集定義,先是建立一個task_struct執行緒結構,然後呼叫wake_up喚醒。

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

核心建立執行緒,實際呼叫到了kthread_create_on_node函式。

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);
	list_add_tail(&create->list, &kthread_create_list);
	spin_unlock(&kthread_create_lock);

	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;
}
EXPORT_SYMBOL(kthread_create_on_node);

從上面程式碼可以看到,實際的執行緒建立並不是由kthread_create_on_node完成,而是通過一個kthreadd_task執行緒完成。通過填充kthread_create_info結構,並加入到kthread_create_list,然後kthreadd_task從kthread_create_list取出需要建立的執行緒資訊,來建立核心執行緒。

kthreadd_task執行緒,在核心啟動的早期便建立。

@/kernel/init/main.c
static __initdata DECLARE_COMPLETION(kthreadd_done);
static noinline void __init_refok rest_init(void)
{
	int pid;

	rcu_scheduler_starting();
	smpboot_thread_init();
	/*
	 * We need to spawn init first so that it obtains pid 1, however
	 * the init task will end up wanting to create kthreads, which, if
	 * we schedule it before we create kthreadd, will OOPS.
	 */
	kernel_thread(kernel_init, NULL, CLONE_FS);
	numa_default_policy();
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
	rcu_read_lock();
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	rcu_read_unlock();
	complete(&kthreadd_done);

	/*
	 * The boot idle thread must execute schedule()
	 * at least once to get things moving:
	 */
	init_idle_bootup_task(current);
	schedule_preempt_disabled();
	/* Call into cpu_idle with preempt disabled */
	cpu_startup_entry(CPUHP_ONLINE);
}

上面kernel_thread()函式呼叫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);
}

這裡建立了兩個執行緒,一個kernel_init,一個是kthreadadd。

此處我們先看kthreadadd執行緒,對應的執行緒函式為kthreadd。

@/kernel/kernel/kthread.c
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_MEMORY]);

	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;
}

上面這段程式碼做的事情就是:

  1. 如果kthread_create_list為空,則手動進行排程,放棄CPU時間。
  2. 如果kthread_create_list不為空,則從該連結串列取出每個kthread_create_info結構。
  3. 使用取出的個kthread_create_info結構,呼叫create_kthread()函式建立執行緒。
@/kernel/kernel/kthread.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;
		}
		// 將task_struct返回
		create->result = ERR_PTR(pid);
		// 喚醒建立執行緒的執行緒
		complete(done);
	}
}

這裡用呼叫kernel_thread建立一個執行緒函式為kthread的執行緒,在該執行緒中執行我們的執行緒函式。因此對於所以執行緒的執行,都是從kthread開始的。

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;
	
	__set_current_state(TASK_UNINTERRUPTIBLE);
	create->result = current;
	complete(done);
	// 建立執行緒後,排程出去等待喚醒
	schedule();
	// 不是stop狀態,呼叫我們的執行緒函式
	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);
}

核心建立執行緒整體過程就是:

  1. 建立kthread_create_info結構,賦值執行緒的操作函式,資料等;
  2. 將執行緒的kthread_create_info結構新增到kthread_create_list連結串列,並喚醒kthreadd執行緒。
  3. kthreadd執行緒執行緒將從kthread_create_list連結串列取出每一個kthread_create_info結構,並呼叫create_kthread()函式建立一個函式為kthread的執行緒,在kthread執行緒中將執行我們需要的執行緒的函式。