Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

42107
469
422674


[리눅스커널] 워크큐: 워크를 워크큐에 큐잉하는 인터페이스 함수 분석하기 7. 워크큐(Workqueue)

이번 소절에서는 워크를 워크큐에 큐잉할 때 진입하는 함수를 분석합니다.
schedule_work()
queue_work()
queue_work_on()

먼저 schedule_work() 함수를 분석해볼까요?

schedule_work() 함수 분석하기
schedule_work() 함수 구현부 코드는 다음과 같습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/workqueue.h]
1 static inline bool schedule_work(struct work_struct *work)
2 {
3 return queue_work(system_wq, work);
4}

schedule_work() 함수는 인라인 타입으로 함수 구현부가 간단합니다.

queue_work() 함수를 호출하는데 system_wq 전역 변수를 첫 번째 인자로 queue_work() 함수에 전달합니다. 이 코드 내용을 토대로 다음 사실을 알 수 있습니다. 

    schedule_work() 함수로 전달하는 워크는 시스템 워크큐에 큐잉된다.

시스템 워크큐는 system_wq 전역 변수로 관리하며 선언부는 다음과 같습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/workqueue.c]
struct workqueue_struct *system_wq __read_mostly;
EXPORT_SYMBOL(system_wq);

이어서 queue_work() 함수 코드를 보겠습니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/include/linux/workqueue.h]
01 static inline bool queue_work(struct workqueue_struct *wq,
02       struct work_struct *work)
03 {
04 return queue_work_on(WORK_CPU_UNBOUND, wq, work);
05 }

4번째 줄 코드를 보면 WORK_CPU_UNBOUND를 첫 번째 인자로 queue_work_on() 함수를 호출합니다.

정리하면 schedule_work() 함수로 전달하는 워크는 시스템 워크큐에 큐잉되며 다음 함수 흐름으로 queue_work_on() 함수를 호출한다는 사실을 알 수 있습니다. 
queue_work()
queue_work_on()

queue_work_on() 함수 분석하기
이어서 queue_work_on() 함수 코드를 분석해봅시다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/workqueue.c]
01 bool queue_work_on(int cpu, struct workqueue_struct *wq,
02    struct work_struct *work)
03 {
04 bool ret = false;
05 unsigned long flags;
06
07 local_irq_save(flags);
08
09 if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
10 __queue_work(cpu, wq, work);
11 ret = true;
12 }
13
14 local_irq_restore(flags);
15 return ret;
16}

먼저 7번째 줄 코드를 보겠습니다.
07 local_irq_save(flags);

7~14번째 줄 코드 구간에서 워크를 큐잉하는 9~12번째 줄 코드 구간 실행 도중에 해당 CPU라인 인터럽트를 비활성화합니다. 이 코드의 목적은 다음과 같습니다. 

    인터럽트가 발생해서 동기화 문제 발생을 방지하고 싶다. 

커널 코드는 언제든 인터럽트가 발생해서 실행 흐름이 멈출 수 있습니다.

이어서 9번째 줄 코드를 보겠습니다. 
09 if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
10 __queue_work(cpu, wq, work);
11 ret = true;
12 }

work_data_bits(work) 매크로 함수는 struct work_struct 구조체 주소에서 data 필드를 읽습니다. 이 값이 WORK_STRUCT_PENDING_BIT(1)이면 9~12 번째 줄 코드를 실행하지 않고 바로 14 번째 줄 코드를 실행합니다.

test_and_set_bit() 함수는 리눅스 커널 자료구조 함수 중 하나입니다.
test_and_set_bit(A, B); 와 같이 호출하면 A와 B란 변수 비트를 AND 연산한 다음 결과가 1이면 1을 반환하고 반대로 0이면 0을 반환합니다. 연산 결과에 상관없이 B에 A비트를 설정합니다.

이해를 돕기 위해 9~12째 줄 코드를 다른 코드로 쉽게 표현하면 다음과 같습니다.
1 if (work->data == WORK_STRUCT_PENDING_BIT) {
3 } else
4 work->data =| WORK_STRUCT_PENDING_BIT;
5 __queue_work(cpu, wq, work);
6 ret = true;
7 }

work->data가 WORK_STRUCT_PENDING_BIT이면 if 문을 만족하니 2번째 줄 코드로 이동합니다. 그런데 2번째 줄에는 코드가 없으니 아무 동작을 안 하고 if 문을 빠져나옵니다. 대신 work->data가 WORK_STRUCT_PENDING_BIT 가 아니면 else문을 실행합니다. 

if 문 조건을 만족하면 아무 동작을 안 하는 코드는 보기 이상하니 if 문을 ! 조건으로 바꿔 코드를 작성하면 다음과 같습니다.
if ( !(work->data == WORK_STRUCT_PENDING_BIT)) {
work->data =| WORK_STRUCT_PENDING_BIT;
__queue_work(cpu, wq, work);
ret = true;
}

work->data 필드가 WORK_STRUCT_PENDING_BIT 플래그가 아니면 work->data에 WORK_STRUCT_PENDING_BIT를 저장하고 __queue_work() 함수를 호출하는 것입니다.

test_and_set_bit() 함수를 다른 코드로 바꿔서 설명을 드렸습니다. test_and_set_bit() 함수는 위에 바꾼 코드와 같이 struct work_struct구조체 data 필드가 WORK_STRUCT_PENDING_BIT가 아니면 struct work_struct 구조체 data 필드를 WORK_STRUCT_PENDING_BIT로 설정하고 0을 반환합니다.

반대로 struct work_struct 구조체 data 필드가 WORK_STRUCT_PENDING_BIT 플래그면 data 필드를 WORK_STRUCT_PENDING_BIT 플래그로 설정한 후 1을 반환합니다.

여기서 한 가지 의문이 생깁니다. 

     struct work_struct 구조체 data 필드가 WORK_STRUCT_PENDING_BIT인지 왜 
   점검하는 이유는 무엇일까?

struct work_struct 구조체 data 필드에 워크 실행 상태가 저장돼있기 때문입니다. queue_work_on() 함수 호출로 워크를 워크큐에 큐잉하기 직전에 이 필드는 WORK_STRUCT_PENDING_BIT 플래그로 바꿉니다. 

만약 워크를 워크큐에 큐잉하기 직전에 이 필드가 WORK_STRUCT_PENDING_BIT이라면 이를 어떻게 해석해야 할까요? 

   이미 워크를 워크에 큐잉한 상태로 볼 수 있습니다. 

이 조건에서는 워크를 워크큐에 큐잉하지 않습니다. 워크를 워크큐에 중복 큐잉할 때를 대비한 예외 처리 코드입니다.


커널은 디바이스 드라이버에서 중복 코드를 실행할 경우 시나리오를 생각해서 예외 처리를 수행합니다.

* 유튜브 강의 동영상도 있으니 같이 들으시면 좋습니다. 




# Reference: For more information on 'Linux Kernel';

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2




핑백

덧글

댓글 입력 영역