Linux Kernel(4.14) Hacks

rousalome.egloos.com

포토로그 Kernel Crash




[리눅스커널] 워크큐(Workqueue) - worker_thread() 함수 분석(1) [라즈베리파이] 워크큐


워크는 워커 쓰레드가 실행합니다. 워커 쓰레드를 관리하는 자료구조는 struct worker 구조체이며 이를 워커라고 부릅니다. 이전까지 자료구조 중심으로 워크를 분석했는데 이번에는 워커 쓰레드가 쓰레드 관점으로 어떻게 실행하는지 알아봅니다. 

다음은 워커 자료구조인 struct worker 구조체 선언부입니다.
[https://elixir.bootlin.com/linux/v4.14.43/source/kernel/workqueue_internal.h#L24]
1 struct worker {
2 union {
3 struct list_head entry; 
4 struct hlist_node hentry; 
5 };
6 struct work_struct *current_work;
7 work_func_t current_func;
8 struct pool_workqueue *current_pwq; 
9 bool desc_valid;
10 struct list_head scheduled;
11
12 struct task_struct *task;
13 struct worker_pool *pool;
14
15 struct list_head node;
16 unsigned long last_active;
17 unsigned int flags;
18 int id;
19
20 char desc[WORKER_DESC_LEN];
21
22 struct workqueue_struct *rescue_wq;
23};

각 멤버의 의미를 살펴봅시다.

*current_work;
struct work_struct 구조체로 현재 실행하려는 워크를 가르키는 멤버입니다.

current_func;
실행하려는 워크 핸들러 주소를 저장하는 멤버입니다.


워크 구조체와 워크 핸들러는 다음과 같이 process_one_work() 함수에서 위 멤버에 저장합니다.
static void process_one_work(struct worker *worker, struct work_struct *work)
{
...
worker->current_work = work;
worker->current_func = work->func;
worker->current_pwq = pwq;

struct task_struct *task;
워커 쓰레드의 태스크 디스크립터 주소입니다.

struct worker_pool *pool;
워커가 포함된 워커 풀 주소를 저장하는 멤버입니다.

struct list_head node;
워커풀에 등록된 링크드 리스트입니다.

워커를 표현하는 구조체인 struct worker 를 살펴봤으니 워커 쓰레드 동작을 점검하겠습니다. 

워커 쓰레드가 어떤 일을 하는지 알려면 어느 코드를 분석해야 할까요? 커널 쓰레드 동작을 알아보려면 쓰레드 핸들 함수를 분석해야 합니다. 커널 쓰레드가 단계별로 어떤 동작을 하는지 쓰레드 핸들 함수에서 구현했기 때문입니다.

워커 쓰레드도 커널 쓰레드 종류 중 일부분이니 워커 쓰레드 동작을 점검하려면 워커 쓰레드 핸들 함수인 worker_thread()를 분석해야 합니다. worker_thread() 함수를 살펴보기 전 이 쓰레드 핸들 함수를 등록하는 코드를 보겠습니다.
[https://elixir.bootlin.com/linux/v4.14.43/source/kernel/workqueue.c#L2190]
1 static struct worker *create_worker(struct worker_pool *pool)
2 {
3 struct worker *worker = NULL;
...
4 worker->task = kthread_create_on_node(worker_thread, worker, pool->node,
5       "kworker/%s", id_buf);

kthread_create_on_node() 함수는 커널 쓰레드를 생성할 때 호출합니다.
중요한 인자만 살펴보면, 첫 번째 인자로 쓰레드 핸들, 두 번째 인자로 쓰레드 핸들 매개 변수,
네 번째 인자로 쓰레드 이름을 지정합니다.

4번째 줄 코드를 보면 worker_thread() 함수를 쓰레드 핸들, 두 번째 인자로 쓰레드 핸들 매개 변수, 그리고 네 번째 인자로 워커 쓰레드 이름을 지정합니다.

이번에는 워커 쓰레드 핸들 함수 선언부를 살펴봅니다.
static int worker_thread(void *__worker);

인자
인자는 *__worker인데 워커 쓰레드를 생성할 때 전달했던 struct worker 구조체 주소입니다.
워커를 처리하는 핸들 주소를 worker_thread()란 쓰레드 핸들 함수로 넘겨 받는 겁니다.

이 자료구조는 각각 워커를 표현하며 워커 쓰레드 디스크립터라고 봐도 됩니다.

반환값
함수 선언부를 보면 int 타입을 반환합니다. 워커 해제 요청을 받아 worker_thread() 함수를 호출할 때만 0을 반환하고 이외에는 워커 쓰레드가 동작하는 동안 반환값을 전달하지 않고 쓰레드 핸들 함수 내에서 계속 실행합니다.

다음 destroy_worker() 함수 코드에서 워커 해제 요청을 합니다.
static void destroy_worker(struct worker *worker)
{
...
list_del_init(&worker->entry);
worker->flags |= WORKER_DIE;
wake_up_process(worker->task);
}

worker_thread() 함수 인자와 반환값을 알아봤습니다.

쓰레드 실행 흐름은 알기 위해서 쓰레드 핸들 함수 구조를 파악해야 합니다. 워커 쓰레드를 생성할 때 쓰레드 핸들로 worker_thread() 함수를 등록했으니 이 함수 중심으로 코드 분석을 해 봅시다.

Reference(워크큐)
워크큐(Workqueue) Overview



핑백

덧글

댓글 입력 영역