Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

5363
1898
209234


[리눅스커널] 워크큐: 워크 실행의 출발점인 worker_thread() 함수 분석 7. 워크큐(Workqueue)

워커 스레드가 깨어나면 스레드 핸들러인 worker_thread() 함수가 실행됩니다.
worker_thread() 함수는 워커 스레드를 종료하거나 생성하는 기능을 수행하지만 핵심 동작은 워크를 실행하는 것입니다.

이번 소절에서는 worker_thread() 함수에서 워크를 실행하는 동작에 초점을 맞춰 분석하겠습니다.

worker_thread() 함수 세부 동작은 다음 워커 스레드 절에서 상세히 다룹니다.

다음은 worker_thread() 함수에서 워크 실행에 관련된 코드 조각입니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/workqueue.c]
01 static int worker_thread(void *__worker)
02 {
03 struct worker *worker = __worker;
04 struct worker_pool *pool = worker->pool;
...
05 do {
06 struct work_struct *work =
07 list_first_entry(&pool->worklist,
08 struct work_struct, entry);
09
10 pool->watchdog_ts = jiffies;
12
13 if (likely(!(*work_data_bits(work) & WORK_STRUCT_LINKED))) {
14 /* optimization path, not strictly necessary */
15 process_one_work(worker, work);
16 if (unlikely(!list_empty(&worker->scheduled)))
17 process_scheduled_works(worker);
18 } else {
19 move_linked_works(work, &worker->scheduled, NULL);
20 process_scheduled_works(worker);
21 }
22 } while (keep_working(pool));

먼저 03 번째 줄 코드를 보겠습니다.
03 struct worker *worker = __worker;

워커 스래드 핸들러 함수 매개 인자인 __worker를 struct worker 구조체로 캐스팅해 worker에 저장합니다.

다음 04번째 줄 코드를 보겠습니다.
04 struct worker_pool *pool = worker->pool;

워커 자료 구조인 struct worker 구조체 pool 필드를 지역 변수인 pool에 저장합니다.
구조체 필드 이름과 지역 변수 이름이 같습니다. 코드를 읽다가 헷갈릴수 있으니 주의합시다. 

다음 05~22번째 줄 코드는 do~while 문입니다. 소스 코드 분석 전 do~while의 조건을 살펴보겠습니다. 22번째 줄 코드와 같이 keep_working() 함수가 true를 반환하면 do~while문을 계속 실행합니다.

keep_working() 함수를 보면 등록된 워크가 있으면 true 아니면 false를 반환합니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/workqueue.c]
static bool keep_working(struct worker_pool *pool)
{
return !list_empty(&pool->worklist) &&
atomic_read(&pool->nr_running) <= 1;
}

list_empty() 함수는 리눅스 커널에서 연결 리스트가 비어있는지 체크하는 기능입니다. 만약 연결 리스트가 비어 있으면 true을 반환합니다. 위 코드에서 list_empty() 함수 앞에 ! 기호가 있으니 연결 리스트가 비어 있지 않으면 true를 반환합니다.

위 코드를 워크큐 관점에서 해석하면 다음과 같습니다.

   "워커풀에 워크가 큐잉됐으면 do_while 문 내 소스 코드를 실행한다."

do~while 문 실행 조건을 봤으니 do~while 문 내 소스 코드를 보겠습니다.
06 struct work_struct *work =
07 list_first_entry(&pool->worklist,
08 struct work_struct, entry);

06~08번째 줄 코드는 워커풀 연결 리스트에서 워크 자료 구조인 struct work_struct 주소를 읽는 동작입니다. 워커풀 연결 리스트 필드인 worklist에 접근해 struct work_struct 구조체 주소를 work에 저장합니다.

이해를 위해 워크큐 관점에서 위 코드는 다음과 같이 해석할 수 있습니다. 

   "워커풀에 큐잉된 워크 연결 리스트를 가져와 워크 구조체를 알아낸다."

10 번째 줄 코드를 보겠습니다. 
10 pool->watchdog_ts = jiffies;

워커풀 시간 정보를 watchdog_ts 필드에 저장합니다. 

watchdog_ts 필드의 용도는 워크가 제대로 실행됐는지 점검하는 것입니다. CONFIG_WQ_WATCHDOG 컨피그가 켜져 있으면 watchdog_ts 필드에 워크를 큐잉한 마지막 시간 정보를 저장합니다. 만약 워크를 워크큐에 큐잉하고 난 후 30초 동안 워크를 실행하지 않으면 시스템에 문제가 있다고 보고 에러 로그를 출력합니다.

드디어 워크를 실행하는 process_one_work() 함수를 호출하는 단계까지 왔습니다.
13 if (likely(!(*work_data_bits(work) & WORK_STRUCT_LINKED))) {
14 /* optimization path, not strictly necessary */
15 process_one_work(worker, work);

13 번째 줄 조건문은 struct work_struct 구조체 data 플래그가 WORK_STRUCT_LINKED가 아닌지 점검합니다. 일반적인 상황에서 data 플래그가 WORK_STRUCT_LINKED가 아니므로 15 번째 줄 코드를 실행합니다.

워크 실행 관점으로 worker_thread() 함수 코드를 분석한 내용을 정리하면 다음과 같습니다.
- 워커풀에 워크가 큐잉됐는지 체크한다.
- 워커풀에 큐잉된 워크 연결 리스트를 가져와 워크 구조체를 알아낸다.
- process_one_work() 함수해 워크를 실행한다.

worker_thread() 함수는 워커풀에 등록된 워크를 모두 로딩해 process_one_work() 함수를 호출합니다. 이어서 워크 핸들러를 실행하는 process_one_work() 함수를 분석하겠습니다.

"혹시 궁금하신 점이 있으면 댓글 달아 주세요. 아는 한 성실히 답변 올려드리겠습니다."



덧글

댓글 입력 영역