Process Priority
커널에서 프로세스의 우선순위에 대한 매크로상수는 prio.h에 정의되어있다.
include/linux/sched/prio.h
#define MAX_NICE 19
#define MIN_NICE -20
#define NICE_WIDTH (MAX_NICE - MIN_NICE + 1)
#define MAX_USER_RT_PRIO 100
#define MAX_RT_PRIO MAX_USER_RT_PRIO
#define MAX_PRIO (MAX_RT_PRIO + NICE_WIDTH)
#define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2)
#define NICE_TO_PRIO(nice) ((nice) + DEFAULT_PRIO)
#define PRIO_TO_NICE(prio) ((prio) - DEFAULT_PRIO)
#define USER_PRIO(p) ((p)-MAX_RT_PRIO)
#define TASK_USER_PRIO(p) USER_PRIO((p)->static_prio)
#define MAX_USER_PRIO (USER_PRIO(MAX_PRIO))
위 헤더파일에 정의된 값을 보면 커널 관점에서의 최대 priority(MAX_PRIO)는 140이다. 그리고 0부터 139까지의 범위를 가지는 것을 볼 수 있다. RT태스크의 최대 priority는 100이다.
#define MAX_USER_RT_PRIO 100
#define MAX_RT_PRIO MAX_USER_RT_PRIO
#define MAX_PRIO (MAX_RT_PRIO + NICE_WIDTH)
유저프로세스의 nice값의 범위는 -20 ~ 19이다. 그리고 유저프로세스의 nice값은 커널에서는 100 - 139의 범위의 priority로 변환되어 사용된다.(SCHED_NORMAL/SCHED_BATCH)
#define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2)
#define NICE_TO_PRIO(nice) ((nice) + DEFAULT_PRIO)
#define PRIO_TO_NICE(prio) ((prio) - DEFAULT_PRIO)
유저프로세스가 사용가능한 최대 priority는 100으로 정해져있다. 이런 제한은 커널스레드가 늘 유저프로세스보다 높은 우선순위를 갖게 해준다.
#define MAX_USER_RT_PRIO 100
저절로 나머지 0 - 99 범위의 priority는 RT태스크의 priority를 표현하는데 사용된다. 높은 우선순위값은 실제로는 낮은우선순위를 나타낸다.
priority를 나타내는 3개의 변수
task_struct에는 priority와 관련된 변수가 3가지가 있다.
- prio
- normal_prio
- static_prio
task_struct 구조체는 태스크의 우선순위를 나타내는 3가지의 변수를 가지고 있다.
struct task_struct {
...
int prio, static_prio, normal_prio;
...
user process의 nice가 변경되면 어떻게 될까?
user process는 nice() 함수나 nice command등으로 nice값을 변경할 수 있다.
nice overview
nice() or nice command
-> nice syscall
---> clamp increment value from userspace
---> clamp nice value from increment
---> set_user_nice()
-----> p->static_prio = NICE_TO_PRIO(nice)
-----> p->prio = effective_prio(p)
-------> p->normal_prio = p->static_prio
커널내부에서는 가장먼저 nice 시스템콜이 호출된다. 인자로 넘겨진 increment값은 태스크의 static_prio를 nice단위로 변환값에 더해져 새로운 nice값을 만든다.
SYSCALL_DEFINE1(nice, int, increment)
{
...
increment = clamp(increment, -NICE_WIDTH, NICE_WIDTH);
nice = task_nice(current) + increment;
nice = clamp_val(nice, MIN_NICE, MAX_NICE);
set_user_nice(current, nice);
...
}
- increment값의 범위는 -40 ~ 40
- nice값의 범위는 -20 ~ 19
새롭게 계산된 태스크의 nice 값은 set_user_nice()를 통해 태스크의 task_struct 구조체에 반영된다. nice값이 priority관련 변수에 반영되는 과정을 살펴보자.
void set_user_nice(struct task_struct *p, long nice)
{
...
p->static_prio = NICE_TO_PRIO(nice);
...
p->prio = effective_prio(p);
...
}
전달된 nice 값을 priority로 변환해서 태스크의 static_prio에 설정한다. effective_prio()는 realtime priority를 가진 태스크가 아니라면 태스크의 normal_prio를 그대로 리턴한다. normal_prio가 어떤값으로 설정되는지를 확인하기 위해 effective_prio()를 살펴보자.
static int effective_prio(struct task_struct *p)
{
p->normal_prio = normal_prio(p);
if (!rt_prio(p->prio))
return p->normal_prio;
return p->prio;
}
static inline int normal_prio(struct task_struct *p)
{
if (task_has_dl_policy(p))
prio = MAX_DL_PRIO-1;
else if (task_has_rt_policy(p))
prio = MAX_RT_PRIO-1 - p->rt_priority;
else
prio = __normal_prio(p);
return prio;
}
static inline int __normal_prio(struct task_struct *p)
{
return p->static_prio;
}
CFS태스크의 경우 normal_prio()는 태스크의 static_prio를 그대로 리턴해준다. 이 값이 그대로 normal_prio에도 설정된다는 말이다.
정리하자면 이렇다. static_prio는 userspace에서 전달된 값으로 갱신된 nice값을 priority로 변환한 값으로 설정된다. 나머지 normal_prio, prio는 동일한 값으로 설정된다.
태스크가 rt priority로 boost-up된 경우는 prio는 변경된 static_prio를 사용하지 않고 예전의 prio를 그대로 사용한다.
RT태스크의 경우도 prio는 예전 prio를 그대로 사용한다. normal_prio는 변경된 static_prio 대신 MAX_RT_PRIO - 1 - rt_priority로 설정된다.
이건 표로 정리해야겠다...
priority의 상속
fork된 자식 태스크는 부모에게서 priority관련된 값을 상속받는다.
- p->static_prio = parent->static_prio
- p->prio = parent->normal_prio
- do not leak PI boosting prio to the child
- p->normal_prio = parent->normal_prio ???
관련된 내용은 sched_fork()를 보자...