이번에는 커널 스레드 생성 요청과 커널 스레드를 생성하는 코드 흐름을 살펴봅시다. 커널 스레드를 생성하려면 다음과 같이 kthread_create() 함수를 호출해야 합니다.
1 #define kthread_create(threadfn, data, namefmt, arg...) \
2 kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)
3
4 struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
5 void *data, int node,
6 const char namefmt[],
7 ...)
먼저 이 함수에 전달하는 인자부터 살펴봅시다.
threadfn
스레드 핸들 함수입니다. 커널 스레드 동작을 살펴보기 위해서 스레드 핸들 함수를 분석해야 합니다.
data
스레드 핸들 함수로 전달하는 매개 변수입니다. 주로 주소를 전달하며 스레드를 식별하는 구조체 주소를 전달합니다.
namefmt
커널 스레드 이름을 전달하는 역할을 수행합니다.
위와 같이 커널 스레드 생성 시 전달하는 인자에 대한 이해를 돕기 위해 다음 코드를 같이 봅시다.
[https://elixir.bootlin.com/linux/v4.14.70/source/drivers/vhost/vhost.c#L334]
1 long vhost_dev_set_owner(struct vhost_dev *dev)
2 {
3 struct task_struct *worker;
4 int err;
...
5 /* No owner, become one */
6 dev->mm = get_task_mm(current);
7 worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);
...
8 static int vhost_worker(void *data)
9 {
10 struct vhost_dev *dev = data;
11 struct vhost_work *work, *work_next;
12 struct llist_node *node;
13
14 for (;;) {
15 set_current_state(TASK_INTERRUPTIBLE);
16
...
17 llist_for_each_entry_safe(work, work_next, node, node) {
18 clear_bit(VHOST_WORK_QUEUED, &work->flags);
19 __set_current_state(TASK_RUNNING);
20 work->fn(work);
21 if (need_resched())
22 schedule();
23 }
24 }
7번 째 줄 코드를 보면 kthread_create() 함수 첫 번째 인자로 vhost_worker 스레드 핸들 함수이름을 지정합니다.
7 worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);
2번째 인자로 dev 변수를 지정하는데 1번째 줄 코드를 보면 dev가 어떤 구조체 인지 알 수 있습니다. struct vhost_dev 란 구조체 주소를 2번째 인자로 전달하는 겁니다.
이런 매개 변수와 같은 역할을 보통 디스크립터라고도 부릅니다. 3번째 인자로 "vhost-%d"를 전달하는데 이는 커널 스레드 이름을 지정하는 겁니다.
이번에는 스레드 핸들 함수로 전달되는 매개 변수를 점검합니다.
8번째 줄 코드를 보면 vhost_worker() 함수로 void 타입 포인터인 data를 전달합니다.
8 static int vhost_worker(void *data)
9 {
10 struct vhost_dev *dev = data;
커널 스레드를 생성할 때 두 번째 인자로 struct vhost_dev 구조체인 dev를 지정했습니다.
vhost_worker() 스레드 핸들 함수 인자로 매개 변수 인자를 전달하며 10번째 줄 코드와 같이 void 타입 data 포인터를 struct vhost_dev 구조체로 형 변환(캐스팅)합니다.
리눅스 커널에서는 이런 방식으로 스레드를 관리하는 유일한 구조체를 매개 변수로 전달합니다.
커널 스레드 생성은 2단계로 나눠서 분류할 수 있습니다.
1. 커널 스레드를 kthreadadd란 프로세스에 요청
2. kthreadadd 프로세스는 요청된 프로세스를 생성
먼저 커널 스레드 생성 요청 코드를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.70/source/kernel/kthread.c#L356]
1 #define kthread_create(threadfn, data, namefmt, arg...) \
2 kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)
3
4 struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
5 void *data, int node,
6 const char namefmt[],
7 ...)
8 {
9 struct task_struct *task;
10 va_list args;
11
12 va_start(args, namefmt);
13 task = __kthread_create_on_node(threadfn, data, node, namefmt, args);
14 va_end(args);
15
16 return task;
17}
kthread_create() 함수에 커널 스레드를 식별하는 인자와 함께 호출하면 kthread_create_on_node() 함수를 호출합니다. kthread_create_on_node() 함수는 가변 인자를 아규먼트로 받고 __kthread_create_on_node() 함수를 호출합니다.
커널 스레드 생성 요청은 __kthread_create_on_node() 함수에서 이루어집니다. 코드를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.70/source/kernel/kthread.c#L270]
1 struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
2 void *data, int node,
3 const char namefmt[],
4 va_list args)
5 {
6 DECLARE_COMPLETION_ONSTACK(done);
7 struct task_struct *task;
8 struct kthread_create_info *create = kmalloc(sizeof(*create),
9 GFP_KERNEL);
10
11 if (!create)
12 return ERR_PTR(-ENOMEM);
13 create->threadfn = threadfn;
14 create->data = data;
15 create->node = node;
16 create->done = &done;
17
18 spin_lock(&kthread_create_lock);
19 list_add_tail(&create->list, &kthread_create_list);
20 spin_unlock(&kthread_create_lock);
21
22 wake_up_process(kthreadd_task);
먼저 8~9번째 줄 코드를 봅시다.
8 struct kthread_create_info *create = kmalloc(sizeof(*create),
9 GFP_KERNEL);
struct kthread_create_info 구조체 메모리를 struct kthread_create_info 크기만큼 할당 받습니다.
다음 13~15번째 줄 코드를 봅시다.
13 create->threadfn = threadfn;
14 create->data = data;
15 create->node = node;
커널 스레드 핸들 함수와 매개 변수 및 노드를 struct kthread_create_info 멤버에 저장합니다.
다음 19번째 줄 코드를 봅시다.
19 list_add_tail(&create->list, &kthread_create_list);
커널 스레드 생성 요청을 관리하는 kthread_create_list이란 링크드 리스트에 &create->list를 추가합니다. kthreadadd란 프로세스는 커널 스레드 생성으로 깨어나면 kthread_create_list 링크드 리스트가 비어 있는지 확인하고 커널 스레드를 생성합니다.
__kthread_create_on_node() 함수 핵심 코드인 22번째 줄입니다.
22 wake_up_process(kthreadd_task);
kthreadd 프로세스의 태스크 디스크립터인 kthreadd_task를 인자로 wake_up_process() 함수를 호출해서 kthreadd 프로세스를 깨웁니다.
kthreadd 프로세스의 스레드 핸들러인 kthreadd() 함수 코드를 분석하겠습니다.
[https://elixir.bootlin.com/linux/v4.14.70/source/kernel/kthread.c#L270]
1 int kthreadd(void *unused)
2 {
3 struct task_struct *tsk = current;
4
5 /* Setup a clean context for our children to inherit. */
6 set_task_comm(tsk, "kthreadd");
7 ignore_signals(tsk);
8 set_cpus_allowed_ptr(tsk, cpu_all_mask);
9 set_mems_allowed(node_states[N_MEMORY]);
10
11 current->flags |= PF_NOFREEZE;
12 cgroup_init_kthreadd();
13
14 for (;;) {
15 set_current_state(TASK_INTERRUPTIBLE);
16 if (list_empty(&kthread_create_list))
17 schedule();
18 __set_current_state(TASK_RUNNING);
19
20 spin_lock(&kthread_create_lock);
21 while (!list_empty(&kthread_create_list)) {
22 struct kthread_create_info *create;
23
24 create = list_entry(kthread_create_list.next,
25 struct kthread_create_info, list);
26 list_del_init(&create->list);
27 spin_unlock(&kthread_create_lock);
28
29 create_kthread(create);
30
31 spin_lock(&kthread_create_lock);
32 }
33 spin_unlock(&kthread_create_lock);
34 }
wake_up_process(kthreadd_task); 함수를 호출해서 kthreadd 프로세스를 깨우면 실행하는 코드입니다.
16 if (list_empty(&kthread_create_list))
17 schedule();
18 __set_current_state(TASK_RUNNING);
커널 스레드 생성 요청이 없으면 kthread_create_list 이란 링크드 리스트가 비게 되고 휴면에 진입하다가 커널 스레드 생성 요청이 오면 18번째 줄 코드를 실행합니다.
21~32번째 줄 코드를 보기 전에 while 문 조건인 21번째 줄 코드를 봅시다.
kthread_create_list 이란 링크드 리스트가 비어있지 않으면 21~32번째 줄 코드를 실행해서 커널 스레드를 생성합니다.
21 while (!list_empty(&kthread_create_list)) {
22 struct kthread_create_info *create;
23
24 create = list_entry(kthread_create_list.next,
25 struct kthread_create_info, list);
26 list_del_init(&create->list);
27 spin_unlock(&kthread_create_lock);
28
29 create_kthread(create);
30
31 spin_lock(&kthread_create_lock);
32 }
24~25번째 줄 코드를 봅시다.
kthread_create_list.next 멤버를 통해 struct kthread_create_info 구조체 주소를 읽습니다.
29번째 줄 코드를 분석합시다.
29 create_kthread(create);
create_kthread() 함수를 호출해서 커널 스레드를 생성합니다.
이제 create_kthread() 함수를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.70/source/kernel/kthread.c#L247]
1 static void create_kthread(struct kthread_create_info *create)
2 {
3 int pid;
4
5 #ifdef CONFIG_NUMA
6 current->pref_node_fork = create->node;
7 #endif
8 /* We want our own signal handler (we take no signals by default). */
9 pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
9번째 줄 코드와 같이 kernel_thread() 함수를 호출하는데 CLONE_FS, CLONE_FILES, SIGCHLD 매크로를 아규먼트로 설정합니다.
kernel_thread() 함수를 분석하겠습니다.
1 pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
2 {
3 return _do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
4 (unsigned long)arg, NULL, NULL, 0);
5 }
3번째 줄 코드와 같이 _do_fork() 함수를 호출합니다.
_do_fork() 함수는 프로세스를 생성할 때 실행하는 함수로 알고 있습니다.
이로 커널 스레드도 프로세스의 한 종류인 것을 알 수 있습니다.
#Reference 시스템 콜
Reference(프로세스)
Reference(워크큐)
워크큐(Workqueue) Overview
최근 덧글