시스템 프로그래밍 시험 공부하면서 정리한 내용이다. 내용 갱신은 없을 예정이다.
Linux Process Scheduling
Process Schdeuling
- 언제 switch 할 것인가 + 무엇을 switch 할 것인가?
- 목표
- 빠른 프로세스 반응 시간
- 백그라운드 작업의 좋은 처리량(throughput)
- 프로세스 기아 방지
- high/low-우선순위 프로세스 중재
프로세스 분류
- 전통적인 분류 방법
- I/O-bound vs CPU-bound
- 다른 분류 방법
- 인터렉티브 프로세스
- 유저가 입력할때 반응해야됨 -> 빠른 반응성 중요
- 배치 프로세스
- 높은 처리량
- 실시간 프로세스
- high/low-우선순위 지기키
- 데드라인은 무조건 지키기
- 인터렉티브 프로세스
Linux Scheduling 기본 원리
- 스케줄링 정책 : 언제, 어떻게 프로세스를 선택하는 규칙
- Time Sharing
- time quantum 만큼 실행. 끝나면 스케줄링
- 우선순위 기반(Priority-based)
- 정적 우선순위(static priority) : 실행시 결정
- 동적 우선순위(dynamic priority) : 런타임 감시하면서 변동
- Preemptive Scheduling : 선점 스케줄링
Preemptive
- block 풀린게 현재 실행중인 프로세스보다 우선순위가 높으면 즉시 우선순위가 높은 작업을 실행한다
- Running->Exit, Running->Blocked 일때만 ownership 교체 가능하면 non-preemptive
- Blocked->Ready, Running->Ready(time quantum 소모) 일때도 스케줄링이 가능하면 Preemptive
POSIX.4
SCHED_FIFO
- Real-time
- 시간 제한 없이 작업을 실행. 같은 우선순위 작업큐에 여러 프로세스 있어도 이전 작업이 끝날때까지 다음것은 실행되지 않는다
- 선점 발생하는 조건
- 작업중인 프로세스가 blocked(ex:I/O)
- 우선순위가 더 높은 프로세스가 실행 가능 상태
sched_yield()
호출
- priority : 0~99
- only supervisor mode
SCHED_RR
- Real-time
- time quantum만큼 실행된다. 같은 우선순위에 여러개의 프로세스가 존재하면 time quantum씩 돌아가면서 실행됨
- priority : 0~99
- only supervisor mode
SCHED_OTHER
- Best-effort
- 일반적인 프로세스가 이 정책으로 수행된다
- proiority: 100~139
O(1) Scheduler
- 2.4에서는 O(n), 2.6부터는 O(1)
- 2.6.23부터는 CFS(Completely Fair Schduler)
- RB-Tree기반, I/O 까지 추적해서 스케줄링 정책 결정
- TASK_RUNNING 상태의 프로세스는 자신의 우선순위에 맞는 runqueue에 들어간다
- 0~139까지 140개의 runqueue
- runqueue list는 2개가 존재한다. active, expired
- active에 있는 모든 작업을 처리하면 active와 expired를 바꾼다.
- CPU마다 독립적인 runqueue가 존재
Static Priority
- 100~139, 기본값=120 (SCHED_OTHER)
- time quantum 결정 공식
- (140 - static_prio) * 20 (if static_prio < 120)
- (140 - static_prio) * 5 (if static_prio >= 120)
Dynamic Priority
좋은 반응시간을 유지하는 것이 목적이다. Sleep time을 이용해서 프로세스별로 다른 우선순위를 준다. nice
I/O Bound
- time quantum을 줘도 못쓴다
- 근데 반응성 때문에 자주 실행되어야한다
- 높은 우선순위 부여
CPU Bound
- time quantum을 주면 아마도 전부 다 쓸거다
- 독점 발생 가능
- 낮은 우선순위 부여
Real-time / Non-Real-time
- Real-time process
- 정적 우선순위만 존재한다
- SCHED_FIFO
- time quantum이 존재하지 않는다
- SCHED_RR
- time quantum이 존재한다.
- expired로 이동하진 않는다. active에서만 움직임
- active - expired queue list 교체는 TASK_RUNNING 상태의 Real-time 작업이 없을때만 가능하다.
Scheduling for fork()
- time quantum은 parent와 child가 적절히 쪼개 갖는다
- child = (time_quantum + 1) / 2
- parent = (time_quantum) / 2
- orig = chlid + parent
- child >= parent
- ex: orig=9, child=5, parent=4
- ex: orig=1, child=1, parent=0
- fork한 다음에 child와 parent의 time quantum의 합이 원래보다 크면 불공평 발생(계속 fork할 경우 독점 가능)
- 부모보다 자식한테 quantum을 더 준다. 자식이 fork후에 실행되서 유저 스택 페이지 복사와 같은 작업을 할 수 있다.
Scheduler 관련 함수
schedule()
schduler_tick()
: 매 tick마다 호출try_to_wake_up()
Invocation
- Direct Invocation
- 현재 프로세스가 직접
schduele()
함수를 호출 - 현재 프로세스가 리소스를 사용할수 없어서 블럭될 경우 발생
- Non-preemptive에서도 가능
- 현재 프로세스가 직접
- Lazy Invocation
- TIF_NEED_RESCHED 플래그를 올려놓으면 나중에 스케줄러가 호출된다
- 유저 모드 프로세스를 실행하기 전에 flag를 확인한다.
- 시스템콜 끝나고 리턴
- 인터럽트 핸들러 끝나고 리턴(timer interrupt 같은거 포함)
- etc…
- Preemptive CPU Scheduling 에서만 가능
Direct Invocation
- 현재 프로세스가 블럭될때 커널은 다음 작업을 수행한다
- current를 wait queue에 집어넣는다
- current state = TASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLE
schduler()
호출- 리소스가 사용가능한지 확인, 사용할수 없으면 2번으로
- 리소스를 사용할수 있으면 current를 대기큐에서 제거
Lazy Invocation
- current 프로세스의
TIF_NEED_RESCHED
가 1이면 발생 - 발생 가능한 경우
- time quantum을 전부 사용한 경우(timer interrupt)
scheduler_tick()
- 새로 깨어난 프로세스의 우선순위가 현재보다 높은 경우
try_to_wake_up()
sched_setscheduler()
,sched_yield()
호출
- time quantum을 전부 사용한 경우(timer interrupt)
schedule()
- 적절한 작업을 runqueue에서 선택하기
switch_mm()
: virtual address space 교체switch_to(prev, next, prev)
: context switch
Preemption
- linux 2.4까지는 non-preemptive
User-Mode Preemption
- 커널이 user-space로 되돌아갈때 TIF_NEED_RESCHED 확인해서 스케줄러가 동작
Kernel-Mode Preemption
- 2.6부터 full preemptive
- 커널 모드에서도 preempt 가능
- 장점 : 현재 프로세스가 user mode 진입할 때 까지 기다리지 않아도 된다. (user-mode preemption만 가능할 경우)
Kernel Preemption
- 락이 걸려있지 않을때만 kernel mode에서 preempt 가능
- struct thread_info의 preempt_count 이용
- preempt_count : default=0, preemptable
- lock 획득 -> preempt_count++
- lock 놓기 -> preempt_count–
- interrupt끝나고 kernel space진입할때 다음을 처리
- TIF_NEED_RESCHED==1 and preempt_count==0
- reschedule
- TIF_NEED_RESCHED==1 and preempt_count>0
- 락이 풀릴때까지 스케줄링 연기
- TIF_NEED_RESCHED==1 and preempt_count==0
Multiprocess scheduling
- Runqueue는 CPU마다 독립적으로 존재한다
- 프로세스는 한쪽에만 존재할수 있다. 동시에 양쪽에 존재 못함
- 작업은 일반적으로 했던 CPU에서 다시 실행
- CPU를 건너다니면 캐시 miss
- CPU별로 로드밸런싱이 필요