Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

119187
803
94448


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

insert_work() 함수 분석
이번에는 insert_work() 함수를 분석하겠습니다.
1 static void insert_work(struct pool_workqueue *pwq, struct work_struct *work,
2 struct list_head *head, unsigned int extra_flags)
3 {
4 struct worker_pool *pool = pwq->pool;
5
6 set_work_pwq(work, pwq, extra_flags);
7 list_add_tail(&work->entry, head);
8 get_pwq(pwq);
9
10 smp_mb();
11
12 if (__need_more_worker(pool))
13 wake_up_worker(pool);
14}

6번 줄 코드부터 분석을 시작합니다. set_work_pwq() 함수를 호출해서 풀워크큐 주소와 워크 플레그를 struct work_struct.data 멤버에 저장합니다. set_work_pwq() 함수는 워크 동작 원리와 자료구조를 이해하기 위해서 잘 알아 둬야 합니다.

다음 set_work_pwq () 함수를 분석하겠습니다.
1 static void set_work_pwq(struct work_struct *work, struct pool_workqueue *pwq,
2  unsigned long extra_flags)
3{
4 set_work_data(work, (unsigned long)pwq,
5       WORK_STRUCT_PENDING | WORK_STRUCT_PWQ | extra_flags);
6}

4번 줄 코드를 보면 set_work_data() 함수를 호출해서 첫 번째 인자로 work, 두 번째로는 struct pool_workqueue 구조체 주소 그리고 세 번째 인자로 (WORK_STRUCT_PENDING | WORK_STRUCT_PWQ | extra_flags)를 연산한 결과를 전달합니다.

이번에는 set_work_data() 함수를 열어 봅시다.
7 static inline void set_work_data(struct work_struct *work, unsigned long data,
8  unsigned long flags)
9{
10 WARN_ON_ONCE(!work_pending(work));
11 atomic_long_set(&work->data, data | flags | work_static(work));
12}

10번 줄 코드는 워크를 워크 큐에 이미 큐잉했으면 WARN() 매크로를 호출해서 커널 로그로 경고 메시지를 출력합니다. 예외 처리 코드입니다.

11번 줄 코드를 보면 data와 flags를 OR 연산해서 &work->data 멤버에 저장합니다. work_static(work) 코드는 CONFIG_DEBUG_OBJECTS_WORK이란 커널 컨피그(.config)를 켰을 때 실행하는데 라즈비안에서는 이 커널 컨피그 꺼져 있으니 0입니다. 

set_work_pwq() 함수는 결국 다음 코드와 같이 동작합니다.
struct work_stuct.data = pwq | (WORK_STRUCT_PENDING | WORK_STRUCT_PWQ | extra_flags)

struct pool_workqueue 구조체 주소를 담고 있는 pwq와 (WORK_STRUCT_PENDING | WORK_STRUCT_PWQ | extra_flags) 를 OR 연산해서 struct work_struct.data에 저장합니다.

struct work_struct.data에 struct pool_workqueue 구조체 주소를 저장하니 이후 워커 쓰레드가 워크를 실행할 때 호출되는 get_work_pool() 함수 8~10번 줄 코드와 같이 struct work_struct.data 와 WORK_STRUCT_WQ_DATA_MASK(0x00) 매크로와 AND 연산을 수행해서 struct pool_workqueue 구조체 주소에 접근할 수 있는 겁니다.
1 static struct worker_pool *get_work_pool(struct work_struct *work)
2 {
3 unsigned long data = atomic_long_read(&work->data);
4 int pool_id;
5
6 assert_rcu_or_pool_mutex();
7
8 if (data & WORK_STRUCT_PWQ)
9 return ((struct pool_workqueue *)
10 (data & WORK_STRUCT_WQ_DATA_MASK))->pool;

set_work_pwq() 함수를 제대로 이해하려면 이 흐름을 알아야 합니다.

함수 분석에 들어가기에 앞서 insert_work() 함수 세 번째 struct list_head *head 인자의 출처를 다시 점검할 필요가 있습니다. insert_work() 함수에서 가장 중요한 인자이며 이 인자의 의미를 모르면 이 함수 동작 원리를 이해할 수 없기 때문입니다. 

다음과 같이 struct list_head *head 인자는 __queue_work() 함수에서 &pwq->pool->worklist 코드로 전달됩니다.
71 worklist = &pwq->pool->worklist; 
...
79 insert_work(pwq, work, worklist, work_flags);

다음 7번 줄 코드를 분석하겠습니다.
7 list_add_tail(&work->entry, head);

이 코드를 분석하기 전 두 번째 인자인 head가 어떤 흐름으로 전달됐는지 확인합시다. head는 insert_work() 세 번째 인자로 전달되는데 타입은 struct list_head인 구조체이며 포인터형 변수(*head)입니다.

다음 코드에서 struct list_head *head 인자는 __queue_work() 함수에서 &pwq->pool->worklist 코드로 전달됩니다.
71 worklist = &pwq->pool->worklist; 
...
79 insert_work(pwq, work, worklist, work_flags);

위 흐름으로 다음 코드 head인자는 &pwq->pool->worklist 코드에 대응합니다. 즉 &pwq->pool->worklist 메모리 주소를 head가 저장하고 있습니다.
list_add_tail(&work->entry, head);
list_add_tail(&work->entry, &pwq->pool->worklist);

이 코드를 실행하면 &pwq->pool->worklist 이란 링크드 리스트는 &work->entry란 링크드 리스트를 가르킵니다. 

다음 그림으로 이 동작을 설명할 수 있습니다.
 
가운데 struct pool_workqueue란 구조체가 보이고 첫 멤버가 struct worker_pool 구조체인 pool입니다. 이 struct worker_pool.worklist에 워크의 entry 멤버를 링크트 리스트로 등록하는 겁니다. 

여기까지 워크를 큐잉하면 워크큐 어느 자료 구조에 등록하는지 알아봤습니다.

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


핑백

덧글

댓글 입력 영역