Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

80258
1323
114582


[리눅스커널] 워크큐: 워크를 워크큐에 큐잉하는 인터페이스 함수 분석하기 8. 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이라면 이를 어떻게 해석해야 할까요? 

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

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


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



핑백

덧글

댓글 입력 영역