일반적인 리눅스 환경에서는 /etc/resolv.conf 파일에 DNS 주소값을 세팅합니다.
그러나, 우분투가 12.04 부터는 /etc/resolvconf 디렉토리와 /etc/network/interfaces 등을 종합하여 /etc/resolv.conf를 덮어씌웁니다.
아래와 같이 /etc/resolvconf 디렉토리에 있는 base 파일에 DNS 서버를 설정하면, /etc/resolv.conf 에 설정내용이 반영됩니다.
쉘 스크립트에는 $? 라는 예약된 특수변수가 있다.
이 특수변수는 이전에 실행된 자식 프로세스의 main() 함수 리턴값을 의미한다.
성공시 0, 실패시 non-zero 을 리턴한다.
이를 스크립트에 넣으면 다음과 같은 코드가 된다.
make 어쩌구 저쩌구
if [ $? -ne 0 ]; then
echo "Fail(make) ... $?"
exit 1
fi
makefile은 error가 나면 중지되겠지만,
make를 호출한 script는 make의 다음 문장을 계속 수행하겠지요.
bash의 경우 -e 옵션을 주면 error가 발생하면 종료하게 됩니다.
예를 들어 #!/bin/bash -e 를 script의 첫줄에 써주면 됩니다.
이번 장은 비례 배분(Proportional Share) 스케줄링을 다룹니다. 앞의 장들이 반환 시간, 응답 시간, 대화형 작업의 반응성에 초점을 맞췄다면, 이 장의 핵심 질문은 조금 다릅니다. 여러 작업이 있을 때 CPU를 정해진 비율로 나누어 줄 수 있는가가 주제입니다.
OSTEP은 그 대표적인 방법으로 Lottery Scheduling을 소개합니다. 이름 그대로 CPU를 사용할 다음 작업을 추첨으로 고르되, 각 작업이 가진 추첨권(ticket)의 수가 CPU 몫을 나타냅니다.
한 문장 요약
Lottery scheduling은 각 작업에 추첨권을 나누어 주고, 매 타임 슬라이스마다 무작위 추첨으로 실행할 작업을 골라 장기적으로 티켓 비율에 가까운 CPU 배분을 달성하는 스케줄링 방식입니다.
핵심 질문
질문
핵심 답변
CPU를 정해진 비율로 배분하려면 어떻게 해야 하는가?
각 작업에 추첨권을 나누어 주고, 작업의 티켓 수를 전체 티켓 수로 나눈 값이 CPU 몫이 되게 합니다.
Lottery scheduling은 어떻게 다음 작업을 고르는가?
전체 티켓 범위에서 난수를 하나 뽑고, 준비 큐를 순회하며 누적 티켓 수가 당첨 번호를 넘는 첫 작업을 실행합니다.
무작위 방식인데 공정한가?
짧은 구간에서는 목표 비율과 어긋날 수 있지만, 실행 시간이 길어질수록 실제 CPU 배분은 티켓 비율에 가까워집니다.
Stride scheduling은 왜 등장하는가?
Lottery scheduling의 무작위성을 없애고 비례 배분을 결정론적으로 달성하기 위해 등장합니다.
비례 배분과 티켓
Lottery scheduling에서 티켓은 CPU 지분을 표현하는 단위입니다. 예를 들어 A가 75장, B가 25장의 티켓을 가지고 있으면 전체 티켓은 100장이고, 장기적으로 A는 약 75%, B는 약 25%의 CPU를 받게 됩니다.
작업
티켓 수
기대 CPU 몫
A
75
75%
B
25
25%
전체
100
100%
스케줄러는 매 타임 슬라이스마다 전체 티켓 중 하나를 뽑고, 그 티켓을 가진 작업을 실행합니다. 중요한 점은 이 방식이 매 순간 정확한 비율을 보장하는 것이 아니라 확률적으로 장기 비율에 가까워지는 방식이라는 점입니다.
추첨 과정
준비 큐에 A, B, C가 있고 티켓 수가 각각 100, 50, 250이라고 가정해 보겠습니다. 전체 티켓은 400장입니다. 당첨 번호가 300이면, 누적합이 300을 처음 넘는 C가 선택됩니다.
그림 12.1: 당첨 번호를 뽑고, 작업 리스트를 순회하며 누적 티켓 수가 당첨 번호를 넘는 작업을 찾습니다.
핵심 로직은 다음과 같이 정리할 수 있습니다.
1
2
3
4
5
6
7
8
counter=0
winner=random(0,total_tickets)
forjob inready_queue:
counter+=job.tickets
ifcounter>winner:
run(job)
break
이 구현은 관리해야 할 상태가 적습니다. 작업별 티켓 수와 전체 티켓 수만 있으면 됩니다. 새 작업이 들어와도 해당 작업의 티켓 수를 추가하고 전체 티켓 수만 갱신하면 됩니다.
무작위성의 의미
Lottery scheduling은 무작위 추첨을 사용하므로 짧은 실행 구간에서는 불공정해 보일 수 있습니다. A와 B가 같은 티켓 수를 가지고 있어도, 짧은 시간 동안은 한쪽이 더 자주 뽑힐 수 있습니다.
그림 12.2: 작업 길이가 길어질수록 추첨 결과는 목표 비율에 가까워지고, 불공정 정도는 완화됩니다.
실행 기간
특징
짧음
운의 영향이 커서 목표 비율과 차이가 날 수 있습니다.
김
추첨 횟수가 늘어나 실제 비율이 목표 비율에 가까워집니다.
이 특성 때문에 lottery scheduling은 즉각적인 공정성보다 장기적인 확률 공정성이 중요한 상황에 더 잘 맞습니다.
티켓을 다루는 기법
OSTEP은 티켓을 더 유연하게 다루기 위한 세 가지 기법을 소개합니다.
기법
의미
사용 예
Ticket currency
사용자나 그룹이 자기 기준의 로컬 티켓을 쓰고, 시스템이 전역 티켓으로 환산합니다.
사용자별 몫은 같게 유지하되, 각 사용자가 자기 작업 안에서 자유롭게 지분을 나눕니다.
Ticket transfer
한 작업이 다른 작업에 티켓을 잠시 넘깁니다.
클라이언트가 서버에 요청을 맡긴 동안 서버가 클라이언트의 티켓까지 받아 빠르게 처리합니다.
Ticket inflation
작업이 자신의 티켓 수를 일시적으로 늘리거나 줄입니다.
서로 신뢰 가능한 환경에서 급한 작업이 자기 CPU 몫을 임시로 키웁니다.
특히 ticket inflation은 신뢰할 수 없는 환경에서는 위험합니다. 아무 작업이나 티켓 수를 마음대로 늘릴 수 있다면 CPU를 독점할 수 있기 때문입니다.
Stride Scheduling
Stride scheduling은 lottery scheduling과 같은 비례 배분 목표를 결정론적으로 달성하려는 방식입니다. 핵심 값은 stride와 pass입니다.
값
의미
tickets
작업의 CPU 지분입니다.
stride
큰 수 / tickets로 계산합니다. 티켓이 많을수록 stride는 작아집니다.
pass
지금까지 CPU를 얼마나 받았는지 나타내는 누적 값입니다.
스케줄러는 항상 pass가 가장 작은 작업을 실행하고, 실행 후 그 작업의 pass에 stride를 더합니다.
그림 12.3: pass 값이 가장 작은 작업을 고르고, 실행 후 pass에 stride를 더하면 티켓 비율에 맞는 실행 순서가 만들어집니다.
예를 들어 A=100 tickets, B=50 tickets, C=250 tickets이고 큰 수를 10000으로 잡으면 stride는 A=100, B=200, C=40입니다. 한 주기에서 C는 5번, A는 2번, B는 1번 실행되며, 이는 티켓 비율 250:100:50과 일치합니다.
Lottery와 Stride 비교
구분
Lottery scheduling
Stride scheduling
선택 방식
무작위 추첨
가장 작은 pass 선택
비례 배분
장기적으로 근접
짧은 구간에서도 더 정확
상태 관리
작업별 티켓 수와 전체 티켓 수 정도면 충분
각 작업의 pass, stride를 유지해야 함
새 작업 추가
상대적으로 쉬움
새 작업의 초기 pass 설정이 민감함
핵심 장점
단순함, 낮은 상태 비용, 동적 변화에 강함
결정론적이고 비율 오차가 작음
실습 코드
이 장의 homework 시뮬레이터는 ostep-homework/cpu-sched-lottery/lottery.py입니다. 작업 목록 형식은 다음과 같습니다.
1
실행시간:티켓수
예를 들어 10:100,20:100은 실행 시간이 10인 작업과 20인 작업이 각각 100장의 티켓을 가진다는 뜻입니다. 숙제를 풀 때는 먼저 -c 없이 직접 계산하고, 이후 -c로 결과를 검산하는 흐름이 좋습니다.
정리
Lottery scheduling은 CPU 몫을 티켓 수로 표현하고 무작위 추첨으로 다음 실행 작업을 고르는 비례 배분 스케줄러입니다. 구현이 단순하고 상태가 적으며, 작업이 오래 실행될수록 실제 CPU 배분은 티켓 비율에 가까워집니다.
하지만 짧은 실행 구간에서는 운의 영향으로 불공정할 수 있고, 티켓을 누구에게 얼마나 줄지 정하는 문제도 쉽지 않습니다. Stride scheduling은 같은 목표를 결정론적으로 달성하지만, pass 같은 상태를 유지해야 하므로 동적인 작업 추가와 상태 초기화가 더 까다롭습니다.
따라서 이 장의 핵심은 CPU 스케줄링 목표가 항상 반환 시간이나 응답 시간만은 아니며, 때로는 정해진 지분을 얼마나 단순하고 유연하게 보장할 수 있는지도 중요하다는 점입니다.
이번 장의 주제는 Multi-level Feedback Queue(MLFQ)입니다. 이전 장의 FIFO, SJF, STCF, RR은 작업의 실행 시간이나 스케줄링 목표를 비교적 단순하게 놓고 봤습니다. 하지만 실제 운영체제는 작업이 얼마나 오래 실행될지 미리 알 수 없습니다. MLFQ는 이 현실적인 조건에서 작업의 과거 실행 행동을 피드백으로 삼아 우선순위를 계속 조정하는 CPU 스케줄링 방식입니다.
한 문장 요약
MLFQ는 모든 작업을 처음에는 높은 우선순위에서 시작시키고, CPU를 오래 쓰는 작업은 낮은 큐로 내리며, 짧게 실행하고 자주 양보하는 작업은 높은 우선순위에 더 오래 머물게 하는 동적 스케줄러입니다.
핵심 질문
이 장의 핵심 질문은 다음과 같습니다.
질문
핵심 답변
작업의 실행 시간을 모를 때 어떻게 스케줄링할 수 있는가?
처음에는 짧은 작업일 가능성이 있다고 보고 높은 우선순위에 둔 뒤, 실제 CPU 사용 패턴을 관찰해 우선순위를 조정합니다.
MLFQ의 목표는 무엇인가?
짧은 작업에는 좋은 반환 시간을, 대화형 작업에는 빠른 응답 시간을 제공하는 것입니다.
여러 큐는 무엇을 의미하는가?
각 큐는 서로 다른 우선순위입니다. 높은 큐가 먼저 실행되고, 같은 큐 안에서는 RR 방식으로 CPU를 나눕니다.
기아 상태는 어떻게 막는가?
일정 주기마다 모든 작업을 최상위 큐로 올리는 priority boost를 사용합니다.
스케줄러를 속이는 작업은 어떻게 막는가?
한 번의 연속 실행 시간이 아니라 해당 큐에서 누적 사용한 CPU 시간을 기준으로 강등합니다.
MLFQ의 기본 구조
MLFQ는 여러 개의 준비 큐를 둡니다. 위쪽 큐일수록 우선순위가 높고, 운영체제는 항상 비어 있지 않은 가장 높은 우선순위 큐에서 실행할 작업을 고릅니다.
그림 11.1: 높은 우선순위 큐의 작업이 먼저 실행되고, 같은 큐 안에서는 라운드 로빈으로 실행됩니다.
기본 선택 규칙은 단순합니다.
규칙
내용
규칙 1
Priority(A) > Priority(B)이면 A를 실행합니다.
규칙 2
Priority(A) = Priority(B)이면 A와 B를 RR 방식으로 실행합니다.
이 두 규칙만 보면 정적 우선순위 스케줄링과 비슷해 보입니다. MLFQ의 핵심은 우선순위가 고정되어 있지 않다는 점입니다. 작업이 CPU를 오래 쓰는지, 짧게 쓰고 자주 I/O를 기다리는지를 관찰한 뒤 큐 위치를 바꿉니다.
우선순위 피드백
MLFQ는 처음부터 어떤 작업이 짧은 작업인지 알 수 없으므로, 새 작업을 일단 최상위 큐에 넣습니다. 짧은 작업이라면 높은 우선순위에서 빨리 끝날 것이고, 긴 작업이라면 CPU를 계속 사용하면서 점차 낮은 큐로 내려갑니다.
규칙
내용
의도
규칙 3
새 작업은 최상위 큐에 배치됩니다.
짧은 작업일 가능성을 먼저 인정합니다.
초기 규칙 4a
타임 슬라이스를 모두 사용하면 한 단계 낮은 큐로 이동합니다.
CPU 중심 작업을 아래 큐로 보냅니다.
초기 규칙 4b
타임 슬라이스를 다 쓰기 전에 CPU를 양보하면 같은 우선순위를 유지합니다.
대화형/I/O 중심 작업의 응답성을 높입니다.
이 방식은 짧은 작업을 빠르게 처리한다는 점에서 SJF에 가까운 효과를 냅니다. 동시에 키보드 입력, 마우스 이벤트, 디스크 I/O 등을 기다리는 대화형 작업은 CPU를 짧게 쓰고 자주 양보하므로 높은 우선순위에 머무를 가능성이 큽니다.
문제 1: 기아 상태
단순한 MLFQ는 대화형 작업이 많을 때 문제가 생깁니다. 높은 우선순위의 작업이 계속 들어오면 낮은 큐의 CPU 중심 작업은 거의 실행되지 못합니다. 이것이 기아 상태(starvation)입니다.
해결책은 일정 주기 S마다 모든 작업을 최상위 큐로 올리는 priority boost입니다.
그림 11.5: priority boost가 없으면 낮은 큐의 긴 작업이 굶을 수 있지만, 주기적으로 상향하면 실행 기회를 얻습니다.
priority boost는 두 가지 역할을 합니다. 첫째, 낮은 큐에 오래 머문 작업도 언젠가는 CPU를 받을 수 있게 합니다. 둘째, 처음에는 CPU 중심 작업이었지만 나중에 대화형 작업처럼 행동이 바뀐 프로세스를 다시 평가할 기회를 줍니다.
문제 2: 스케줄러 조작
초기 규칙 4b에는 허점이 있습니다. 어떤 작업이 타임 슬라이스가 끝나기 직전에 일부러 I/O를 요청하면, CPU를 거의 다 쓰고도 같은 우선순위에 남을 수 있습니다. 이렇게 하면 높은 우선순위 큐를 사실상 독점할 수 있습니다.
따라서 최종 규칙에서는 연속 실행 시간이 아니라 해당 큐에서 누적 사용한 CPU 시간을 기준으로 강등합니다. CPU를 한 번에 다 쓰든, 여러 번 나눠 쓰든, 배정받은 시간 할당량을 모두 쓰면 아래 큐로 내려갑니다.
그림 11.6: 누적 CPU 사용량을 기준으로 강등하면, 타임 슬라이스 직전 I/O 요청으로 스케줄러를 속이기 어렵습니다.
큐별 타임 슬라이스
실제 MLFQ 구현에서는 큐마다 타임 슬라이스를 다르게 줄 수 있습니다. 높은 우선순위 큐는 대화형 작업을 빠르게 번갈아 실행해야 하므로 짧은 타임 슬라이스가 어울립니다. 낮은 우선순위 큐는 CPU 중심 작업이 많으므로 더 긴 타임 슬라이스를 주어 문맥 교환 비용을 줄일 수 있습니다.
그림 11.7: 낮은 우선순위 큐에는 더 긴 타임 슬라이스를 부여할 수 있습니다.
큐
일반적인 타임 슬라이스
이유
높은 큐
짧음
대화형 작업을 빠르게 교체해 응답 시간을 줄입니다.
낮은 큐
김
CPU 중심 작업의 문맥 교환 비용을 줄입니다.
예를 들어 Q2=10ms, Q1=20ms, Q0=40ms처럼 설정할 수 있습니다. 다만 큐 개수, 큐별 타임 슬라이스, priority boost 주기 S는 정답이 있는 값이 아니라 워크로드에 맞춰 조정해야 하는 정책 변수입니다.
최종 규칙
책에서 정리한 MLFQ의 최종 규칙은 다음과 같습니다.
규칙
최종 형태
규칙 1
Priority(A) > Priority(B)이면 A가 실행됩니다.
규칙 2
Priority(A) = Priority(B)이면 A와 B는 RR 방식으로 실행됩니다.
규칙 3
새 작업은 최상위 큐에 배치됩니다.
규칙 4
작업이 현재 큐에서 배정받은 CPU 시간을 누적해서 모두 사용하면, CPU를 몇 번 양보했는지와 무관하게 한 단계 낮은 큐로 이동합니다.
규칙 5
일정 주기 S가 지나면 모든 작업을 최상위 큐로 이동합니다.
실습 코드
OSTEP homework의 mlfq.py를 사용하면 우선순위 하락, 짧은 작업 선호, priority boost를 직접 확인할 수 있습니다.