Process Loadweight
태스크의 우선순위는 그 자체로 CFS스케쥴러에 의해 사용되는 개념은 아니다. 스케쥴러는 그보다는 loadweight라는 값을 통해 태스크에게 얼마큼의 CPU시간 비율을 줄것인지를 결정한다. 이 말은 우선순위는 load라는 개념으로 적절히 변환되어야 한다는 것을 의미한다.
load_weight 구조체에 의해 태스크의 loadweight이 표현된다.
struct load_weight {
unsigned long weight;
u32 inv_weight;
};
loadweight의 값은 아래 두개의 인자에 의존적으로 계산된다.
- 프로세스의 스케쥴링클래스? 타입?
- 프로세스의 static priority
nice value -> priority -> load weight로 변환되는 과정에 대한 그림???
우선순위에 걸맞는 loadweight값은 미리계산되어 sched_prio_to_weight에 저장되어 있다.
const int sched_prio_to_weight[40] = {
/* -20 */ 88761, 71755, 56483, 46273, 36291,
/* -15 */ 29154, 23254, 18705, 14949, 11916,
/* -10 */ 9548, 7620, 6100, 4904, 3906,
/* -5 */ 3121, 2501, 1991, 1586, 1277,
/* 0 */ 1024, 820, 655, 526, 423,
/* 5 */ 335, 272, 215, 172, 137,
/* 10 */ 110, 87, 70, 56, 45,
/* 15 */ 36, 29, 23, 18, 15,
};
우선순위와 loadweight값을 매핑한 그림하나 넣을까??
120이라는 우선순위값은 loadweight 1024와 매핑되어 있다. 배열의 나머지 값들은 태스크의 우선순위가 한단계 내려갈때마다 기존보다 10%의 CPU시간을 더 갖도록 미리 계산되어져있다. 비슷한 방식으로 우선순위가 한단계 올라가면, 태스크는 10%의 CPU시간을 덜 갖게 될것이다.
실제로 그런지 자주 쓰이는 예제를 통해 알아보자. 태스크가 얻는 CPU시간의 비율은 아래와 같이 계산할 수 있다.
태스크의 얻는 CPU시간비율
= 태스크의 loadweight/전체 태스크의 loadweight합
시스템에 2개의 태스크가 존재하고 둘 다 nice 값이 0일 경우라고 가정해보자.(== 태스크의 우선순위는 120임) loadweight는 1024가 된다. 이 때 태스크들이 얻는 CPU시간비율은 50%가 된다.
task A's loadweight 1024
------------------------ = 0.5 (50%)
1024(task A) + 1024(task B)
태스크 A의 우선순위를 한 단계 낮추면 어떻게 될까? A의 nice값은 1이 되고 loadweight 820과 매핑된다.
태스크 A가 얻게되는 CPU시간 비율은 0.45가 된다.
820(task A)
----------------------- = about 0.45
820(task A) + 1024(task B)
반대로 태스크 B가 얻게되는 CPU시간 비율은 0.55가 된다.
1024(task A)
----------------------- = about 0.55
820(task A) + 1024(task B)
태스크의 우선순위가 낮아지면 다른 태스크와의 CPU시간 비율이 10%차이가 나게되는걸 확인할 수 있다.
RT태스크의 이런 변환은 어떻게 진행될까?
실제 코드로 살펴보는 priority와 loadweight의 변환과정
태스크의 loadweight가 설정되는 경우는 아래와 같다.
- 태스크가 새롭게 fork될 때
- userspace에 의해 nice 시스템콜이 호출될 때
- 태스크의 스케쥴링클래스가 변경될 때
- RT쓰레드의 우선순위가 변경될 때(확실치 않음)
이때마다 set_load_weight()를 호출해서 태스크의 loadweight를 설정한다.
static void set_load_weight(struct task_struct *p)
{
int prio = p->static_prio - MAX_RT_PRIO;
struct load_weight *load = &p->se.load;
/*
* SCHED_IDLE tasks get minimal weight:
*/
if (idle_policy(p->policy)) {
load->weight = scale_load(WEIGHT_IDLEPRIO);
load->inv_weight = WMULT_IDLEPRIO;
return;
}
load->weight = scale_load(sched_prio_to_weight[prio]);
load->inv_weight = sched_prio_to_wmult[prio];
}
먼저 미리 계산되어 배열에 담긴 loadweight과 매핑된 priority를 구한다. 이과정에서 태스크의 static_prio가 쓰인다.(nice값과 1:1 매핑되어있다.)
위에서 구한 priority를 sched_prio_to_weight[] 배열의 index로 사용해서 매핑된 loadweight를 구하고 설정한다.
런큐에서의 loadweight
런큐도 loadweight를 사용한다.
struct rq {
/* ... */
struct load_weight load;
/* ... */
}
struct cfs_rq {
struct load_weight load;
/* ... */
}
런큐의 load필드는 런큐에 속한 태스크의 loadweight의 합이 담겨져 있다. 태스크의 loadweight의 합이 곧 런큐의 loadweight이므로 태스크가 enqueue, dequeue될 때마다 런큐의 loadweight이 갱신된다.
- 태스크를 런큐에 enqueue할 때
- 태스크를 런큐에 dequeue할 때
- 태스크의 weight가 변경될 때??