Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

80258
1323
114582


[라즈베리파이] 워크큐(Workqueue) - 워크는 누가 언제 호출하나? (1) @process_one_work 8. Workqueue

워크를 워크큐에 큐잉하면 다음 그림과 같이 insert_work() 함수에서 wake_up_worker() 이란 함수를 호출해서 워커 쓰레드를 깨웁니다.

이 동작 흐름은 위 그림과 같이 3단계로 분류할 수 있습니다. 

1 단계
워크를 워크큐에 큐잉합니다. 

2 단계
wake_up_worker() 함수를 호출해서 워커 쓰레드를 깨웁니다. 커널 
스케줄러에게 워커 쓰레드를 깨워달라는 요청입니다. 

3 단계
스케줄링 정책에 따라 워커 쓰레드가 실행할 순서가 되면 커널 스케줄러가 워커 쓰레드를 실행합니다. 워커 쓰레드 핸들 함수는 worker_thread() 입니다. 이 함수에서 process_one_work() 함수를 호출해서 워크 핸들러를 호출하는 겁니다.

이번 소절에서는 process_one_work() 함수 분석으로 워크큐에서 워크를 어떻게 실행하는지 알아보겠습니다.

우선 process_one_work() 함수 전체 코드를 보겠습니다.
1 static void process_one_work(struct worker *worker, struct work_struct *work)
2 __releases(&pool->lock)
3 __acquires(&pool->lock)
4 {
5 struct pool_workqueue *pwq = get_work_pwq(work);
6 struct worker_pool *pool = worker->pool;
7 bool cpu_intensive = pwq->wq->flags & WQ_CPU_INTENSIVE;
8 int work_color;
9 struct worker *collision;
10
11 /* ensure we're on the correct CPU */
12 WARN_ON_ONCE(!(pool->flags & POOL_DISASSOCIATED) &&
13      raw_smp_processor_id() != pool->cpu);
14
15 collision = find_worker_executing_work(pool, work);
16 if (unlikely(collision)) {
17 move_linked_works(work, &collision->scheduled, NULL);
18 return;
19 }
20
21 /* claim and dequeue */
22 debug_work_deactivate(work);
23 hash_add(pool->busy_hash, &worker->hentry, (unsigned long)work);
24 worker->current_work = work;
25 worker->current_func = work->func;
26 worker->current_pwq = pwq;
27 work_color = get_work_color(work);
28
29 list_del_init(&work->entry);
30
31 if (unlikely(cpu_intensive))
32 worker_set_flags(worker, WORKER_CPU_INTENSIVE);
33
34 if (need_more_worker(pool))
35 wake_up_worker(pool);
36
37 set_work_pool_and_clear_pending(work, pool->id);
38
39 spin_unlock_irq(&pool->lock);
40
41 lock_map_acquire(&pwq->wq->lockdep_map);
42 lock_map_acquire(&lockdep_map);
43 lockdep_invariant_state(true);
44
45 trace_workqueue_execute_start(work);
46 worker->current_func(work);
47
48 trace_workqueue_execute_end(work);
49 lock_map_release(&lockdep_map);
50 lock_map_release(&pwq->wq->lockdep_map);
51
52 if (unlikely(in_atomic() || lockdep_depth(current) > 0)) {
53 pr_err("BUG: workqueue leaked lock or atomic: %s/0x%08x/%d\n"
54        "     last function: %pf\n",
55        current->comm, preempt_count(), task_pid_nr(current),
56        worker->current_func);
57 debug_show_held_locks(current);
58 dump_stack();
59 }
60
61 cond_resched_rcu_qs();
62
63 spin_lock_irq(&pool->lock);
64
65 /* clear cpu intensive status */
66 if (unlikely(cpu_intensive))
67 worker_clr_flags(worker, WORKER_CPU_INTENSIVE);
68
69 /* we're done with it, release */
70 hash_del(&worker->hentry);
71 worker->current_work = NULL;
72 worker->current_func = NULL;
73 worker->current_pwq = NULL;
74 worker->desc_valid = false;
75 pwq_dec_nr_in_flight(pwq, work_color);
76 }

15번 줄 코드를 분석하겠습니다.
15 collision = find_worker_executing_work(pool, work);
16 if (unlikely(collision)) {
17 move_linked_works(work, &collision->scheduled, NULL);
18 return;
19 }

지금 실행하려는 워크를 다른 워커가 실행 중인지 점검하는 코드입니다. 한 개 워크는 한 개 워커에서 실행해야 워커 쓰레드를 효율적으로 쓸 수 있습니다. find_worker_executing_work() 함수를 열어보면 워커 풀 멤버인 busy_hash를 순회하며 현재 처리하는 워크가 이미 다른 워커에서 등록됐는지 점검합니다.

이 조건을 점검한 후 이미 같은 워크를 처리했던 워커(&collision->scheduled) 멤버에 워크를 이동합니다. 18번째 줄 코드와 같이 return 문을 실행해서 process_one_work() 함수를 바로 빠져 나옵니다.

이 방식으로 하나의 워크를 여러 워크에서 실행하지 않도록 관리합니다.

다음 23번째 줄 코드를 보겠습니다.
22 debug_work_deactivate(work);
23 hash_add(pool->busy_hash, &worker->hentry, (unsigned long)work);

pool->busy_hash이란 해시 테이블에 &worker->hentry를 등록합니다. 최근 실행한 워커 쓰레드는 워커 풀 bush_hash란 멤버에 등록됩니다. 이 워커 쓰레드를 표현하는 워커 자료구조를 가져와 실행했던 워크 정보를 읽습니다. 만약 워크 실행 정보가 일치하면 해당 워커 자료구조를 반환합니다. 효율적으로 워커를 쓰려는 목적입니다.

&pool->busy_hash 해시 테이블은 find_worker_executing_work() 함수에서 접근합니다. 


Reference(워크큐)
워크큐(Workqueue) Overview


핑백

덧글

댓글 입력 영역