Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

99258
1323
114601


[리눅스커널] 워크큐: queue_work_on() 함수 분석 8. Workqueue

schedule_work() 함수를 호출하면 어떻게 워크를 워크큐에 큐잉하는지 이제부터 살펴보겠습니다. schedule_work() 함수를 호출하면 queue_work_on() 함수를 호출하니 먼저 schedule_work() 함수를 보겠습니다.
[/include/linux/workqueue.h]
1 static inline bool schedule_work(struct work_struct *work)
2 {
3 return queue_work(system_wq, work);
4}

5 static inline bool queue_work(struct workqueue_struct *wq,
6       struct work_struct *work)
7 {
8 return queue_work_on(WORK_CPU_UNBOUND, wq, work);
9 }

먼저 3번 줄 코드를 보겠습니다.
queue_work() 이란 함수를 호출하는데 system_wq 이란 전역 변수를 첫 번째 인자로 queue_work() 함수에 전달합니다. schedule_work() 함수로 전달하는 워크는 시스템 워크큐에 큐잉된다는 사실을 알 수 있습니다.

8번 줄 코드를 보면 queue_work() 함수는 현재 워크를 WORK_CPU_UNBOUND를 첫 번째 인자로 queue_work_on() 함수를 호출합니다.

schedule_work() 함수로 워크를 워크큐에 큐잉을 하면 queue_work() 함수에서 queue_work_on() 함수를 호출합니다. 이때 워크는 시스템 워크 system_wq에 큐잉됩니다.

queue_work_on() 함수 분석
이어서 queue_work_on() 함수를 분석하겠습니다.
[kernel/workqueue.c]
1 bool queue_work_on(int cpu, struct workqueue_struct *wq,
2    struct work_struct *work)
3 {
4 bool ret = false;
5 unsigned long flags;
6
7 local_irq_save(flags);
8
9 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번 줄 코드를 보겠습니다.
7 local_irq_save(flags);

9~12번 줄 코드를 보호 구역으로 보고 인터럽트 동기화시킵니다. 
7번과 14번 줄 코드로 워크를 큐잉하는 9~12줄 코드 실행 도중에 인터럽트가 발생해서 동기화 문제가 일어나는 것을 방지합니다.

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

9번 줄을 보겠습니다. 
9 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 구조체 주소에서 struct work_struct.data 멤버를 읽습니다. 이 값이 WORK_STRUCT_PENDING_BIT(1)이면 9~12줄 코드를 실행하지 않고 if 문을 빠져나옵니다.

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번 줄 코드와 같이 동작도 안 하고 if 문을 빠져나옵니다. 2번째 줄 코드와 같이 실행할 코드가 없기 때문입니다. 대신 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 매크로이면 struct work_struct.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 워크큐
워크큐 소개
워크큐 종류 알아보기
워크란  
워크를 워크큐에 어떻게 큐잉할까?
   워크를 큐잉할 때 호출하는 워크큐 커널 함수 분석   
워커 쓰레드란
워크큐 실습 및 디버깅
   ftrace로 워크큐 동작 확인   
   인터럽트 후반부로 워크큐 추가 실습 및 로그 분석 
   Trace32로 워크큐 자료 구조 디버깅하기 
딜레이 워크 소개  
   딜레이 워크는 누가 언제 호출할까?
라즈베리파이 딜레이 워크 실습 및 로그 확인  


핑백

덧글

댓글 입력 영역