Kthreadd()

 

이 페이지에서는 kthread_create() 함수와 kthreadd() 함수에 대해 분석해본다.

 

kthreadd

생성 요청한 커널 스레드를 fork 한다.

 

사용 APIs

  • kthread_create()
  • kthread_run()
  • kthread_bind()
  • kthread_stop()
  • kthread_should_stop()
  • kthread_data()

 

 

 

kthread_create()

include/linux/kthread.h

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

threadfn 함수를 엔트리 진입점으로 하여 커널 스레드를 만든다. 곧 바로 동작이 필요한 경우 후속 호출로 wake_up_process() 함수를 사용한다.

 

kthread_create_on_node()

kernel/kthread.c

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

threadfn 함수를 엔트리 진입점으로 하여 지정된 cpu에 커널 스레드를 만든다. 만들어진 스레드는 곧 바로 park 상태로 바꾸어 둔다.

 

kthread_create_on_node()

kernel/kthread.c

/**
 * kthread_create_on_node - create a kthread.
 * @threadfn: the function to run until signal_pending(current).
 * @data: data ptr for @threadfn.
 * @node: memory node number.
 * @namefmt: printf-style name for the thread.
 *
 * Description: This helper function creates and names a kernel
 * thread.  The thread will be stopped: use wake_up_process() to start
 * it.  See also kthread_run().
 *
 * If thread is going to be bound on a particular cpu, give its node
 * in @node, to get NUMA affinity for kthread stack, or else give -1.
 * When woken, the thread will run @threadfn() with @data as its
 * argument. @threadfn() can either call do_exit() directly if it is a
 * standalone thread for which no one will call kthread_stop(), or
 * return when 'kthread_should_stop()' is true (which means
 * kthread_stop() has been called).  The return value should be zero
 * or a negative error number; it will be passed to kthread_stop().
 *
 * Returns a task_struct or ERR_PTR(-ENOMEM) or ERR_PTR(-EINTR).
 */
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);
  • 코드 라인 30~42에서 kthread_create_info 구조체를 할당받고 요청 인수 내용으로 채운 후 kthread_create_list에 추가한다.
  • 코드 라인 44에서 kthread_create_list에 담은 내용으로 커널 스레드를 생성시키는 “kthreadd” 라는 이름의 최상위 커널 스레드를 깨운다.

 

        /*
         * 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);
  • 코드 라인 6~19에서 “kthreadd” 커널 태스크를 통해 사용자가 요청한 커널 스레드가 생성될 때까지 대기한다. 만일 낮은 확률로 에러(-ERESTARTSYS)가 발생하는 경우에는 sigkill 시그널이 와서 종료처리를 하는 경우에는 -EINTR 결과로 함수를 빠져나가고 그렇지 않은 경우 다시 한 번 기다린다.
  • 코드 라인 20~36에서 생성이 완료된 경우 생성된 커널 태스크를 반환한다. 커널 태스크가 정상적으로 생성된 경우 normal 태스크로 스케줄러 속성을 변경하고 모든 cpu에서 동작하도록 설정한다.
    • 커널 스레드를 normal 정책을 사용하는 cfs 태스크로 만들 때 sched_priority는 항상 0으로 세팅하여야 한다.

 

kthreadd()

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

커널 스레드를 생성시킬 때 사용하는 최상위 커널 스레드이다. 무한 루프를 돌며 커널 스레드 생성 요청이 있을 때 마다 이를 수행한다.

  • 코드 라인 6~11에서 태스크의 이름을 “kthreadd”로 설정하고, 시그널을 받을 수 없게 한다. 이 커널 스레드는 모든 cpu와 모든 메모리 노드에서 동작할 수 있게 설정한다. 또한 freeze되지 않게 한다.
  • 코드 라인 13~17에서 무한 루프를 돌며 thread_create_list에 생성할 내용이 없으면 interruptible 상태로 변경하고 sleep 한다. 깨어나면 runnng 상태로 변경한다.
  • 코드 라인 19~32에서 kthread_create_list에 등록된 내용으로 커널 스레드를 생성하면서 순회한다.

 

create_kthread()

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;
                }
                create->result = ERR_PTR(pid);
                complete(done);
        }
}

커널 스레드를 생성한다. 만일 실패하는 경우 에러 결과로 completion 처리를 한다.

  • kthread() 함수를 fork하고 argument로 요청한 함수 포인터를 넘겨준다.
  • kthread() 함수 내부에서 현재 fork된 태스크를 sleep 시킨다. 깨어날 때 곧바로 인수로 넘겨 받은 함수를 호출한다.

 

kernel_thread()

kernel/fork.c

/*                     
 * Create a kernel thread.
 */
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);
}

fn 함수를 엔트리 시작으로 fork 하여 커널 스레드를 생성한다.

  • fn 함수들:
    • kthread()
    • kernel_init()
    • kthreadd()
    • wait_for_helper()
    • ____call_usermodehelper()

 

kthread()

kernel/kthread.c

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;
        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 */
        __set_current_state(TASK_UNINTERRUPTIBLE);
        create->result = current;
        complete(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 */
        do_exit(ret);
}

fork 되어 실행된 이 함수는 스레드 생성되었음을 알리는 completion 처리를 한 후 sleep 한다. 이 후 wakeup 요청을 받으면 인수로 받은 함수를 호출한다.

  • 코드 라인 18~22에서 SIGKILL에 의해 스레드 생성 요청이 없어진 경우 현재 스레드를 종료시킨다.
  • 코드 라인 24~27에서 스레드 생성 요청에 따라 wait_for_completion() 함수에서 대기중인 스레드를 계속 진행할 수 있도록 complete() 함수를 사용한다. 그런 후 sleep 한다.
  • 코드 라인 31~34에서 스레드 종료 요청을 받은 경우가 아니면 인수로 받은 함수를 호출한다.
  • 코드 라인 36에서 수행이 완료된 경우 스레드를 종료시킨다.

 

참고

댓글 남기기