-
인터럽트 후반부 처리 / 워크큐Linux/Linux Kernel 2020. 9. 13. 13:29
워크큐 구성요소
워크
워크(워크 핸들러)는 실행단위다. 워커 스레드(워커 스레드 핸들러; woker_thread())가 워크를 실행한다.
워커 스레드
워커 스레드(워커 스레드 핸들러; woker_thread())가 워크(워크 핸들러)를 실행한다.
워커 풀
워크를 큐잉한 워크 리스트를 관리한다. 워커 스레드를 생성하면서 관리한다. 그래서 큐잉된 워크의 개수나 실행 중인 워커 스레드의 개수를 워커 풀로 부터 알 수 있다.
풀워크큐
풀워크큐는 워커 풀을 통해 워크와 워커 스레드를 관리한다.
워크큐 안에 *per_cpu 포인터 구조체 필드인 풀워크큐가 있고 풀워크큐안에 포인터 구조체 필드인 워커풀이 있고 워커풀안에 워크를 관리할 연결리스트 구조체 필드와 워커 스레드를 관리할 연결리스트 구조체 필드가 있다.
※ per_cpu 타입은 CPU 개수만큼 있다는 뜻이다. 리눅스 커널은 기본적으로 7개의 워크큐를 지원한다. 각 워크큐에는 풀워크큐가 총 4개가 있고 각 풀워크큐에는 워커풀이 1개 있다. 그럼 기본적으로 총 워크큐는 7개 풀워크큐는 28개 워커풀도 총 28개가 있게되는것이다. 이 7개의 기본적인 워크큐는 커널에서 생성하기 때문에 사용자가 동적으로 워크큐를 생성하면 풀워크큐랑 워커풀이 더 많아진다. 그리고 CPU 개수 만큼 있다는 뜻은 해당 CPU에 1:1 대응된다는 말이다. 즉 해당 CPU에서만 해당 풀워크큐에 있는 워크만 처리가능하다.
워크큐 생성(alloc_workqueue() 함수)
workqueue_init_early() 함수로 기본적으로 7개의 워크큐를 생성한다.
int __init workqueue_init_early(void)
workqueue.c - kernel/workqueue.c - Linux source code (v4.19.115) - Bootlin
elixir.bootlin.com
동적으로 워크큐를 생성하려면 alloc_workqueue() 함수를 호출한다.
#define alloc_workqueue(fmt, flags, max_active, args...)
workqueue.h - include/linux/workqueue.h - Linux source code (v4.19.115) - Bootlin
/* SPDX-License-Identifier: GPL-2.0 */ /* * workqueue.h --- work queue handling for Linux. */ #ifndef _LINUX_WORKQUEUE_H #define _LINUX_WORKQUEUE_H #include #include #include #include #include #include #include #include struct workqueue_struct; struct work
elixir.bootlin.com
alloc_workqueue() 매개변수
- fmt: 워크큐의 이름을 지정한다 워크큐 구조체인 workqueue_struct의 name 필드에 저장된다.
- flags: 워크큐의 속성 정보를 지정한다. 워크큐 구조체인 workqueue_struct의 flag 필드에 저장된다.
workqueue.h - include/linux/workqueue.h - Linux source code (v4.19.115) - Bootlin
/* SPDX-License-Identifier: GPL-2.0 */ /* * workqueue.h --- work queue handling for Linux. */ #ifndef _LINUX_WORKQUEUE_H #define _LINUX_WORKQUEUE_H #include #include #include #include #include #include #include #include struct workqueue_struct; struct work
elixir.bootlin.com
- max_active: 워크큐의 cpu별 동시 처리 갯수를 지정한다. 워크큐 구조체인 workqueue_struct의 saved_max_active 필드에 저장된다.
workqueue_init_early() 함수를 보면 기본적인 7개의 워크큐를 alloc_workqueue() 함수로 생성한다.
7개의 워크큐
events
events_highpri
events_long
events_unbound
events_freezable
events_power_efficient
events_freezable_efficient
워크 초기화
워크는 INIT_WORK() 매크로 함수와 DECLARE_WORK() 매크로 함수로 초기화할 수 있다.
INIT_WORK()
workqueue.h - include/linux/workqueue.h - Linux source code (v4.19.115) - Bootlin
/* SPDX-License-Identifier: GPL-2.0 */ /* * workqueue.h --- work queue handling for Linux. */ #ifndef _LINUX_WORKQUEUE_H #define _LINUX_WORKQUEUE_H #include #include #include #include #include #include #include #include struct workqueue_struct; struct work
elixir.bootlin.com
DECLARE_WORK()
workqueue.h - include/linux/workqueue.h - Linux source code (v4.19.115) - Bootlin
/* SPDX-License-Identifier: GPL-2.0 */ /* * workqueue.h --- work queue handling for Linux. */ #ifndef _LINUX_WORKQUEUE_H #define _LINUX_WORKQUEUE_H #include #include #include #include #include #include #include #include struct workqueue_struct; struct work
elixir.bootlin.com
DECLARE_WORK()는 내부에서 work_struct 구조체를 선언하여 초기화한다.
그럼 워크 구조체는 어떻게 생겨먹었을까?
struct work_struct
workqueue.h - include/linux/workqueue.h - Linux source code (v4.19.115) - Bootlin
/* SPDX-License-Identifier: GPL-2.0 */ /* * workqueue.h --- work queue handling for Linux. */ #ifndef _LINUX_WORKQUEUE_H #define _LINUX_WORKQUEUE_H #include #include #include #include #include #include #include #include struct workqueue_struct; struct work
elixir.bootlin.com
필드
- atomic_long_t data: 워크의 상태를 나타낸다.
- struct list_head entry: *자신의 워크 구조체의 entry 필드의 주소를 저장한다.
- work_func_t func: 워크 핸들러의 주소를 저장
※ 그니까 그냥 자기 자신의 주소를 자기 자신에 넣는다. 뭐지.. 워크를 워크큐에 큐잉할때 worker_pool 구조체의 연결리스트인 worklist에 work_struct구조체의 주소가 아닌 work_struct 구조체의 entry 필드의 주소를 저장한다. 그래서 entry 필드의 값은 의미가 없는것 같다.
워크를 워크큐에 큐잉
워크를 워크큐에 큐잉한다는 말은 워크큐의 per_cpu 포인터 구조체 필드인 풀워크큐의 포인터 구조체 필드인 워커풀의 워크를 관리할 연결리스트 구조체 필드에 저장하고 워크큐의 per_cpu 포인터 구조체 필드인 풀워크큐의 포인터 구조체 필드인 워커풀의 워커 스레드를 관리할 연결리스트 구조체 필드의 워커 스레드를 깨우는 것이다.
워크를 워크큐에 큐잉하기 위해서는 schedule_work() 함수를 호출한다.
static inline bool schedule_work(struct work_struct *work)
workqueue.h - include/linux/workqueue.h - Linux source code (v4.19.115) - Bootlin
/* SPDX-License-Identifier: GPL-2.0 */ /* * workqueue.h --- work queue handling for Linux. */ #ifndef _LINUX_WORKQUEUE_H #define _LINUX_WORKQUEUE_H #include #include #include #include #include #include #include #include struct workqueue_struct; struct work
elixir.bootlin.com
함수 호출 순서 아래에서 위
---------->wake_up_work()
-------->insert_work()
-------->find_worker_executing_work()
-------->get_work_pool()
------>__queue_work()
---->queue_work_on()
-->queue_work()
>schedule_work()
함수 별 기능
- wake_up_work(): 아이들 워커를 깨운다.
- insert_work(): 워커 풀의 연결리스트 worklist 필드에 work_struct 구조체의 entry 필드의 주소를 저장
- find_worker_executing_work(): 지금 큐잉하려는 워크큐와 워크@@@@@@@@@@@@@@수정중
- get_work_pool(): 가져온 풀워크큐의 워커 풀의 주소를 반환한다.
- __queue_work(): 워크큐로 부터 해당 CPU라인 풀워크큐의 주소를 가져온다.
- queue_work_on(): 1. __queue_work() 함수로 진입하기전 해당 CPU라인 인터럽트 비활성화 2. work_struct의 data필드 *큐잉상태로 변경
- queue_work(): 워크를 *어떤 워크큐에 큐잉할지 결정
- schedule_work()
※ 어떤 워크큐: schedule_work() 함수를 통해 queue_work()를 실행하면 system_wq에 큐잉된다.
※ 워크를 워크큐에 중복 큐잉할 때를 대비한 예외 처리다.
워크 실행
워커 스레드 생성
워크를 초기화하여 워크를 워크큐에 큐잉하고 워커 스레드가 워크를 실행했다. 근데 워크를 실행할 워커 스레드는 누가 언제 만들까?
워커 스레드는 부팅 과정에서 생성된다. 동적으로는 생성도 가능하다.
틀린 점이 있다면 댓글 부탁드리겠습니다. 감사합니다.
'Linux > Linux Kernel' 카테고리의 다른 글
인터럽트 후반부 처리 / 태스크릿 (0) 2020.09.13 인터럽트 후반부 처리 / Soft IRQ (0) 2020.09.13 인터럽트 후반부 처리 / IRQ 스레드 (0) 2020.09.13 인터럽트 후반부 처리 (0) 2020.08.16 인터럽트 (0) 2020.08.16