티스토리 뷰
6.3 CFS(Completely Fair Scheduling)
SCHED_NORMAL, SCHED_BATCH, SCHED_IDLE 정책을 사용하는 태스크를 처리하는 스케줄러
6.3.5 스케줄링 엔티티의 실행시간 관리하기
- 실제 실행시간 : exec_start, sum_exec_start
- 가상 실행시간 : vruntime, min_vruntime
6-38 kernel/sched/fair.c update_curr
current task 의 sched_entity 와 run queue 의 런타임 정보 갱신
static void update_curr(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr = cfs_rq->curr;
curr->exec_start = now; // 마지막 실행 시간
curr->sum_exec_runtime += delta_exec; // 총 실행 시간
curr->vruntime += calc_delta_fair(delta_exec, curr); // load weight 에 따라 delta 값이 달라짐.
update_min_vruntime(cfs_rq);
}
6-39 kernel/sched/fair.c calc_delta_fair
load weight 가 클수록 delta 는 작아짐 (vruntime 이 적게 추가되기 때문에 더 많이 돌게됨)
static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
{
if (unlikely(se->load.weight != NICE_0_LOAD))
delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
return delta;
}
// sched.h
#if 0 /* BITS_PER_LONG > 32 -- currently broken: it increases power usage under light load */
# define SCHED_LOAD_RESOLUTION 10
# define scale_load(w) ((w) << SCHED_LOAD_RESOLUTION)
# define scale_load_down(w) ((w) >> SCHED_LOAD_RESOLUTION)
#else
# define SCHED_LOAD_RESOLUTION 0
# define scale_load(w) (w)
# define scale_load_down(w) (w)
#endif
#define SCHED_LOAD_SHIFT (10 + SCHED_LOAD_RESOLUTION)
#define SCHED_LOAD_SCALE (1L << SCHED_LOAD_SHIFT)
6-40 kernel/sched/fair.c __calc_delta
- (delta * weight) / loadweight 인데 나누기를 계산하지 못함.
- (delta * weight * loadwegiht.inv) >> WMULT_SHIF
#define WMULT_CONST (~0U) // 0xFFFF
#define WMULT_SHIFT 32
static u64 __calc_delta(u64 delta_exec, unsigned long weight, struct load_weight *lw)
{
u64 fact = scale_load_down(weight);
int shift = WMULT_SHIFT; // 32 위 define 에 따라서 inv 를 32비트 기준으로 하기 때문
__update_inv_weight(lw);
// lw.weight 가 겁나크면 Inv_weight = 1
// lw.weight 가 겁나작으면 inv_weight = 0xFFFF
// lw.weight 가 적당하면 inv_weight = 0xFFFF / lw.weight
if (unlikely(fact >> 32)) { // fact 가 32비트보다 큰 경우 한칸씩 shift 해줌
while (fact >> 32) {
fact >>= 1;
shift--;
}
}
/* hint to use a 32x32->64 mul */
fact = (u64)(u32)fact * lw->inv_weight; // weight * loadweight.inv
while (fact >> 32) {
fact >>= 1;
shift--;
}
return mul_u64_u32_shr(delta_exec, fact, shift); // a * b >> shift
}
struct load_weight {
unsigned long weight;
u32 inv_weight;
};
static void __update_inv_weight(struct load_weight *lw)
{
unsigned long w;
if (likely(lw->inv_weight)) // 캐싱. weight 가 변경될때 갱신해줘야함
return;
w = scale_load_down(lw->weight); // w >> SCHED_LOAD_RESOLUTION
if (BITS_PER_LONG > 32 && unlikely(w >= WMULT_CONST)) // w 가 겁나 큰 경우
lw->inv_weight = 1;
else if (unlikely(!w)) // w 가 작은경우
lw->inv_weight = WMULT_CONST;
else // 적당한 경우일때는 inverse
lw->inv_weight = WMULT_CONST / w;
}
// 여기서 inv_weight 가 갱신됐었다.
static void set_load_weight(struct task_struct *p)
{
load->weight = scale_load(sched_prio_to_weight[prio]); // 우선순위가 높을수록 높은 weight 를 사용. (이후 스케줄링에서 다룸)
load->inv_weight = sched_prio_to_wmult[prio];
}
6.3.6 타임 슬라이스 관리하기
6-41 kernel/sched/fair.c sched_slice
entity 가 사용할 수 있는 time slice 계산하기.
CFS 런큐에 속한 entity 의 time sllice 의 합은, CFS 런큐의 스케줄링 레이턴시와 같다.
/*
* We calculate the wall-time slice from the period by taking a part
* proportional to the weight.
*
* s = p*P[w/rw]
*/
static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq); // 6-42
for_each_sched_entity(se) { // 상위 그룹을 찾아가면서
struct load_weight *load;
struct load_weight lw;
cfs_rq = cfs_rq_of(se);
load = &cfs_rq->load; // rq 의 load
if (unlikely(!se->on_rq)) { // 아직 enqueue 되지 않았다면
lw = cfs_rq->load;
update_load_add(&lw, se->load.weight); // 내 weight 를 더해준 다음에 slice 를 계산한다.
load = &lw;
}
slice = __calc_delta(slice, se->load.weight, load); // (slice * se 의 weight / 그룹의 load)
}
return slice;
}
6-42 kernel/sched/fair.c __sched_period
/*
* The idea is to set a period in which each task runs once.
*
* When there are too many tasks (sched_nr_latency) we have to stretch
* this period because otherwise the slices get too small.
*
* p = (nr <= nl) ? l : l*nr/nl
*/
static u64 __sched_period(unsigned long nr_running) // 돌고있는 task 수
{
// sched_nr_latency. deafult = 8.
if (unlikely(nr_running > sched_nr_latency))
return nr_running * sysctl_sched_min_granularity; // 태스크 개수만큼 크게 주고, 계산하면서 점점 작아짐
else
return sysctl_sched_latency; // task 가 적은 경우 미리 정해진 주기
}
- sched_vslice : load weight 가중치를 사용하여 타임 슬라이스를 변환
6-43 kernel/sched/fair.c update_sysctl
- 레이턴시 계산하는 변수 값을 cpu 개수에 맞게 설정
sysctl_sched_tunable_scaling { SCHED_TUNABLESCALING_NONE, // factor = 1 SCHED_TUNABLESCALING_LINEAR, // factor = cpu SCHED_TUNABLESCALING_LOG, // factor = 1 + log(cpu) }
#define SET_SYSCTL(name)
(sysctl_##name = (factor) * normalized_sysctl_##name)
SET_SYSCTL(sched_min_granularity);
SET_SYSCTL(sched_latency);
SET_SYSCTL(sched_wakeup_granularity);
#undef SET_SYSCTL
## 6.4 쓰케줄러 초기화하기
### 6.4.1 sched_init 스케줄러 초기화하기S(Completely Fair Scheduling)
SCHED_NORMAL, SCHED_BATCH, SCHED_IDLE 정책을 사용하는 태스크를 처리하는 스케줄러
### 6.3.5 스케줄링 엔티티의 실행시간 관리하기
- 실제 실행시간 : exec_start, sum_exec_start
- 가상 실행시간 : vruntime, min_vruntime
#### 6-38 kernel/sched/fair.c update_curr
current task 의 sched_entity 와 run queue 의 런타임 정보 갱신
```c++
static void update_curr(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr = cfs_rq->curr;
curr->exec_start = now; // 마지막 실행 시간
curr->sum_exec_runtime += delta_exec; // 총 실행 시간
curr->vruntime += calc_delta_fair(delta_exec, curr); // load weight 에 따라 delta 값이 달라짐.
update_min_vruntime(cfs_rq);
}
6-39 kernel/sched/fair.c calc_delta_fair
load weight 가 클수록 delta 는 작아짐 (vruntime 이 적게 추가되기 때문에 더 많이 돌게됨)
static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se)
{
if (unlikely(se->load.weight != NICE_0_LOAD))
delta = __calc_delta(delta, NICE_0_LOAD, &se->load);
return delta;
}
// sched.h
#if 0 /* BITS_PER_LONG > 32 -- currently broken: it increases power usage under light load */
# define SCHED_LOAD_RESOLUTION 10
# define scale_load(w) ((w) << SCHED_LOAD_RESOLUTION)
# define scale_load_down(w) ((w) >> SCHED_LOAD_RESOLUTION)
#else
# define SCHED_LOAD_RESOLUTION 0
# define scale_load(w) (w)
# define scale_load_down(w) (w)
#endif
#define SCHED_LOAD_SHIFT (10 + SCHED_LOAD_RESOLUTION)
#define SCHED_LOAD_SCALE (1L << SCHED_LOAD_SHIFT)
6-40 kernel/sched/fair.c __calc_delta
- (delta * weight) / loadweight 인데 나누기를 계산하지 못함.
- (delta * weight * loadwegiht.inv) >> WMULT_SHIF
#define WMULT_CONST (~0U) // 0xFFFF
#define WMULT_SHIFT 32
static u64 __calc_delta(u64 delta_exec, unsigned long weight, struct load_weight *lw)
{
u64 fact = scale_load_down(weight);
int shift = WMULT_SHIFT; // 32 위 define 에 따라서 inv 를 32비트 기준으로 하기 때문
__update_inv_weight(lw);
// lw.weight 가 겁나크면 Inv_weight = 1
// lw.weight 가 겁나작으면 inv_weight = 0xFFFF
// lw.weight 가 적당하면 inv_weight = 0xFFFF / lw.weight
if (unlikely(fact >> 32)) { // fact 가 32비트보다 큰 경우 한칸씩 shift 해줌
while (fact >> 32) {
fact >>= 1;
shift--;
}
}
/* hint to use a 32x32->64 mul */
fact = (u64)(u32)fact * lw->inv_weight; // weight * loadweight.inv
while (fact >> 32) {
fact >>= 1;
shift--;
}
return mul_u64_u32_shr(delta_exec, fact, shift); // a * b >> shift
}
struct load_weight {
unsigned long weight;
u32 inv_weight;
};
static void __update_inv_weight(struct load_weight *lw)
{
unsigned long w;
if (likely(lw->inv_weight)) // 캐싱. weight 가 변경될때 갱신해줘야함
return;
w = scale_load_down(lw->weight); // w >> SCHED_LOAD_RESOLUTION
if (BITS_PER_LONG > 32 && unlikely(w >= WMULT_CONST)) // w 가 겁나 큰 경우
lw->inv_weight = 1;
else if (unlikely(!w)) // w 가 작은경우
lw->inv_weight = WMULT_CONST;
else // 적당한 경우일때는 inverse
lw->inv_weight = WMULT_CONST / w;
}
// 여기서 inv_weight 가 갱신됐었다.
static void set_load_weight(struct task_struct *p)
{
load->weight = scale_load(sched_prio_to_weight[prio]); // 우선순위가 높을수록 높은 weight 를 사용. (이후 스케줄링에서 다룸)
load->inv_weight = sched_prio_to_wmult[prio];
}
6.3.6 타임 슬라이스 관리하기
6-41 kernel/sched/fair.c sched_slice
entity 가 사용할 수 있는 time slice 계산하기.
CFS 런큐에 속한 entity 의 time sllice 의 합은, CFS 런큐의 스케줄링 레이턴시와 같다.
/*
* We calculate the wall-time slice from the period by taking a part
* proportional to the weight.
*
* s = p*P[w/rw]
*/
static u64 sched_slice(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
u64 slice = __sched_period(cfs_rq->nr_running + !se->on_rq); // 6-42
for_each_sched_entity(se) { // 상위 그룹을 찾아가면서
struct load_weight *load;
struct load_weight lw;
cfs_rq = cfs_rq_of(se);
load = &cfs_rq->load; // rq 의 load
if (unlikely(!se->on_rq)) { // 아직 enqueue 되지 않았다면
lw = cfs_rq->load;
update_load_add(&lw, se->load.weight); // 내 weight 를 더해준 다음에 slice 를 계산한다.
load = &lw;
}
slice = __calc_delta(slice, se->load.weight, load); // (slice * se 의 weight / 그룹의 load)
}
return slice;
}
6-42 kernel/sched/fair.c __sched_period
/*
* The idea is to set a period in which each task runs once.
*
* When there are too many tasks (sched_nr_latency) we have to stretch
* this period because otherwise the slices get too small.
*
* p = (nr <= nl) ? l : l*nr/nl
*/
static u64 __sched_period(unsigned long nr_running) // 돌고있는 task 수
{
// sched_nr_latency. deafult = 8.
if (unlikely(nr_running > sched_nr_latency))
return nr_running * sysctl_sched_min_granularity; // 태스크 개수만큼 크게 주고, 계산하면서 점점 작아짐
else
return sysctl_sched_latency; // task 가 적은 경우 미리 정해진 주기
}
- sched_vslice : load weight 가중치를 사용하여 타임 슬라이스를 변환
6-43 kernel/sched/fair.c update_sysctl
- 레이턴시 계산하는 변수 값을 cpu 개수에 맞게 설정
sysctl_sched_tunable_scaling { SCHED_TUNABLESCALING_NONE, // factor = 1 SCHED_TUNABLESCALING_LINEAR, // factor = cpu SCHED_TUNABLESCALING_LOG, // factor = 1 + log(cpu) }
#define SET_SYSCTL(name)
(sysctl_##name = (factor) * normalized_sysctl_##name)
SET_SYSCTL(sched_min_granularity);
SET_SYSCTL(sched_latency);
SET_SYSCTL(sched_wakeup_granularity);
#undef SET_SYSCTL
## 6.4 쓰케줄러 초기화하기
### 6.4.1 sched_init 스케줄러 초기화하기
#### kernel/sched/core.c sched_init 1
- root 태스크 그룹 초기화
1. 자신만의 entity 나 cfs 런큐를 가지고 있지 않다. (enqueue 할 상위 run queue 가 없음)
2. 대신 possible cpu 의 CFS 런큐를 가리키고 있다.
```c++
void __init sched_init(void)
{
alloc_size += 2 * nr_cpu_ids * sizeof(void **);
if (alloc_size) {
// sched_entity[possible cpu], cfs_rq[possible_cpu]
ptr = (unsigned long)kzalloc(alloc_size, GFP_NOWAIT);
root_task_group.se = (struct sched_entity **)ptr;
ptr += nr_cpu_ids * sizeof(void **);
root_task_group.cfs_rq = (struct cfs_rq **)ptr;
ptr += nr_cpu_ids * sizeof(void **);
}
task_group_cache = KMEM_CACHE(task_group, 0); // task_group 생성하는 slab cache. 이후 group 추가시 사용
list_add(&root_task_group.list, &task_groups); // 모든 Taskgroup 연결하는 task_groups 에 root 추가
INIT_LIST_HEAD(&root_task_group.children);
INIT_LIST_HEAD(&root_task_group.siblings);
for_each_possible_cpu(i) {
struct rq *rq;
rq = cpu_rq(i); // per-cpu
init_tg_cfs_entry(&root_task_group, &rq->cfs, NULL, i, NULL); // 6-45
// pseudo
// root_tg.cfs_rq[i] = cpu[i].rq
// root_tg.sched_entity[i] = null
// cpu[i].tg = root_tg;
}
}
6-45 kernel/sched/fair.c init_tg_cfs_entry
void init_tg_cfs_entry(struct task_group *tg, struct cfs_rq *cfs_rq,
struct sched_entity *se, int cpu,
struct sched_entity *parent)
{
struct rq *rq = cpu_rq(cpu); // cpu index ->cpu rq
// cpu i 의 런큐 안에 cfs_rq 가 들어있음
cfs_rq->tg = tg;
cfs_rq->rq = rq;
init_cfs_rq_runtime(cfs_rq);
// root tg 에서도 가리킴. 근데 root tg 의 .se(sched_entity) 가 뭘까? 왜 cpu 갯수만큼 일까? 실행중인 entity 가 나중에 여기로 오나?
// 그와중에 아래에서는 !se 면 return 임. 나중에 확인해봐야 할듯
// root_task_group 는 se 를 갖고있지 않다.
tg->cfs_rq[cpu] = cfs_rq;
tg->se[cpu] = se;
/* se could be NULL for root_task_group */
if (!se)
return;
if (!parent) {
se->cfs_rq = &rq->cfs;
se->depth = 0;
} else {
se->cfs_rq = parent->my_q; // 내 rq 는 부모(group) 의 my_q 를 가리키게 하자
se->depth = parent->depth + 1;
}
se->my_q = cfs_rq; // task group 소유의 CFS 런큐를 설정?
/* guarantee group entities always have weight */
update_load_set(&se->load, NICE_0_LOAD);
se->parent = parent; // 부모 태스크 그룹 entity 설정
}
/*
* This is the main, per-CPU runqueue data structure.
*
* Locking rule: those places that want to lock multiple runqueues
* (such as the load balancing or the thread migration code), lock
* acquire operations must be ordered by ascending &runqueue.
*/
struct rq { // cpu 별로 갖고있음
struct cfs_rq cfs; // rq 안에 cfs_rq 존재
}
struct cfs_rq {
#ifdef CONFIG_FAIR_GROUP_SCHED
struct rq *rq; /* cpu runqueue to which this cfs_rq is attached */
struct task_group *tg; /* group that "owns" this runqueue */
#end
}
struct task_group {
#ifdef CONFIG_FAIR_GROUP_SCHED
/* schedulable entities of this group on each cpu */
struct sched_entity **se;
/* runqueue "owned" by this group on each cpu */
struct cfs_rq **cfs_rq;
#endif
}
struct sched_entity {
#ifdef CONFIG_FAIR_GROUP_SCHED
int depth;
struct sched_entity *parent;
/* rq on which this entity is (to be) queued: */
struct cfs_rq *cfs_rq;
/* rq "owned" by this entity/group: */
struct cfs_rq *my_q;
#endif
}
일반 task 인 경우
- task->se->cfs_rq 는 cpu 의 cfs_rq
- task->se->my_q 는 null
root task group 이 아닌 task group 인 경우.
- tg->se[i] 가 sched_entity
- tg->se[i]->my_q 가 내 cfs_rq
- tg->se[i]->my_q->tg 다시 나를 가리킴
6-46, 6-47 kernel/sched/core.c sched_init 2, 3
for_each_possible_cpu(i) {
struct rq *rq;
// rq 초기화
}
autogroup_init(&init_task);
set_load_weight(&init_task); // swapper 는 nice 0 으로 load weight 1024
current->sched_class = &fair_sched_class; // swapper 의 스케줄링 클래스를 fair 로 설정
init_idle(current, smp_processor_id()); // swapper 를 idle trhread 로 사용
idle_thread_set_boot_cpu(); // per-cpu idl_threads 를 cpu 0 의 idle 쓰레드로 설정
init_sched_fair_class();
scheduler_running = 1;
}
'개발 > 코드로 알아보는 ARM 리눅스 커널 TIL' 카테고리의 다른 글
250322 Secondary Booting (7.3) (0) | 2025.03.22 |
---|---|
250317 SMP 를 위한 커널지원 & cpu topology(7.1~7.2) (0) | 2025.03.17 |
250301 CFS 태스크 선택, 제거, 삽입 (6.3.1~6.3.4) (0) | 2025.03.01 |
250222 6.2.6 태스크 깨우기 (ttwu) (0) | 2025.02.22 |
250208 6.2.4 스케줄링 요청하기 (0) | 2025.02.15 |
- Total
- Today
- Yesterday
- Reciprocal n-body Collision Avoidance
- vr핏
- C++
- it's called a vrpit
- 봄날에 스케치
- RVO
- 우리는 vr핏이라고 부릅니다
- vrpit
- 코어 남기기
- 면접
- 카카오
- 클래스 맴버 변수 출력하기
- shared_from_this
- cockroach db
- chrome-extension
- 에러 위치 찾기
- hole-punching
- set value
- boost
- Quest2
- ad skip
- 영상 픽셀화 하기
- SuffixArray
- red underline
- Obstacle Avoidance
- Visual Studio
- print shared_ptr class member variable
- 잘못된 빨간줄
- Golang
- mysql
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |