티스토리 뷰
6.3 CFS(Completely Fair Scheduling)
SCHED_NORMAL, SCHED_BATCH, SCHED_IDLE 정책을 사용하는 태스크를 처리하는 스케줄러
6.3.1 태스크를 런큐에 삽입하기
레드 블랙 트리에 enqueue
6-23 kernel/sched/fair.c enqueue_task_fair
static void enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
struct cfs_rq *cfs_rq;
struct sched_entity *se = &p->se;
for_each_sched_entity(se) { // 내 se 부터 부모 se 를 순회
if (se->on_rq) // se 가 rq 에 들어가있는 경우 멈춤
break;
cfs_rq = cfs_rq_of(se);
enqueue_entity(cfs_rq, se, flags); // 6-24
if (cfs_rq_throttled(cfs_rq)) // 바쁜 상태면 멈춤 -->?? 그러면 못넣은 애는 언제들어가게 될까?
break;
cfs_rq->h_nr_running++; // h (hierarchical) 그룹 내부에 counting 까지
flags = ENQUEUE_WAKEUP; // 가장 자식쪽에서 여길 왔다면 flag 가 ENQUEUE_WAKEUP 이 됨
}
// 그러면 이 se 는 없거나, rq 에 들어가서 멈췄거나, 바쁜 상태였음
for_each_sched_entity(se) {
cfs_rq = cfs_rq_of(se);
cfs_rq->h_nr_running++; // 자식에 entity 가 추가됐더라도 h_nr_running 은 추가됨
if (cfs_rq_throttled(cfs_rq))
break;
update_load_avg(se, 1); // load weight 계산, 두번째 인자는 task_group 까지 업데이트 할지. nice 로 계산 절대값
update_cfs_shares(cfs_rq); // 비율 계산
}
if (!se) // 최상위까지 모두 마쳤다면 (중간에 throttle 이 걸렸다면 se 가 남아있으니)
add_nr_running(rq, 1); // nr_running 증가
hrtick_update(rq);
}
6-24 kernel/sched/fair.c enqueue_entity
vruntime, load weight 등 반영
이 함수는 on_rq 가 아닐때만 들어온거.
static void
enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
// re normalize 하지 않는 케이스가 있음
// 예를들어 위 코드의, 상위 스케줄링 엔테티티를 하는 경우
bool renorm = !(flags & ENQUEUE_WAKEUP) || (flags & ENQUEUE_WAKING);
bool curr = cfs_rq->curr == se; // rq 의 현재 task 가 se 인지 여부
// 현재 task 인 경우 update_curr 하기전에 미리 vruntime 을 갱신
if (renorm && curr)
se->vruntime += cfs_rq->min_vruntime;
update_curr(cfs_rq);
// 다른 task 를 넣는 경우, update_curr 을 하고 vruntime 을 갱신
if (renorm && !curr)
se->vruntime += cfs_rq->min_vruntime;
enqueue_entity_load_avg(cfs_rq, se); // rq 에 load 평균을
account_entity_enqueue(cfs_rq, se); // rq 에 se 의 weight 를 추가.
update_cfs_shares(cfs_rq);
if (flags & ENQUEUE_WAKEUP) { // 잠들어있던 task 의 vruntime 독점하지 않도록 조정
place_entity(cfs_rq, se, 0);
if (schedstat_enabled())
enqueue_sleeper(cfs_rq, se); // stat 쪽은 통계
}
if (!curr)
__enqueue_entity(cfs_rq, se); // 현재 task 가 아니었다면, 실제 런큐(rb 트리) 에 추가
se->on_rq = 1; // 만약 현재 task 였다면 어차피 돌고있던거니까 on_rq 는 무조건 1임
if (cfs_rq->nr_running == 1) { // rq 에 처음으로 실행이 가능해졌을때
list_add_leaf_cfs_rq(cfs_rq); // 특정 CFS 그룹을 leaf 노드로 관리함.
check_enqueue_throttle(cfs_rq); // When a group wakes up we want to make sure that its quota is not already. 깨어났을때 해제
}
}
root_cfs_rq (on_list == 1)
├── cfs_rq_A (on_list == 1) <-- 실행할 태스크가 있음
│ ├── cfs_rq_A1 (on_list == 1) <-- 실행할 태스크가 있음
│ ├── cfs_rq_A2 (on_list == 0) <-- 실행할 태스크가 없음 (or throttled)
│
├── cfs_rq_B (on_list == 0) <-- 실행할 태스크가 없음
6-25 kernel/sched/fair.c place_entity
static void
place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int initial)
{
u64 vruntime = cfs_rq->min_vruntime;
if (initial && sched_feat(START_DEBIT))
vruntime += sched_vslice(cfs_rq, se); // 다른 task 를 배려함
if (!initial) { // 처음 enqueue 가 아니라면
unsigned long thresh = sysctl_sched_latency;
/*
* Halve their sleep time's effect, to allow
* for a gentler effect of sleepers:
*/
if (sched_feat(GENTLE_FAIR_SLEEPERS)) // 런큐에서 아예 빠져서 자다가 돌아온 애임
thresh >>= 1; // 자다가 깼는데 너네 배려해서 일부러 vruntime 적게 뺄깨
vruntime -= thresh; // vruntime 이 작으면 더 빨리 깨어남
}
se->vruntime = max_vruntime(se->vruntime, vruntime);
}
6-26 kernel/sched/fair.c __enqueue_entity
rb tree 에 insert
struct cfs_rq {
struct rb_root tasks_timeline;
}
struct sched_entity {
struct rb_node run_node;
}
static void __enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
rb_node **link = cfs->rq->task_timeline.rb_node
entry = rb_entry(parent, struct sched_entity, run_node);
// rb tree insert
}
6.3.2 태스크를 런큐에서 선택하기
current task 가 timeslice 를 소진한다. (vruntime & timeslice 개념이 아직 확실치 않음)
6-27 kernel/sched/core.c pick_next_task
static inline struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev)
{
const struct sched_class *class = &fair_sched_class;
struct task_struct *p;
/*
* Optimization: we know that if all tasks are in
* the fair class we can call that function directly:
*/
if (likely(prev->sched_class == class && // 일반적으로 fair scheduler 를 사용
rq->nr_running == rq->cfs.h_nr_running)) { // 현재 rq 에 fair scheduling 만을 사용하는 task 가 돌고 있다면
p = fair_sched_class.pick_next_task(rq, prev); // 6-28. 런큐에서 가져오거나 로드밸런싱을 통해 가져올 수도 있음
if (unlikely(p == RETRY_TASK)) // normal 태스크보다 더 높은 task 를 가져왔다면
goto again;
/* assumes fair_sched_class->next == idle_sched_class */
if (unlikely(!p)) // 태스크가 없다면 idle 쓰레드를 선택
p = idle_sched_class.pick_next_task(rq, prev);
return p;
}
again:
for_each_class(class) { // 우선순위가 높은 스케줄러를 차례대로 돌며 task 찾기
p = class->pick_next_task(rq, prev);
if (p) {
if (unlikely(p == RETRY_TASK))
goto again;
return p;
}
}
BUG(); /* the idle class will always have a runnable task */
}
6-28 kernel/sched/fair.c pick_next_task_fair 1
태스크를 찾는 과정
- current task 가 normal task 인 경우
- current 태스크가 normal task 가 아닌 경우 : 마무리 작업이 생략되어 단순
- CFS 런큐에 실행할 task 가 없는 경우 : 다른 cpu 의 런큐에서 마이그레이션
current_task -> enqueue 됨
next_task -> current_task 가 됨
static struct task_struct *
pick_next_task_fair(struct rq *rq, struct task_struct *prev)
{
struct cfs_rq *cfs_rq = &rq->cfs;
struct sched_entity *se;
struct task_struct *p;
int new_tasks;
again:
#ifdef CONFIG_FAIR_GROUP_SCHED
if (!cfs_rq->nr_running) // run queue 에 enqueue 된 entity 가 없다면
goto idle; // idle 로 점프하여 migration 할 수 있도록 한다
if (prev->sched_class != &fair_sched_class) // normal task 가 아니었다면
goto simple; // 복잡한 과정 없이 next task 만 찾는다
/*
* Because of the set_next_buddy() in dequeue_task_fair() it is rather
* likely that a next task is from the same cgroup as the current.
*
* Therefore attempt to avoid putting and setting the entire cgroup
* hierarchy, only change the part that actually changes.
*/
do {
struct sched_entity *curr = cfs_rq->curr; // 현재 돌고있던 entity
/*
* Since we got here without doing put_prev_entity() we also
* have to consider cfs_rq->curr. If it is still a runnable
* entity, update_curr() will update its vruntime, otherwise
* forget we've ever seen it.
*/
if (curr) {
if (curr->on_rq) // 아직 dequeue 되지 않았다면
update_curr(cfs_rq); // vruntime 을 갱신
else
curr = NULL;
/*
* This call to check_cfs_rq_runtime() will do the
* throttle and dequeue its entity in the parent(s).
* Therefore the 'simple' nr_running test will indeed
* be correct.
*/
if (unlikely(check_cfs_rq_runtime(cfs_rq)))
goto simple;
}
se = pick_next_entity(cfs_rq, curr); // 6-30 다음 entity 찾기
cfs_rq = group_cfs_rq(se); // 그룹인 경우 자식을 찾아가면서 처리
} while (cfs_rq);
p = task_of(se); // se 를 사용하는 task
/*
* Since we haven't yet done put_prev_entity and if the selected task
* is a different task than we started out with, try and touch the
* least amount of cfs_rqs.
*/
if (prev != p) { // 다른 task 라면
struct sched_entity *pse = &prev->se; // p (current), se(next)
while (!(cfs_rq = is_same_group(se, pse))) { // se, pse 의 그룹이 같아질때까지 검색 (왜냐면 더 높은 그룹이면 이미 enqueue 되어있는거니까)
int se_depth = se->depth;
int pse_depth = pse->depth;
if (se_depth <= pse_depth) {
put_prev_entity(cfs_rq_of(pse), pse);
pse = parent_entity(pse);
}
if (se_depth >= pse_depth) {
set_next_entity(cfs_rq_of(se), se);
se = parent_entity(se);
}
}
put_prev_entity(cfs_rq, pse);
set_next_entity(cfs_rq, se);
}
if (hrtick_enabled(rq))
hrtick_start_fair(rq, p);
return p;
target group 의 cfs_rq 는 cpu 개수만큼 존재
struct cfs_rq *cfs_rq = tg->cfs_rq[i];
6-28 kernel/sched/fair.c pick_next_task_fair 2
simple:
cfs_rq = &rq->cfs;
#endif
if (!cfs_rq->nr_running) // entity 가 없다면 migration 시도
goto idle;
put_prev_task(rq, prev); // 부모방향으로 가며 curr 을 null 로 초기화. rq 에 enqueue
do {
se = pick_next_entity(cfs_rq, NULL); // 다음에 실행할 태스크를 top-down 으로 검색 6-30
set_next_entity(cfs_rq, se); // current entity 로 설정
cfs_rq = group_cfs_rq(se); // ..? 여기두
} while (cfs_rq);
p = task_of(se);
if (hrtick_enabled(rq))
hrtick_start_fair(rq, p);
return p;
idle:
/*
* This is OK, because current is on_cpu, which avoids it being picked
* for load-balance and preemption/IRQs are still disabled avoiding
* further scheduler activity on it and we're being very careful to
* re-start the picking loop.
*/
lockdep_unpin_lock(&rq->lock);
new_tasks = idle_balance(rq); // 다른 cpu 에서 task 가져오기
lockdep_pin_lock(&rq->lock);
/*
* Because idle_balance() releases (and re-acquires) rq->lock, it is
* possible for any higher priority task to appear. In that case we
* must re-start the pick_next_entity() loop.
*/
if (new_tasks < 0)
return RETRY_TASK; // 더 높은 우선순위
if (new_tasks > 0)
goto again;
return NULL; // 태스크가 없음
}
6-30 kernel/sched/fair.c pick_next_entity
vruntime 이 작거나 buddy 로 설정된 sched_entity 가져오기
static struct sched_entity *
pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
struct sched_entity *left = __pick_first_entity(cfs_rq); // get leftmost
if (!left || (curr && entity_before(curr, left))) left = curr; // left 가 없거나 curr vruntime 이 더 작음
se = left; /* ideally we run the leftmost entity */
/*
* Avoid running the skip buddy, if running something else can
* be done without getting too unfair.
*/
if (cfs_rq->skip == se) { // yield_task_fair 등에서 skip 을 설정함
struct sched_entity *second;
if (se == curr) { // curr 이었던 경우는 어차피 rb 에 없었음
second = __pick_first_entity(cfs_rq);
} else {
second = __pick_next_entity(se); // left 다음 노드
if (!second || (curr && entity_before(curr, second)))
second = curr;
}
if (second && wakeup_preempt_entity(second, left) < 1) // second 가 더 많이 돌았지만, 차이가 많이 안나는 경우
se = second;
}
/*
* Prefer last buddy, try to return the CPU to a preempted task.
*/
if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1)
se = cfs_rq->last; // 최근에 돌았던 se 를 우선 선택
/*
* Someone really wants this to run. If it's not unfair, run it.
*/
if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1)
se = cfs_rq->next; // buddy 로 설정된것
clear_buddies(cfs_rq, se); // 선택된 last, next, skip 을 제거
return se;
}
6-31 kernel/sched/fair.c put_prev_entity
static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev)
{
if (prev->on_rq) // 넣기전에 돌고있었다면 업데이트
update_curr(cfs_rq);
/* throttle cfs_rqs exceeding runtime */
check_cfs_rq_runtime(cfs_rq); // 너무 많이 돌았다면 throttle 걸기
if (schedstat_enabled()) {
check_spread(cfs_rq, prev);
if (prev->on_rq)
update_stats_wait_start(cfs_rq, prev);
}
if (prev->on_rq) {
/* Put 'current' back into the tree. */
__enqueue_entity(cfs_rq, prev); // rb 트리에 넣기
/* in !on_rq case, update occurred at dequeue */
update_load_avg(prev, 0 /*update_tg*/);
}
cfs_rq->curr = NULL; // 돌고있는 entity 를 null 로 설정
}
on_rq : 태스크가 CFS 런큐에 있는지 여부, 즉 실행 대기 상태인지를 나타냅니다. (rq 에 있거나 rb 트리에 enqueue)
6-32 kernel/sched/fair.c set_next_entity
static void
set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
/* 'current' is not kept within the tree. */
if (se->on_rq) {
/*
* Any task has to be enqueued before it get to execute on
* a CPU. So account for the time it spent waiting on the
* runqueue.
*/
if (schedstat_enabled())
update_stats_wait_end(cfs_rq, se);
__dequeue_entity(cfs_rq, se); // 트리에서 dequeue
update_load_avg(se, 1);
}
update_stats_curr_start(cfs_rq, se);
cfs_rq->curr = se; // 현재 task 를 설정
se->prev_sum_exec_runtime = se->sum_exec_runtime; // 백업
6.3.3 태스크를 런큐에서 제거하기
더이상 CPU 를 사용하지 않을때
6-33 kernel/sched/fair.c dequeue_task_fair
static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
struct cfs_rq *cfs_rq;
struct sched_entity *se = &p->se;
int task_sleep = flags & DEQUEUE_SLEEP; // TASK_RUNNING 이 아닌상태로 스위칭 되거나, throttling 이 걸린 경우
for_each_sched_entity(se) { // bottom-up
cfs_rq = cfs_rq_of(se);
dequeue_entity(cfs_rq, se, flags); // rb 트리에서 제거 & on_rq = 0
if (cfs_rq_throttled(cfs_rq))
break;
cfs_rq->h_nr_running--;
/* Don't dequeue parent if it has other entities besides us */
if (cfs_rq->load.weight) { // 그룹에 weight 가 남아있는 경우, 순회를 멈춘다
if (task_sleep && parent_entity(se)) // sleep 요청이 오면서 root group 이 아닌경우
set_next_buddy(parent_entity(se)); // 같은 그룹이 먼저 실행 될 수 있도록 buddy 등록
/* avoid re-evaluating load for this entity */
se = parent_entity(se);
break;
}
flags |= DEQUEUE_SLEEP;
}
for_each_sched_entity(se) { // load 정보 갱신
cfs_rq = cfs_rq_of(se);
cfs_rq->h_nr_running--;
if (cfs_rq_throttled(cfs_rq))
break;
update_load_avg(se, 1);
update_cfs_shares(cfs_rq);
}
if (!se)
sub_nr_running(rq, 1);
hrtick_update(rq);
}
6-34 kernel/sched/fair.c dequeue_entity
static void
dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
update_curr(cfs_rq);
dequeue_entity_load_avg(cfs_rq, se);
if (schedstat_enabled())
update_stats_dequeue(cfs_rq, se, flags);
clear_buddies(cfs_rq, se);
if (se != cfs_rq->curr) // curr 인 경우 설정되는 과정에서 이미 dequeue 됨
__dequeue_entity(cfs_rq, se);
se->on_rq = 0;
account_entity_dequeue(cfs_rq, se);
/*
* Normalize the entity after updating the min_vruntime because the
* update can refer to the ->curr item and we need to reflect this
* movement in our normalized position.
*/
if (!(flags & DEQUEUE_SLEEP))
se->vruntime -= cfs_rq->min_vruntime;
/* return excess runtime on last dequeue */
return_cfs_rq_runtime(cfs_rq);
update_min_vruntime(cfs_rq); // min_vruntime 을 갱신
update_cfs_shares(cfs_rq);
}
se->on_rq / task->on_rq 다른듯
6.3.4 주기적으로 발생하는 틱 처리하기
6-35 kernel/sched/fair.c task_tick_fair
void scheduler_tick(void) // 타이머 인터럽트(틱) 에서 호출
{
curr->sched_class->task_tick(rq, curr, 0);
}
6-36 kernel/sched/fair.c entity_tick
스케줄링 정보 갱신 & currenty 엔티티가 충분히 돌았다면 선점 요청
6-37 kernel/sched/fair.c check_preempt_tick
틱마다 호출되어 current entity 를 선점 가능한지 확인
- current 가 타임 슬라이스 이상으로 실행한 경우
- current 가 최소 보장 시간보다 더 실행됐고, leftmost 와 차이가 타임 슬라이스 이상인 경우
static void check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
ideal_runtime = sched_slice(cfs_rq, curr); // 이상적 실행시간
delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
if (delta_exec > ideal_runtime) {...}
}
'개발 > 코드로 알아보는 ARM 리눅스 커널 TIL' 카테고리의 다른 글
250317 SMP 를 위한 커널지원 & cpu topology(7.1~7.2) (0) | 2025.03.17 |
---|---|
250308 스케줄링 엔티티의 실행시간 관리하기 & 스케줄러 초기화 (6.3~6.4) (2) | 2025.03.08 |
250222 6.2.6 태스크 깨우기 (ttwu) (0) | 2025.02.22 |
250208 6.2.4 스케줄링 요청하기 (0) | 2025.02.15 |
250201 5.7 IDLE 쓰레드 초기화 ~ 6.2 스케줄링 (1) | 2025.02.08 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- print shared_ptr class member variable
- 코어 남기기
- 영상 픽셀화 하기
- RVO
- 카카오
- red underline
- 잘못된 빨간줄
- Reciprocal n-body Collision Avoidance
- Golang
- it's called a vrpit
- mysql
- 클래스 맴버 변수 출력하기
- Quest2
- vrpit
- 봄날에 스케치
- vr핏
- shared_from_this
- cockroach db
- Obstacle Avoidance
- 우리는 vr핏이라고 부릅니다
- SuffixArray
- C++
- hole-punching
- Visual Studio
- boost
- chrome-extension
- set value
- 면접
- ad skip
- 에러 위치 찾기
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
29 | 30 |
글 보관함