Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

110187
803
94439


[리눅스커널][워크큐] 딜레이 워크(delayed_work) 소개 8장. 워크큐

딜레이 워크 소개

워크큐는 대표적인 커널 후반부 처리 기법으로 후반부 처리 코드를 워크 핸들러에서 실행합니다. 동기적으로 처리 할 필요가 없는 코드를 워크 핸들러에 위치시켜 비동기적으로 커널 쓰레드 레벨에서 처리하는 겁니다. 이런 구조로 드라이버를 설계하면 다양한 디바이스 드라이버 시나리오에 맞게 코드를 구성할 수 있습니다. 워크에서 유연성을 추가한 기법이 딜레이 워크입니다.

딜레이 워크란 무엇인가? 

구체적으로 딜레이 워크란 무엇일까요? 딜레이 워크는 워크를 일정 시각(HZ 단위) 후에 지연시켜 실행합니다. 여기서 말하는 지연 시각은 디바이스 드라이버 시나리오에 맞게 변경할 수 있습니다.


라즈베리파이에선 진동수인 HZ는 100입니다.


예를 들어 다음 온도를 콘트롤하는 드라이버 시나리오를 생각해 봅시다.
1. 온도가 높아지면 인터럽트가 발생
2. 인터럽트 핸들러에서 워크를 워크큐에 큐잉
3. 워크 핸들러에서 온도 콘트롤 디바이스에 CPU 클락을 낮췄다는 응답을 온도 콘트롤 디바이스에 전달
  - 딜레이 워크를 워크큐에 큐잉함(지연 시각은 60밀리 초 설정)
  - 시나리오: 온도 콘트롤 디바이스는 50밀리 초 후에 응답을 전달함
4. 딜레이 워크가 실행하면서 온도 콘트롤 디바이스에서 전달한 응답을 확인

위 온도 콘트롤 디바이스가 새롭게 바뀌어 온도 디바이스 응답 시간이 50초에서 80밀리 초로 변경됐다고 가정하겠습니다. 이럴 때 디바이스 드라이버 코드를 많이 수정해야 할 수도 있습니다. 그런데 딜레이 워크로 후반부 처리 코드를 설계하면 딜레이 워크의 지연 시각을 90 밀리 초로 변경하면 됩니다. 


여기서 딜레이 워크 지연 시각을 80밀리 초가 아닌 90밀리 초로 주는 이유는 충분히 응답을 기다리는 시각을 고려해야 하기 때문입니다.


딜레이 워크는 어떤 코드로 표현할 수 있을까요? 정답은 struct delayed_work 이란 구조체입니다. 해당 코드를 같이 보겠습니다. 
[https://elixir.bootlin.com/linux/v4.14.43/source/include/linux/workqueue.h]
1 struct delayed_work {
2 struct work_struct work;
3 struct timer_list timer;
4
5 struct workqueue_struct *wq;
6 int cpu;
};

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

work
가장 첫 번째 멤버는 struct work_struct 타입의 work입니다. 이번 장에서 살펴봤던 워크를 표현하는 자료구조입니다.

timer
3번째 줄 코드를 보면 struct timer_list 이란 구조체인 timer를 볼 수 있습니다. 워크를 동적 타이머로 처리하기 위한 타이머 자료 구조입니다. 이 동적 타이머로 지정한 시간 후 워크를 실행합니다.

wq
struct workqueue_struct 구조체로 해당 딜레이 워크가 속한 워크큐 주소를 담고 있습니다.

cpu
딜레이 워크를 실행한 cpu 번호입니다.

딜레이 워크 자료구조를 보니 기존 struct work_struct 타입인 work 멤버와 struct timer_list이란 멤버를 볼 수 있습니다. 워크를 일정 시간 지연하기 위해 동적 타이머를 쓴다는 사실을 알 수 있습니다. 구조체 멤버를 보면 워크와 비슷한 동작을 한다고 유추할 수 있습니다.

이번 절에는 딜레이 워크 구조체를 소개했으니 다음 소절에서 자료 구조로 딜레이 워크를 어떻게 초기화하는지 살펴보겠습니다. 이미 워크에 대해 상세히 코드 분석을 했으니 워크와 차이점 위주로 분석하겠습니다.


딜레이 워크는 어떻게 초기화할까?

딜레이 워크를 실행하기 위해서 먼저 딜레이 워크를 초기화해야 합니다. 이를 위해 INIT_DELAYED_WORK() 매크로 함수를 호출해야 합니다.

먼저 딜레이 워크를 초기화하는 디바이스 드라이버 코드를 열어 봅시다.
[https://elixir.bootlin.com/linux/v4.14.43/source/drivers/thermal/da9062-thermal.c#L248]
1 static int da9062_thermal_probe(struct platform_device *pdev)
2 {
3 struct da9062 *chip = dev_get_drvdata(pdev->dev.parent);
4 struct da9062_thermal *thermal;
...
5 INIT_DELAYED_WORK(&thermal->work, da9062_thermal_poll_on);

5번째 줄 코드를 보면 첫 번째 인자인 &thermal->work 로 struct delayed_work 구조체 주소를 전달하고, 두 번째 인자로 워크 핸들러인 da9062_thermal_poll_on() 함수를 지정합니다. 

딜레이 워크 초기화 과정은 워크 초기화 코드와 비교해서 어떤 차이가 있을까요? 딜레이 워크는 초기화 과정에서 핸들러 함수를 바로 지정합니다.

이제 INIT_DELAYED_WORK() 함수 구현부 코드를 보면서 어떤 동작을 수행하는지 조금 더 확인합시다.
[https://elixir.bootlin.com/linux/v4.14.43/source/include/linux/workqueue.h#L259]
1 #define INIT_DELAYED_WORK(_work, _func) \
2 __INIT_DELAYED_WORK(_work, _func, 0)
3
4 #define __INIT_DELAYED_WORK(_work, _func, _tflags) \
5 do { \
6 INIT_WORK(&(_work)->work, (_func)); \
7 __setup_timer(&(_work)->timer, delayed_work_timer_fn, \
8       (unsigned long)(_work), \
9       (_tflags) | TIMER_IRQSAFE); \
10 } while (0)

2번 줄 코드를 보면 INIT_DELAYED_WORK() 매크로 함수는  __INIT_DELAYED_WORK() 함수로 치환됩니다. 이때 __INIT_DELAYED_WORK() 매크로 함수에 전달된 인자는 그대로   __INIT_DELAYED_WORK() 함수에 전달합니다.

6번째 줄 코드를 보겠습니다.
워크를 초기화할 때 썼던 INIT_WORK() 매크로 함수를 써서 워크를 초기화합니다. 여기서 __INIT_DELAYED_WORK() 함수에 전달된 첫 번째 인자는 _work인데 구조체는 struct delayed_work입니다.


INIT_WORK() 매크로 코드는 다음과 같습니다. 링크드 리스트인 entry 멤버를 초기화하고 data에 WORK_DATA_INIT() 값인 0xFFFFFFE0을 지정합니다. func 멤버에는 워크 핸들러 함수 주소를 저장합니다.
[https://elixir.bootlin.com/linux/v4.14.43/source/include/linux/workqueue.h#L236]
1 #define INIT_WORK(_work, _func) \
2 __INIT_WORK((_work), (_func), 0)
3
4 #define __INIT_WORK(_work, _func, _onstack) \
5 do { \
6 __init_work((_work), _onstack); \
7 (_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
8 INIT_LIST_HEAD(&(_work)->entry); \
9 (_work)->func = (_func);


위에서 살펴봤듯이 struct delayed_work의 첫 번째 멤버는 work이고 타입은 struct work_struct 이니 struct delayed_work->work이란 인자로 워크를 초기화하는 겁니다.

다음 7~9번째 줄 코드를 보겠습니다.
__setup_timer() 함수를 호출해서 동적 타이머를 초기화합니다. 동적 타이머 구조체는 &(_work)->timer이고 동적 타이머 핸들러는 delayed_work_timer_fn() 함수입니다. __setup_timer() 함수에 전달하는 세 번째 인자는 _work인데 구조체는 struct delayed_work입니다.


__setup_timer() 함수는 동적 타이머를 초기화하는 동작입니다.


딜레이 워크는 지정한 시각 후에 실행하는 워크입니다. 이를 위해 동적 타이머를 쓰는 겁니다. 딜레이 워크 함수로 지연 시각을 지정하면 동적 타이머 핸들러인 delayed_work_timer_fn() 함수가 지연 시각 후 실행합니다.

이번 절에서는 딜레이 워크를 초기화하는 코드 리뷰 분석을 했습니다. 코드를 열어보니 워크와 비슷한 코드가 많습니다. 딜레이 워크를 워크를 HZ 단위 시각으로 지연해서 실행하는 워크이기 때문입니다. 

다음 절에서는 딜레이 워크를 실행하는 동작을 알아보겠습니다.

#Reference 워크큐
워크큐 소개
워크큐 종류 알아보기
워크란  
워크를 워크큐에 어떻게 큐잉할까?
   워크를 큐잉할 때 호출하는 워크큐 커널 함수 분석   
워커 쓰레드란
워크큐 실습 및 디버깅
   ftrace로 워크큐 동작 확인   
   인터럽트 후반부로 워크큐 추가 실습 및 로그 분석 
   Trace32로 워크큐 자료 구조 디버깅하기 
딜레이 워크 소개  
   딜레이 워크는 누가 언제 호출할까?
라즈베리파이 딜레이 워크 실습 및 로그 확인  


핑백

덧글

댓글 입력 영역