운영체제 - 반효경 #5 CPU Scheduling & Process Synchronization
# 5-1 CPU Scheduling
CPU Scheduler & Dispatcher
CPU Scheduler
- Ready 상태의 프로세스를 통해 이번에 CPU 쓸 프로세스 선택
Dispatcher
- CPU Scheduler 에 의해 선택된 프로세스에게 CPU 제어권을 넘김
- = Context switch
- = 문맥 교환
CPU 스케줄링이 필요한 경우 = 프로세스에게 아래와 같은 상태 변화가 있는 경우
- Running ➡️ Blocked (ex. I/O 요청하는 시스템콜)
- Running ➡️ Ready (ex. 할당 시간 만료로 timer interrupt)
- Blocked ➡️ Ready (ex. I/O 완료 후 interrupt)
- Terminate
비선점형 | 선점형 |
nonpreemptive | preemptive |
강제로 빼앗지 않음 | 강제로 빼앗음 |
Scheduling Algorithms
- FCFS (First-Come First-Served)
- SJF (Shortest-Job-First)
- SRTF (Shortest-Remaining-Time-First)
- Priority Scheduling
- RR (Round Robin)
- Multilevel Queue
- Multilevel Feedback Queue
Scheduling Criteria
- Performance Index = Performance Measure = 성능 척도
- 시스템 입장의 성능 척도 = 최대한 일 많이 시킬수록 좋음
- 프로세스 입장의 성능 척도 = 고객 입장, 내가 CPU 얻어서 빨리 끝낼수록 좋음
- 시간 관련 성능 척도 = CPU 기준, CPU burst 기준으로 봐야 함
CPU utilization (이용률)
- Keep the CPU as busy as possible
- 최대한 일 많이 시켜서 전체 일한 비율 높임
Throughput (처리량)
- # of processes that complete their execution per time unit
- Throughput = 처리량, 산출량
- 주어진 시간 동안 몇 개의 작업을 완료했느냐
Turnaround time (소요시간, 반환시간)
- Amount of time to execute a particular process
- CPU burst 가 다 끝나고 나갈 때까지 걸리는 시간
- 프로세스가 CPU 쓰러 들어와서 CPU 쓰고, I/O 하러 나갈 때까지 걸리는 시간
Waiting time (대기 시간)
- Amount of time a process has been waiting in the ready queue
- CPU 를 쓰려 해도 하나뿐이니 ready queue 에서 대기해야 함
- 순수하게 줄 서서 기다리는 시간
- CPU 를 쓰다 뺏겨서 맨 뒤에 가서 줄 서는 대기시간들을 합친 총 대기시간
- Running 시간까지 전부 합친 시간이면 turnaround time 이겠지
Response time (응답 시간)
- Amount of time it takes from when a request was submitted until the first response is produced, not output
- For time-sharing environment
- Ready queue 에 들어와 처음으로 CPU 쓸 때까지 걸리는 시간
FCFS (First-Come First-Served)
- 먼저 온 순서대로 처리
- 비선점형 스케줄링
- CPU 얻으면 이 프로세스가 직접 내줄 때까지 빼앗지 않음
- CPU 오래 쓰는 프로그램이 CPU 계속 쓰게 되기에, 그다지 효율적이진 않음
- Convoy effect = 짧은 프로세스들이 CPU 점유할 때까지 오래 기다려야 하는 현상
SJF (Shortest-Job-First)
- 각 프로세스의 다음 CPU burst time 을 가지고 스케줄링에 활용
- CPU burst time 이 가장 짧은 프로세스를 제일 먼저 스케줄링
- Average waiting time 최소화
- 짧게 쓰는 친구들을 먼저 CPU 쓸 수 있게 해줌
Non-preemptive
- 일단 CPU 를 잡으면 이번 CPU burst 가 완료될 때까지 CPU 를 선점(preemptive) 당하지 않음
Preemptive
- 현재 수행 중인 프로세스의 남은 burst time 보다 더 짧은 CPU burst time 을 가지는 새로운 프로세스가 도착하면 CPU 를 빼앗김
- 이 방법을 Shortest-Remaining-Time-First (SRTF) 라고도 부름
- CPU 를 줬더라도 더 짧은 시간을 쓸 수 있는 프로세스가 오면 CPU 빼앗아 넘겨줌
SJF is optimal
- 주어진 프로세스들에 대해 minimm average waiting time 을 보장
Example of Non-preemptive SJF / Preemptive SJF
- 각 예시의 Average waiting time 결과 = 이 결과보다 더 짧은 average waiting time 은 구할 수 없다는 의미
Non-preemptive
- 0초 시점에 누구에게 CPU 줄지 결정해야
Preemptive
- 짧은 burst time 인 프로세스에게 CPU 빼앗아 넘겨줌
Priority Scheduling
- A priority number (integer) is associated with each process
- 우선순위가 높은 프로세스에게 CPU 를 주겠다
- Highest priority 를 가진 프로세스에게 CPU 할당(smallest integer = highest priority)
- Preemptive = 우선순위 더 높은 프로세스가 오는 경우
- Non-preemptive = 더 높은 우선순위가 오더라도 이미 CPU 할당했으니 기다려주자
- SJF 는 일종의 priority scheduling
- Priority = predictd next CPU burst time
- CPU 사용량 가장 적은 프로세스에게 먼저 주니까
- Problem
- Starvation = low priority processes may never execute
- = CPU 사용이 긴 프로세스는 영원히 CPU 를 못 받을 수도 있음
- Solution
- Aging = as time progresses increase the priority of the process
- = 오래 기다릴수록 우선순위를 높여줘서 starvation 을 막아주자
다음 CPU Burst Time의 예측
- tn = 실제 CPU 사용 시간
- r(n+1) = n+1 번째 CPU 를 사용하고자 할 때의 예측시간
- 예측시간 = n 번째 실제 CPU 사용 시간과 n 번째 예측했던 CPU 사용시간을 일정 비율 곱해 사용
- a + (1 – a) = 1
- a 와 (1 – a) 둘에 일정 비율 적용하겠다는 의미
Exponential Averaging
SJF가 가진 Starvation 문제와 보완책
- CPU 사용이 긴 프로세스는 영원히 CPU 를 못 받을 수도 있음, 즉, Long process = starvation
- CPU 사용시간을 미리 알 수 없음 ➡️ CPU 사용시간을 추정할 수는 있음
- 과거 CPU 사용 흔적을 통해 이번에 얼마나 쓸지 예측(정확하진 않음) = Exponential Averaging
마지막 공식
- 최근에 진행된 CPU 사용시간일수록 가중치 높게 적용
- 오래 전 CPU 사용시간은 가중치 더 낮게 적용
Round Robin (RR)
- 각 프로세스는 동일한 크기의 할당 시간(time quantum) 가짐(일반적으로 10 - 100 milliseconds)
- 할당 시간이 지나면 프로세스는 선점(preemted) 당하고, ready queue 의 제일 뒤에 가서 다시 줄을 섬
- n 개의 프로세스가 ready queue 에 있고, 할당 시간이 q time unit 인 경우 각 프로세스는 최대 q time unit 단위로 CPU 시간의 1/n 을 얻음 ➡️ 어떤 프로세스도 (n-1)q time unit 이상 기다리지 않음
- Performance
- q large ➡️ FIFO
- q small ➡️ context switch 오버헤드 증가
- 현대 컴퓨터는 모두 round robin 기반
- 응답시간이 점점 빨라짐 – 모든 프로세스가 CPU를 정해진 시간 동안 쓰게 되면서, 짧은 프로세스는 한번에 끝날 수 있고, 오래 걸리는 프로세스는 정해진 시간만큼 응답시간이 계속 줄어들게 됨
- 누구나 CPU를 맛볼 확률 높아짐
Example of RR
- CPU 사용시간이 각자 다르거나 사용시간을 모를 때 round robin 유용
- 모든 프로세스가 기다렸다가 CPU를 마지막까지 조금씩 쓰고 동시에 빠져나감
- Heterogeneous ➡️ why?
- Time quantum 짧게 잡으면 모든 프로세스가 CPU를 쬐금쬐금씩 씀
- Time quantum 크게 잡으면 동일하게 긴 시간 동안 CPU 사용하는 프로세스들 처리할 때 유용
# 5-2 CPU Scheduling 2
CPU-burst Time 의 분포
CPU scheduling 필요 이유
- 컴퓨터 시스템 안에 있는 job 들이 homogeneous 않고
- I/O 처럼, 사람과 인터렉션하는 job 이나 CPU 와만 작업하는 job 들이 섞여있기 때문에 스케줄링이 쉽지 않음
- 그래서 FCFS 스케줄링 또한 쉽지 않음
FCFS (First-Come First-Served)
- 화장실 스케줄링
- 변비인 사람이 칸을 차지하면 super villain!
Round Robin (RR)
- FCFS 같이 오래 기다려야 하는 스케줄링은 곤란
- RR 은 컴퓨터 시스템에서 heterogeneous한 프로세스 스케줄링에 있어, 효율적 / 공정한 방법
- CPU 를 주고 나서 무작정 쓰게 하는 게 아니라, 할당된 시간 미리 세팅해서 사용하게 하고, 그 시간이 지나면 다른 프로세스에게 CPU 넘겨줌
- CPU 를 썼다, 뺏겼다를 반복
- CPU 를 쓰는 시간 = 본인이 CPU 를 사용하려는 시간과 어느 정도 비례한다는 측면에서 공정한 스케줄링이라 볼 수 있음
Time quantum
- 주어진 시간, 할당 시간
RR 이 좋은 이유
- Context 를 save 하고, 다시 CPU 를 얻었을 때 그 지점부터 재개할 수 있는 매커니즘이 제공되기에
Multi-level Queue
Ready queue
- 지금까지는 한줄로 줄을 서서 프로세스들이 CPU 를 사용했었는데
- 앞으로의 개념들은 여러 줄로 CPU 를 기다리는 줄서기를 하게 됨
Multi-level queue 의 줄서기 방식
- Ready queue
- Foreground = interactive
- Background = 사람과의 interaction 없이, batch 처럼 CPU 만 쓰는
- 줄마다 우선순위가 있음
- 맨 위일수록 우선순위가 높음
- 시스템 프로세스 ➡️ 인터렉티브 ➡️ 약간 인터렉티브 ➡️ 배치(CPU 만 사용하는) ➡️ …
- 이 우선순위는 완전 계급제 = 우선순위가 높은 애들이 먼저 CPU 를 쓸 수 있고, 낮은 우선순위들은 이를 극복할 수 없음
Foreground, background 두 줄을 각각 세운 후, 각 줄마다의 순서도 정해야 하는데,
Foreground 에 줄 선 프로세스
- 사람과 interaction 하는 애니까 RR 써서 응답시간 짧게
Background 에 줄 선 프로세스
- CPU 만 사용하니, 응답시간(interaction)이 빠를 필요 없음
- 그래서 중간에 CPU 를 얻었다 뺐었다 하는 context switching 이 없는, FCFS 방식이 좋음
Fixed priority scheduling
- 어떤 queue 에게 CPU 를 줄 것이냐 고민 ➡️ 아예 우선순위를 지정해보자
- Starvation 발생하는 문제 있음
Time slice
- CPU 시간을 적절하게 할당해주는
Multi-level queue는 어쨌든 RR에 비해 차별적임, 그래서 나온 게 Multi-level feedback queue
Multi-level Feedback Queue
- 현대사회같은 체제
- 출신이 낮아도 중간에 승격이 되기도 하는, 줄을 왔다갔다 할 수 있는 스케줄링
Multi-level queue나 multi-level feedback queue처럼 줄은 여러 개인데 CPU는 하나인 경우 고려사항
- 프로세스를 어느 줄에 넣을지
- 그 줄에 들어가 있을 때 무조건 우선순위가 높은 게 비어있지 않으면 그 줄에만 우선권을 주느냐, 그럼 낮은 우선순위는 starvation을 겪는데 이에 대한 해결책은?
Multi-level Feedback Queue 운영 방법
- 처음 들어온 프로세스는 가장 우선순위 높은 프로세스에 넣고, time quantum 을 짧게 줌(늘 1번 프로세스는 시간을 짧게 주기에 별도로 예측할 필요도 없음)
- 밑의 큐로 갈수록 그 다음 프로세스들은 round robin 에서 점점 긴 time quantum 을 주고
- CPU burst가 짧은 프로세스는 맨 위의 큐에서 바로 할당시간 받고 빠져나갈 수 있는 거임
- 위의 큐가 비었을 때만 밑의 큐를 처리하는 방식이지만, 할당시간이라는 제한을 기반으로 스케줄링이 되는 것
- CPU 할당시간이 짧은 프로세스에게 우선순위를 주는 방식
- RR 만으로도 할당시간 분배가 잘 안 이루어지기에 multi-level feedback queue 써서 짧은 사용시간의 프로세스에게 먼저 할당해주는 것
Example of Multilevel Feedback Queue
Queue
- 위에 있을수록 할당시간 짧고
- 밑일수록 FCFS
Multiple-Processor Scheduling
- CPU 가 여러 개 있는 경우의 스케줄링
- EX) 화장실 칸이 늘어나는 경우 / 은행에서 은행 창구가 늘어나는 경우 등
Homogeneous processor
- CPU 가 여러 개 있는데 어떤 job 은 특정 CPU 가 반드시 실행해야 하는 경우
Load sharing
- CPU 가 여러 개 있으면 load sharing 이 잘 되어야 함
- 특정 CPU 만 일하고, 나머지는 놀면 안 됨
- 부하를 적절히 조절할 수 있어야 함
- CPU 마다 한줄서기만 해야 하는 건 아님
- 별도의 줄(큐)을 서게 할 수도 있음
Symmetric multi-processing
- 모든 CPU 가 대등
- 각 CPU 가 알아서 스케줄링
Asymmetric multi-processing
- CPU 가 여러 개인데, 그 중 하나의 CPU 가 전체 컨트롤(데이터 접근 / 공유 등) 담당
Real-Time Scheduling
Real-time job
- 데드라인이 있는, 정해진 시간 안에 무조건 끝내야 하는 job
- 먼저 CPU를 주는 그런 문제가 아니라, 시간 안에 끝내는 걸 보장해줘야 한다는 것
Hard real-time systems
- 데드라인 절대 보장
Soft real-time computing
- 다른 task에 비해 우선순위만 높여주는
- 데드라인 꼭 보장 못해도 됨
- EX) 영화보기
Thread Scheduling
Thread
- 프로세스 하나 안에 CPU 수행 단위가 여러 개 있는 것
Local scheduling
- User level thread
- 사용자 프로세스가 직접 스레드 관리, 운영체제는 스레드의 존재를 모름
- 운영체제는 그 프로세스에게 CPU 를 줄지 말지만 정하면 됨
- 할당받은 CPU 를 그 프로세스 내의 어떤 스레드에게 줄지 결정하는 것, 즉 내부에서 일어나는 일
Global scheduling
- Kernel level thread
- 운영체제가 스레드 존재를 이미 알고 있는 상태
- 프로세스 스케줄링 하듯 알고리즘에 근거해 운영체제가 어떤 스레드에게 CPU 를 줄지 결정
Algorithm Evaluation
- 어떤 알고리즘이 좋은지 평가하는 방법
- 그림 속 server = CPU
- CPU 용량에 따라 service rate (CPU 의 처리율) 결정됨
Queueing models
- 아주 이론적인 접근
- 도착율과 처리율 통해 확률 계산
- 실제 시스템에서 직접 돌리는 걸 더 의미있기 보기에, 이 개념은 이론적인 개념으로만 이해하자
Implementation & measurement
- 실제 시스템에 구현 후 돌려봐서 성능 측정
- CPU 스케줄링 알고리즘을 내가 새로 만들었다 ➡️ 이를 평가하려면 ➡️ 실제 시스템에 구현
- 기존에 있던 스케줄러와 내가 작성한 스케줄러를 실제 작업(workload) 에 돌려봐서 성능 테스트
- 이는 운영체제 레벨의 접근이기에 결코 단기간에 되지 않고 어려운 작업임
- 그래서 쉬운 작업 할 수 있는 다른 방식이 있는데 그게 simulation
Simulation
- CPU burst 와 I/O 작업이 빈번하며, 프로세스들이 왔다갔다하면 simulation 으로 가정해, 주어진 조건으로 돌려보는 것
- 실제 CPU burst pattern 에 적용해 simulation 돌리면 신빙성 높아짐
- 실제 프로그램 통해 추출한 input 데이터 = trace = 실제 시뮬레이션에 들어가는 인풋 데이터(추출도 가능)
# 5-2 Process Synchronization 1
데이터의 접근
- 컴퓨터 시스템 안에서 데이터가 접근되는 패턴
- 컴퓨터 시스템의 어떤 위치에 있던 간에 그림의 경로를 지나게 됨
- Storage 에서 가져와 execution 처리 후 다시 storage 에 저장
- 보통 데이터를 읽기만 하면 다른 애가 읽어도 상관 없는데,
- 내가 데이터를 가져와 연산 후 그 데이터를 변경하면, 다른 애가 데이터 조회 시 synchronization 문제 발생
Race Condition
- 각각의 execution 에서 count ++, count– 되어버리면 Storage 내에서의 결과는 그냥 count 가 됨
- 여러 주체가 하나의 데이터에 동시 접근 시 race condition (경쟁 상태)
- 프로세스는 자기 자신만의 주소공간에 접근할 수 있지만
- 각 프로세스의 일부를 공유해 접근하는 공간에서는 sync 문제 발생할 수 있음
더 큰 문제는 운영체제 커널 관련 sync 문제
- 프로세스들이 직접 실행 못하고, 운영체제에 요청할 때 시스템콜을 하는데,
- 그럼 커널의 코드가 그 프로세스를 대신해 실행됨 = 커널의 어떤 데이터에 접근
- 또 커널 코드 실행 중에도 인터럽트 발생할 수 있음
- 인터럽트 코드 자체도 커널 코드임
- 이전 운영체제 커널이 실행 중이면서 데이터 건드릴 때 인터럽트 걸리면 인터럽트 코드를 실행할 텐데, 인터럽트 또한 커널의 코드이기에 어쨌든 커널의 데이터를 계속 건드린다는 것
- 근데 커널의 데이터는 공유 데이터이기에 문제 발생
OS에서 race condition은 언제 발생하는가?
Race condition 1
- 운영체제 커널이 cpu 에서 실행되고 있는데 count 변수를 ++ 해주고 있는 상태
- 이런 count++ 시키는 문장 = cpu 내부에서는 여러 개의 인스트럭션으로 진행됨
- Count 라는 메모리 변수값을 1 증가시킨다
- 메모리의 변수를 cpu 안의 레지스터로 불러들이고 = load
- 그 레지스터 값을 1 증가시키고 = inc
- 레지스터 값을 메모리의 변수의 위치에서 갖다 쓰는 순서 = store
- Cpu 안의 레지스터에 접근할 때(load) 인터럽트 들어오면 인터럽트 핸들러가 작업 진행 ➡️ Count– 진행
- 이후 count++ 작업으로 돌아와 마저 수행해야 하는데,
- 그럼 count-- ➡️ count++ 순서가 되는데, 근데 결과값은 어떻게 되느냐?
- Count– 작업 들어가기 전, count++ 는 이미 count 변수를 가지고 있는 상태
- 그렇기에 count– 작업과 상관 없이, count++ 가 load 단계에서 가지고 있던 오리지널 count 값에 ++ 한 결과값을 도출
- 마이너스 작업과 상관 없이, 값이 1 증가한 값만 반영됨
- 이게 바로 sync 문제!
- 위처럼 데이터 건드리고 도중에 인터럽트 발생할 수 있는 상황이면
- 도중 인터럽트가 될 수 없게 막고
- 먼저 하던 일 다 끝내서 저장한 이후 나머지 진행할 수 있게 함
- 한 마디로 순서를 정해주는 것임, 이 순서가 비효율적일 수 있기에 효율적인 방식으로 순서를 정해주는 것
OS에서의 race condidtion (1/3)
OS에서의 race condition (2/3)
Race condition 2
- User mode ➡️ kernel mode ➡️ user mode ➡️ kernel mode ➡️ …
- 작업이 이런 식으로 왔다갔다 할 수 있는데
- 이때 cpu 를 독점 사용하지 않고, 할당 시간 만료되면 cpu 를 반납
문제
- A가 본인 코드 실행하다 시스템콜로 커널에 요청해서 커널이 count ++ 작업 진행하던 도중, 이 프로세스에 할당된 시간이 끝남(count++ 가 끝나지 않은 것)
- 그럼 B가 자기 작업 진행하다 시스템콜로 커널에 요청했고, 그때 count ++ 실행
- 그럼 A는 아까 변수값 + 1 하려 CPU로 읽어들인 부분 그 다음의 인스트럭션을 실행하게 되는데, B에서 증가된 값은 반영 안 됨
- B 들어가기 전 A에서 count 증가 작업이 시작됐기에, B 작업에서 증가한 건 반영되지 않음
- Sync 문제 발생!
해결책
- 프로세스가 커널 모드에 있을 땐 할당시간이 끝나도 CPU를 뺏기지 않게 해야
- 커널 모드에서 유저 모드로 빠져나올 때 CPU를 빼앗는 게 방법
- 물론 커널에서 유저 모드로 바뀔 때 CPU 유실이 아주 작게 발생할 수는 있지만 크리티컬하지 않음
OS에서의 race condition (3/3)
Race condition 3
- CPU 여러 개인 경우
- Race condition 1, 2 의 해결책으로는 해결 불가
- CPU가 여러 개니까, 한 CPU 내에서의 인터럽트는 다른 CPU에 영향을 주지 않기에 sync 문제 발생
- 그래서 여러 CPU가 주체가 되어 데이터에 접근할 땐 lock 을 걺
- 결국 이런 문제는 사용자 프로그램이 아닌, 운영체제 커널을 여러 cpu 가 동시에 접근하기에 생기는 문제
방법 1
- 커널에 접근하는 CPU를 매순간 1개로만 제한하는 것도 방법일 수 있음
- = 커널 전체를 하나의 lock으로 막고, 커널에서 빠져나올 때 unlock 하는
- 근데 이렇게 되면 커널 전체에 lock 을 거니까 되게 비효율적임
방법 2
- 커널 전체에 lock 을 거는 게 아니라, 각각 데이터 별로 lock / unlock 해서 해당 데이터에 접근하는 게 아니라면 여러 CPU가 동시에 커널 코드를 실행할 수 있게 하는 게 훨씬 효율적
Process Synchronization 문제
Example of a Race Condition
- x = x + 1;
- 고급언어에서의 문장 하나가 cpu 에서는 여러 개의 인스트럭션을 통해 실행됨
- 프로세스들 간의 공유 데이터를 건드릴 때
- 혹은 커널의 데이터를 건드릴 때
- 데이터 sync 문제가 발생함
- A 프로세스 수행 중 인터럽트 발생해서 b 프로세스가 cpu 를 잡을 땐
- 사실상 프로세스들의 공유 데이터를 건드린 것이나 커널의 데이터를 건드린 것이 아니라면
- Race condition 문제 발생하지 않음
- 보통 프로세스들은 자기 자신만의 독자적인 주소 공간의 데이터를 처리하기 때문에
The Critical-Section Problem
Critical section
- = 임계 구역
- = 공유 데이터로 접근하는 코드
- 이 프로세스가 critical section 안에 있는, 공유 데이터에 접근하는 코드를 실행 중이면
- CPU를 뺏겨서 다른 프로세스에 CPU가 넘겨지더라도 공유 데이터에 접근하는 것은 불가하게 해야