티스토리 뷰
5.7.3 idle 쓰레드가 설정되는 과정
cpu 별로 idle 쓰레드를 생성하나, 초기화 과정은 0번 cpu 와 나머지 cpu 에 차이가 있다.
- 0번
0번 cpu 의 스케줄링 클래스를 idle 스케줄링 클래스로 설정
void init_idle_bootup_task(struct task_struct *idle)
{
idle->sched_class = &idle_sched_class;
}
- 나머지 cpu 의 idle 쓰레드 초기화
copy 하여 생성하고, 0번 cpu 의 idle 쓰레드의 pid 를 같이 사용하도록 설정
struct task_struct *fork_idle(int cpu)
{
return copy_process(CLONE_VM, 0, 0, NULL, &init_struct_pid, 0, 0);
}
5.7.4 초기화 과정 분석
5-25 kernel/sched/core.c init_idle_bootup_task
swapper task 의 스케줄링 클래스를 idle 스케줄링 클래스로 변경
void init_idle_bootup_task(struct task_struct *idle)
{
idle->sched_class = &idle_sched_class;
}
5-26 kernel/sched/smpboot.c idle_threads_init
0번 cpu 를 제외한 cpu 의 idle 쓰레드도 초기화. 각 cpu 의 idle 쓰레드를 생성
void __init idle_threads_init(void)
{
unsigned int cpu, boot_cpu;
boot_cpu = smp_processor_id();
for_each_possible_cpu(cpu) {
if (cpu != boot_cpu)
idle_init(cpu);
}
}
5-27 kernel/smpboot.c idle_init
cpu 가 사용할 idle 쓰레드를 생성, 5.7.3 의 fork_idle 을 사용
static inline void idle_init(unsigned int cpu)
{
struct task_struct *tsk = per_cpu(idle_threads, cpu);
if (!tsk) {
tsk = fork_idle(cpu);
}
}
5.7 을 정리해보면, CPU당 하나의 idle 태스크만 존재하며, 일반 태스크는 idle 스케줄러를 사용하지 않는다. idle 태스크는 실행 가능한 다른 태스크가 없을 때만 실행. DL → RT → CFS → Idle
5-29 kernel/sched/core.c init_idle
idle 스케줄링용으로 생성된 task 를 초기화
// 스케줄링 필드들을 초기화, TASK_RUNNING 으로 설정하고, cpu 의 현재 상태를 idle 스케줄링으로 만듬
__sched_fork(0, idle);
idle->state = TASK_RUNNING;
// 실행 가능한 cpu 를 마스킹 (현재 cpu 만 사용 가능) cpu affinitty
set_cpus_allowed_common(idle, cpumask_of(cpu));
// task 의 cpu 를 지정하고, 현재 돌고있는 task 를 idle 로 지정
__set_task_cpu(idle, cpu);
rq->curr = rq->idle = idle;
// PREEMPT_ENABLED 설정
init_idle_preempt_count(idle, cpu);
PREEMPT_ENABLED
: 현재 실행 중인 태스크가 선점될 수 있는 상태, 언제든 중단 가능
5-30 kernel/sched/idle.c cpu_startup_entry
idle 루프 진입하기.
void cpu_startup_entry(enum cpuhp_state state)
{
arch_cpu_idle_prepare();
cpuhp_online_idle(state); // cpu 의 상태를 CPUHP_ONLINE_IDLE 로 설정. (800p 코드 7-28) , boot cpu 는 아무것도 안함
cpu_idle_loop(); // idle 루프로 진입 (5.7 Idle thread 의 동작)
}
5.7 마무리 : 각 cpu 별 IDLE 쓰레드 생성과, IDLE 쓰레드 루프에 진입
5.8 태스크 관련 자료구조의 필드 설명
task
- task 간의 관계
- 우선순위
- task 의 상태
- 스케줄링
- 태스크 식별 정보
- 파일 & 파일 시스템
- 유저 공간
- 아키텍처
thread_info
- 커널 엔트리에서 빠르게 접근하기 위한 정보
thread_struct
6 태스크 스케줄링
6.1 스케줄러의 주요 개념
load weight
테스크의 중요도를 나타내는 가중치
priority 가 낮을수록 중요하기 때문에 load weight 가 커짐.
task 의 실행시간, cpu 점유율, 사용 가능한 타임 슬라이스의 가중치로 동작
CFS 런큐에서는 load weight 의 합에 기여하는 비율로 결정
vruntime
스케줄러는 다음에 실행할 테스크를 선택할 때 vruntime 이 작은 task 를 선택
vruntime = real run time * 1024 / load weight
min_vruntime
vruntime 은 task 가 생성되고 계속 증가하기 때문에, 새로 생긴 task 의 vruntime 이 0 인 경우 오래 선점하게 된다.
이를 방지하기 위해 최소 vruntime 값을 지정하여 새로 생성됐거나 마이그레이션하는 테스크에 공정함을 유지한다.
기존에 돌던 task 들도 갱신 될 수 있을듯
6.1.2 스케줄링 레이턴시와 타임 슬라이스
scheduling latency = CFS 런큐에 enqueue 된 모든 태스크의 타임 슬라이스 합
task 의 time slice 는 scheduling latency 를 load weight 에 따라 나눠서 가짐
태스크 수가 sched_nr_latency 보다 적은 경우 기본 latency 를 사용, 그리고 task 수가 늘어나면 task 수를 곱해서 사용
6.1.3 런큐와 CFS 런큐, 레드 블랙 트리
cpu 의 런큐는 하나씩 존재하며, 서브 런큐로 CFS, RT, DL 를 가진다.
SCHED 정책에 따라 sub runqueue 가 정해짐
태스크는 vruntime 을 기준으로 정렬된 redblack tree 를 사용한다.
red black tree 를 사용하는 이유. 빨간 노드가 두개이상 있으면 안된다라는 규칙과 신규 노드를 빨간색으로 추가한다의 제약으로 트리의 높이가 제한됨
6.1.4 테스크 그룹과 스케줄링 엔티티
task 간은 공평하지만, 유저별로 task 수의 차이가 있다면, 유저 입장에서는 불공정
유저별로 task group 에 묶고, group 에 cpu 시간을 분배. cgroup 파일 시스템을 이용한 방법을 지원
task group 안에서도 task group 을 지정할 수 있음.
task 와 task group 모두 스케줄링의 단위로 지원하기 위해 스케줄링 엔티티라는 개념을 도입. (struct sched_entity)
따라서 task 나 taskgroup 을 enqueue 하지 않고, sched_entity 를 enqueue
struct sched_entity {
u64 vruntime;
struct sched_entity *parent; // 부모의 스케줄링 엔티티. (task 가 그룹에 속한 경우 parent 는 group 의 entity)
struct cfs_rq *cfs_rq; // entity 가 enqueue 될 cfs rq
/* rq "owned" by this entity/group: */
struct cfs_rq *my_q;
}
struct task_struct {
struct sched_entity se;
}
struct task_group {
struct sched_entity **se;
/* runqueue "owned" by this group on each cpu */
struct cfs_rq **cfs_rq;
}
parent 같이 관리하는 이유는 vruntime 같은걸 부모 노드가 갖고 있는 것 같다. rq 도 같은 그룹이지만 다른곳에 들어갈 수 있는 것 같다. task group 에 속한 task 가 부모를 계속 타고 올라가면서 rq 가 있으면 쓰는 것 같다. my_q 의 사용처는 아직 잘 모르겠음
root 태스크 그룹
별도의 그룹을 사용하지 않을 때, 모든 스케줄링 엔티티는 root 태스크 그룹에 enqueue 됨
자신만의 cfs rq 를 할당하지 않고, cpu 런큐의 cfs 서브 런큐를 사용
task 나 task group 은 root task group 에 속해야 한다. task 와 task group 은 sched_entity 를 가진다.
근데 task group 은 sched_entity 를 cpu 갯수별로 들고 있어서, 각 cpu 에서 돌때 사용한다.
task 는 하나의 cpu 에서 돌다가 옮겨가는 식이고, group 은 여러 task 가 다른 cpu 에서 돌 수 있어서 그런 것 같다.
아무래도 이거같다
cpu 가 sched_entity 를 하나 뽑음. 근데 이게 task 면 그냥 실행하고, task_group 이었으면 task 중에 하나만 실행하고 다시 넣음
6.1.5 스캐줄링 클래스
STOP - DL - RT - RAIR - IDLE
6.2 메인 스케줄러
6.2.1 타이머 인터럽트를 이용한 주기적인 스케줄링
tick handler 에서 타임 슬라이스를 소진했다면 스케줄링 요청
6-1 kernel/sched/core.c scheduler_tick
- runqueue 의 clock 을 갱신
- task_tick 에서 task 의 실행시간을 갱신하고, 타임슬라이스를 사용했다면 TIC_NEED_RESCHED 플래그 설정
- runqueue 의 load 설정 (너무 많아지면 밸런싱이 필요)
6.2.2 비주기적인 스케줄링
- 태스크가 깨어나는 경우
- 태스크가 명시적으로 양보하는 경우
- 우선순위가 변경된 경우 등등..
요청된 스케줄링 시도하기
- el0_irq
유저모드에서 인터럽트 발생 시, el0_irq 를 실행하고 돌아올때 TIF_NEED_RESCHED가 설정되어있다면 schedule 을 호출 - el1_irq
커널모드에서 인터럽트 발생한 경우, el1_irq 를 실행하고, 선점이 가능하다면 (유저 task 를 변경 가능한 상태 라면) 새로운 task 로 선점을 마치고 커널 코드를 종료. preempt_schedule_irq - el0_svc
유저모드에서 시스템 콜을 호출한 경우, 유저모드로 돌아가기전 - 커널 선점을 활성화 한 경우
긴 시간동안 일정 구간의 코드를 실행해야 하는 경우, 커널 선점을 비활성화한다. 커널 선점이 가능하도록 활성화 할때 스케줄링을 시도
명시적으로 스케줄링 시도하기
current 태스크가 대기 상태로 바뀌는 경우
sleep, blocking api 등을 사용했을때는 원하는 조건이 되기까지 cpu 를 사용하지 않아도 되기 때문에 스케줄링 함수를 호출한다.
6.2.3 스케줄링 시작하기
6-2 kernel/sched/core.c schedule
#define tif_need_resched() test_thread_flag(TIF_NEED_RESCHED)
asmlinkage __visible void __sched schedule(void)
{
do {
preempt_disable();
__schedule(false); // 6.2.5
sched_preempt_enable_no_resched(); // 원래는 선점 활성화 시 스케줄링을 돌리는데, 이건 스케줄링 된거라 안하는버전
} while (need_resched());
}
6-3 kernel/sched/core.c preempt_schedule_irq
IRQ 컨텍스트에서 인터럽트 처리 후 스케줄링 시도
asmlinkage __visible void __sched preempt_schedule_irq(void)
{
enum ctx_state prev_state;
/* Catch callers which need to be fixed */
BUG_ON(preempt_count() || !irqs_disabled());
prev_state = exception_enter();
do {
preempt_disable(); // 인터럽트 처리 후 선점 가능하다면 el1_preempt 가 동작하기 때문에, 선점 불가능하도록 하면 다시 인터럽트가 발생하더라도 el1_preempt 로 분기되지 않음
local_irq_enable(); // 이 함수 호출 전 irq 를 비활성화 했었는데, 스케줄링 중에는 인터럽트 처리 가능
__schedule(true);
local_irq_disable(); // 다시 원래대로 irq 활성화
sched_preempt_enable_no_resched();
} while (need_resched());
}
6.2.4 스케주링 요청하기, 요청 체크하여 스캐줄링 시도하기
'개발 > 코드로 알아보는 ARM 리눅스 커널 TIL' 카테고리의 다른 글
250201 6.2.4 스케줄링 요청하기 (0) | 2025.02.15 |
---|---|
20250111 5.5 테스크 생성하기 ~ 5.7 idle 쓰레드 (swapper) (0) | 2025.01.18 |
20250104 5.2.2 PID 할당 ~ 5.5 테스크 생성하기 (0) | 2025.01.11 |
20241221 4.9.5 per-cpu 동적 할당 (1) | 2025.01.04 |
20241214 4.9.3 per-cpu first_chunk 구성 & 초기화 & 동적할당 (1) | 2024.12.21 |
- Total
- Today
- Yesterday
- SuffixArray
- 에러 위치 찾기
- RVO
- hole-punching
- 카카오
- set value
- it's called a vrpit
- red underline
- shared_from_this
- Reciprocal n-body Collision Avoidance
- 우리는 vr핏이라고 부릅니다
- cockroach db
- 영상 픽셀화 하기
- Quest2
- boost
- Visual Studio
- C++
- vrpit
- 잘못된 빨간줄
- chrome-extension
- 코어 남기기
- 면접
- mysql
- 클래스 맴버 변수 출력하기
- ad skip
- Golang
- print shared_ptr class member variable
- vr핏
- Obstacle Avoidance
- 봄날에 스케치
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |