Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

110187
803
94439


[리눅스커널] 워크큐: 7가지 워크큐에 대한 소개 8장. 워크큐

workqueue_init_early() 함수는 리눅스 커널에서 지원하는 7가지 워크큐를 생성합니다.
workqueue_init_early() 함수를 분석하면서 워크큐 종류에 대해 알아보겠습니다. 
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/workqueue.c]
1 int __init workqueue_init_early(void)
2 {
3 int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL };
4 int i, cpu;
...
5 system_wq = alloc_workqueue("events", 0, 0);
6 system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);
7 system_long_wq = alloc_workqueue("events_long", 0, 0);
8 system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND,
9     WQ_UNBOUND_MAX_ACTIVE);
10 system_freezable_wq = alloc_workqueue("events_freezable",
11       WQ_FREEZABLE, 0);
12 system_power_efficient_wq = alloc_workqueue("events_power_efficient",
13       WQ_POWER_EFFICIENT, 0);
14 system_freezable_power_efficient_wq = 
15 alloc_workqueue("events_freezable_power_efficient",
16       WQ_FREEZABLE | WQ_POWER_EFFICIENT,
17       0);
18 BUG_ON(!system_wq || !system_highpri_wq || !system_long_wq ||
19        !system_unbound_wq || !system_freezable_wq ||
20        !system_power_efficient_wq ||
21        !system_freezable_power_efficient_wq);

workqueue_init_early() 함수의 핵심 동작은 다음과 같습니다.
7가지 워크큐 생성
워크큐가 제대로 생성됐는지 점검

5~17번째 줄 코드는 워크큐를 생성하고 18~21번째 줄 코드는 워크큐가 제대로 생성됐는지 점검하는 동작입니다.

먼저 5번째 줄 코드를 보겠습니다.
5 system_wq = alloc_workqueue("events", 0, 0);

“event” 이름으로 워크큐를 생성해서 system_wq이란 전역 변수에 저장합니다. 

    system_wq는 보통 시스템 워크큐라고도 부릅니다.  

다음은 6번째 줄 코드입니다.
6 system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);

system_highpri_wq 워크큐를 생성하는 코드입니다. system_highpri_wq 워크큐는 시스템 워크큐에서 쓰는 워커 스레드보다 우선순위가 높은 워커 스레드가 처리합니다. struct workqueue_struct 구조체 flags 필드는 WQ_HIGHPRI로 저장합니다.


WQ_HIGHPRI 플래그를 설정하면 후속 함수에서 어떤 흐름으로 동작하는지 알아봅시다.
__alloc_workqueue_key() 함수에서 alloc_and_link_pwqs() 함수를 실행합니다. 이 때 struct workqueue_struct 구조체flags 필드가 WQ_HIGHPR 플래그이면 cpu_worker_pools[1]에 위치한 워커풀를 해당 워크큐에 할당합니다. 해당 코드는 다음과 같습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/workqueue.c]
01 static int alloc_and_link_pwqs(struct workqueue_struct *wq)
02 {
03 bool highpri = wq->flags & WQ_HIGHPRI;
04 int cpu, ret;
05
...
06 for_each_possible_cpu(cpu) {
07 struct pool_workqueue *pwq =
08 per_cpu_ptr(wq->cpu_pwqs, cpu);
09 struct worker_pool *cpu_pools =
10 per_cpu(cpu_worker_pools, cpu);
11
12 init_pwq(pwq, wq, &cpu_pools[highpri]);

3번째 줄 코드에서 워크큐 wq->flags 필드와 WQ_HIGHPRI enum 매크로와 AND 비트 연산을 합니다. WQ_HIGHPRI로 설정된 워크큐인 경우 highpri 지역 변수는 1이 됩니다.

9~10번째 줄 코드는 percpu 타입 변수인 cpu_worker_pools 전역 변수에 접근해서 각 CPU 별 struct worker_pool 구조체 주소를 읽습니다. 우선순위를 높혀서 처리하는 워커풀은 per-cpu 타입 변수인 cpu_worker_pools[1]에 있습니다.

12번째 줄 코드는 init_pwq() 함수를 호출해서 워커풀을 설정합니다.


다음 7번째 줄 코드를 보겠습니다.
7 system_long_wq = alloc_workqueue("events_long", 0, 0);

system_long_wq이라는 워크큐를 생성합니다. system_wq과 유사한 환경에서 조금 더 오래 걸리는 작업에 사용합니다.

다음 8 번째 줄 코드입니다.
8 system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND,
9     WQ_UNBOUND_MAX_ACTIVE);

system_unbound_wq이라는 워크큐를 생성합니다. system_unbound_wq 워크큐는 per-cpu 타입 워커를 쓰지 않고 wq->numa_pwq_tbl[node]에 지정된 워크풀을 씁니다. 시스템 워크큐보다 빨리 실행합니다.

워크큐 커널 함수에서 WQ_UNBOUND 플래그로 struct workqueue_struct 구조체 flags 필드와 AND 비트 연산한 결과로 “events_unbound” 워크큐 여부를 판단합니다.

이어서 10번째 코드를 분석하겠습니다. 
10 system_freezable_wq = alloc_workqueue("events_freezable",
11       WQ_FREEZABLE, 0);

system_freezable_wq 워크큐를 생성합니다. "events_freezable" 이란 이름을 지정하며 struct workqueue_struct 구조체 flags 필드에 WQ_FREEZABLE을 저장합니다. freeze 가능한 프로세스를 처리할 때 freeze_workqueues_begin() 함수에서 실행합니다.

마지막 12~17번째 줄 코드를 보겠습니다. 
12 system_power_efficient_wq = alloc_workqueue("events_power_efficient",
13       WQ_POWER_EFFICIENT, 0);
14 system_freezable_power_efficient_wq = 
15 alloc_workqueue("events_freezable_power_efficient",
16       WQ_FREEZABLE | WQ_POWER_EFFICIENT,
17       0);

system_power_efficient_wq와 system_freezable_power_efficient_wq 워크큐를 생성합니다. 절전 목적으로 쓰는 워크큐입니다.

다음과 같이 CONFIG_WQ_POWER_EFFICIENT_DEFAULT 컨피그를 키면 활성화되는 wq_power_efficient 전역 변수와 함께 사용합니다.
static bool wq_power_efficient = IS_ENABLED(CONFIG_WQ_POWER_EFFICIENT_DEFAULT);

참고로 라즈비안에서 CONFIG_WQ_POWER_EFFICIENT_DEFAULT 컨피그는 꺼져 있습니다.

마지막 18번째 줄 코드를 보겠습니다.
18 BUG_ON(!system_wq || !system_highpri_wq || !system_long_wq ||
19        !system_unbound_wq || !system_freezable_wq ||
20        !system_power_efficient_wq ||
21        !system_freezable_power_efficient_wq);

이제까지 설명한 7가지 워크큐를 생성했는지 점검합니다. 각각 워크큐 전역 변수가 NULL이 아닌지 OR 비트 연산으로 확인한 후 하나의 워크큐라도 NULL이면 다음과 같이 처리합니다. 

    커널 크래시를 유발합니다. 

커널 내부에서 기본으로 7가지 워크큐를 지원하며 커널 내부 서브시스템에서도 워크큐를 사용해 커널을 관리합니다. 한 가지 워크큐라도 제대로 생성하지 못하면 커널 기본 동작을 장담할 수 없기 때문에 제대로 생성됐는지 체크하는 것입니다.


그러면 리눅스 커널에서는 워크큐 개수가 7개로 정해져 있을까요? 그렇지는 않습니다. 디바이스 드라이버에서 워크큐를 생성해서 드라이버 흐름을 제어할 수 있습니다. 위에서 설정한 7개 워크큐는 기본으로 리눅스 커널에서 생성하는 것입니다.


워크큐에 대한 내용을 알아 봤으니 다음 절에서 워크에 대해 살펴보겠습니다.


핑백

덧글

댓글 입력 영역