Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

16312
2027
233849


[리눅스커널] 워크큐: create_worker() 함수에서 호출한 워크큐 커널 함수 분석하기 7. 워크큐(Workqueue)

이번 시간에는 create_worker() 함수에서 호출한 워커 스레드 세부 제어 함수를 살펴보겠습니다.

worker_attach_to_pool() 함수 분석하기

worker_attach_pool() 함수는 워커를 워커풀에 연결하는 역할을 수행합니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c] 
01 static void worker_attach_to_pool(struct worker *worker,
02    struct worker_pool *pool)
03 {
04 mutex_lock(&pool->attach_mutex);
05
06 set_cpus_allowed_ptr(worker->task, pool->attrs->cpumask);
07 if (pool->flags & POOL_DISASSOCIATED)
08 worker->flags |= WORKER_UNBOUND;
09
10 list_add_tail(&worker->node, &pool->workers);
11
12 mutex_unlock(&pool->attach_mutex);
13 }

이 함수의 핵심 동작은 워커풀 구조체 struct worker_pool 필드인 worker(연결리스트)에 워커 struct worker 구조체 node 필드(연결리스트 타입)를 등록하는 것입니다.

10번째 줄 코드를 보겠습니다. 
10 list_add_tail(&worker->node, &pool->workers);

&worker->node와 &pool->workers 변수 모두 struct list_head 타입인 연결 리스트입니다. &pool->workers 연결 리스트에 &worker->node를 등록합니다. 이 코드가 실행하면 다음 그림과 같이 자료 구조가 업데이트됩니다.
 
[그림 7.12] 워커를 워커풀에 등록 후 변경되는 자료구조 

다른 관점으로 &pool->workers 연결 리스트는 &worker->node 주소를 가리키고 있습니다. 이후 &pool->workers 주소에 접근해서 struct worker 구조체 node 필드 오프셋을 빼서 struct work 구조체 주소에 접근합니다.


리눅스 커널에서는 이 방식을 아주 많이 씁니다. 링크드 리스트가 다른 자료구조 링크드 리스트 필드 주소를 가르키는 동작입니다. container_of 매크로 함수도 이 방식을 적용해서 구현한 것입니다.


worker_enter_idle() 함수 분석하기

worker_enter_idle() 함수는 워커의 flags를 WORKER_IDLE로 바꾸고 워커 정보를 업데이트합니다.

이어서 worker_enter_idle() 함수를 분석하겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/workqueue.c]
1 static void worker_enter_idle(struct worker *worker)
2 {
3 struct worker_pool *pool = worker->pool;
4
5 if (WARN_ON_ONCE(worker->flags & WORKER_IDLE) ||
6     WARN_ON_ONCE(!list_empty(&worker->entry) &&
7  (worker->hentry.next || worker->hentry.pprev)))
8 return;
9
10 worker->flags |= WORKER_IDLE;
11 pool->nr_idle++;
12 worker->last_active = jiffies;
13
14 list_add(&worker->entry, &pool->idle_list);
15
16 if (too_many_workers(pool) && !timer_pending(&pool->idle_timer))
17 mod_timer(&pool->idle_timer, jiffies + IDLE_WORKER_TIMEOUT);
18
19 WARN_ON_ONCE(!(pool->flags & POOL_DISASSOCIATED) &&
20      pool->nr_workers == pool->nr_idle &&
21      atomic_read(&pool->nr_running));
22 }

이 함수의 핵심 동작은 10~14번째 줄이며 나머지는 예외 처리 코드입니다.
10 worker->flags |= WORKER_IDLE;
11 pool->nr_idle++;
12 worker->last_active = jiffies;
13
14 list_add(&worker->entry, &pool->idle_list);

10번째 코드를 봅시다.
10 worker->flags |= WORKER_IDLE;

worker->flags에 WORKER_IDLE 플래그 설정해 워커를 WORKER_IDLE 타입으로 설정합니다.

다음 11번째 줄 코드를 보겠습니다.
11 pool->nr_idle++;
12 worker->last_active = jiffies;

11번째 줄 코드는 워커 풀 nr_idle 필드를 +1만큼 증감합니다. nr_idle은 워커풀에 등록된 idle 워커 개수입니다.

다음 12번째 줄 코드는 worker->last_active에 jiffies를 저장합니다. 현재 시각 정보가 있는 jiffies로 워커가 마지막에 처리된 시각을 저장하는 것입니다.

여기까지 워커를 생성하는 create_worker() 중심으로 관련 함수를 분석했습니다. 워커를 만드는 create_worker() 함수에서 다음 함수를 호출한다는 사실을 알게 됐습니다. 각 함수별 세부 동작을 요약하면 다음과 같습니다. 
kthread_create_on_node() 함수: "kworker/" 이름으로 워커 스레드 만듦
worker_attach_to_pool() 함수: 워커풀에 워커를 등록
worker_enter_idle() 함수: 워커 상태를 WORKER_IDLE로 바꿈 
wake_up_process() 함수: 워커 스레드를 깨움

다음 절에서는 워커 스레드 핸들 함수인 worker_thread() 분석으로 워커 스레드 동작을 살펴보겠습니다.


핑백

덧글

  • Summer 2021/02/10 12:16 # 삭제 답글

    안녕하세요 저자님, 항상 좋은 포스트 감사합니다.

    한 가지 여쭙고 싶은 게 있는데 create_worker() 함수에서 wake_up_proess() 함수를 호출하기 전 워커를 idle 상태로 만드는 이유가 뭘까요? 생략하고 프로세스를 깨우면 문제가 생길까요?

    감사합니다.
  • AustinKim 2021/02/10 13:56 #

    워커에게 상태 정보를 정의한 커밋은 아래 링크에서 확인할 수 있는데요.
    https://lkml.org/lkml/2010/6/14/423

    상태 정보
    +enum {
    +/* worker flags */
    +WORKER_STARTED= 1 << 0,/* started */
    +WORKER_DIE= 1 << 1,/* die die die */
    +WORKER_IDLE= 1 << 2,/* is idle */

    FromTejun Heo
    Subject[PATCH 17/30] workqueue: implement worker states
    DateMon, 14 Jun 2010 23:37:34 +0200

    Implement worker states. After created, a worker is STARTED. While a
    worker isn't processing a work, it's IDLE and chained on
    gcwq->idle_list. While processing a work, a worker is BUSY and
    chained on gcwq->busy_hash. Also, gcwq now counts the number of all
    workers and idle ones.

    워크큐의 최고 전문가이신 허태준님이 구현하신 코드인데,
    커밋이 생성된 날짜를 보니 11년전이네요.

    워커에게 상태 정보를 부여해 워커를 안정적으로 관리하려는 목적인 듯 합니다.

    워커는 생성되면 IDLE 상태가 되는데,
    이를 'struct list_headentry;/* L: while idle */' 링크드 리스트에서 관리합니다.

    워커가 워크를 실행할 때는,
    'struct hlist_nodehentry;/* L: while busy */' 링크드 리스트에서 관리합니다.

    더 자세한 내용은 패치 코드나 아래 링크에 있는 내용을 참고하세요.
    https://www.kernel.org/doc/html/latest/core-api/workqueue.html

    감사합니다.
댓글 입력 영역