자료 출처 : KOCW 운영체제 - 반효경 교수님
시분할 방식에서는 프로그램 여러개를 번갈아가며 실행시켜, 마치 모든 프로그램이 동시에 실행되고 있는 듯한 느낌을 주게 한다고 했다. 이를 위해 어떠한 개념, 도구들이 필요한지 알아본다. 또한, 여러개의 process를 다루는 것은 굉장히 힘들고 시간이 많이 드는 작업인데, 이를 조금 더 효율적으로 만들어 줄 수 있는 thread라는 개념도 알아본다.
1. Process - 그래서 뭘 어떻게 한다는거지?
용어 정리를 먼저 하고 간다. Process는 실행중인 프로그램이다. (a program in execution) 그냥 디스크에 댕그러니 들어있는 코드 덩어리를 실제로 실행시키면, 그 친구들이 process라고 부른다.
무엇을 할지는 이미 말했다. 여러 process의 실행!
여기서는 '어떻게'에 대해 다뤄볼 것이다. 실제로 여러 process를 실행하기 위해 필요한 것들에 대해 알아본다.
그냥 읽지말고, 내가 process들을 관리하는 프로그램을 만든다면, 어떻게 해야할까?? 라는 생각을 먼저 하고 읽어보자. 자연스럽게 pcb같은 것들이 필요하다는 것이 당연하게 느껴질 것이다.
1) Context
Process들이 번갈아 실행될 때, 기존 process를 실행하는 것과 관련된 내용들을 저장해두지 않으면, 다시 차례가 돌아왔을 때 시작할 갈피를 잡을 수 없어진다.
앞에서도 말했지만, cpu는 그냥 명령을 주면 실행할 뿐이다. 얘가 뭘 기억할 수 있고 그런 것은 아니라는 것.
따라서 process 실행과 관련된 어떠한 것들을 문맥, context라고 일컫고, 이 친구를 가지고 놀기로 했다.
또 다르게 말하면 특정 시점을 놓고 봤을 때, 어디까지 와있는가를 나타내는데 필요한 모든 것을 합쳐놓은게 context이다. 다음과 같은 내용들이 포함된다.
CPU
instr를 어디까지 실행했는지를 알기 위해서는 cpu 수행 상태를 알아야한다
- program counter
- process의 어디까지 실행했는가 알아야할 것이기 때문. 나중에 다시 실행하려면 당연히 어디까지 실행됐는지 알아야겠지?
- register에 저장된 값
Memory
memory와 관련된 것도 알아야한다. 현재 시점에 process의 메모리 공간에 뭐가 있는지에 대한 것 말이다. CPU에게 줄 데이터들의 상태들이니, 당연히 알아야겠지?
Kernel
- PCB(process control block)
- 이 친구 자체를 context라고 봐야하는지는 모르겠지만, context에 해당하는 정보들을 여기다 넣는다.
- kernel stack
- 각 process가 system call을 하면 몇 가지 과정을 거친 후 pc가 운영체제 메모리 공간의 어딘가를 가리키게 되고, 여기서 함수호출이 일어날 수 있다.
- 커널 code는 여러 process들이 공유하는 공간이라고 볼 수 있는 것이다.
- 따라서 함수호출이 일어나서 stack에 쌓일 때는 process별로 별도의 커널 stack을 할당한다. 그래서 얘도 context로 남겨둬야한다.
2) State
Process들을 돌려가며 실행하기 위해 필요한 정보들이 대충 뭐가 있는지는 알아봤다. 그런데 사람들이 열심히 컴퓨터를 사용하다보니, process마다 현재 어떤 상태인지 구분을 해놓을 필요가 있더라. 얘는 지금 io 작업이 끝날 때 까지 기다리고 있다던가, 아니면 cpu에 올라가기를 기다린다던가. 그러한 것을 나타내는게 state이다.
기본적인 state
process는 state가 변경되며 수행된다.
Running
- cpu를 잡은 친구의 상태
- cpu를 잡는 process는 매 순간 하나이다
Ready
- cpu기다리는 친구들의 상태
- 얘는 필요한 부분이 물리적 메모리에 올라와있는 등, 모든 준비는 끝나있어야 한다. cpu만 얻으면 되는 상태
Blocked(wait, sleep)
- instr 실행을 못하는 상태
- 디스크에서 뭐 읽어온다던지, io결과 기다린다던지. cpu를 얻어봐야 뭐 실행을 못하는 상태
- 나중에 나오는데, 물리적 메모리가 아니라 디스크에 실행해야할 코드 부분이 내려가있는 경우도 여기에 해당한다.
- process 자신이 요청한 io 작업과 같은 event가 만족되지 않아 이를 기다리는 상태
경우에 따라 아래를 추가한다.
New
Terminated
- Process 수행이 종료된 상태
- instr수행은 다 마치고, 정리중인 상태
State 변화의 시각화
여러 process의 state 변화를 컴퓨터 구조 위에서 시각화 시켜보면 위와 같다. 그냥 쭉 읽어보자
- cpu에 올라가 있는 친구는 running
- ready queue에서는 자기 차례를 기다리는 ready상태의 process들
- 그러다 disk 읽기 작업이나 io 작업이 필요한 친구는 io queue에 올라가 자신의 작업 차례를 기다린다. blocked상태
- io 작업이 성공적으로 마쳐지면 device controller는 interrupt를 걸고 os가 cpu를 잡는다. os는 device의 local buffer의 데이터를 memory로 옮겨주거나 하는 작업도 하지만 process의 상태를 다시 ready로 바꿔주는 작업도 할 것
- resource queue. 공유데이터에 여러 process가 동시에 막 접근하면 안된다. 일관성이 깨지기 때문이다. 쉽게 말해 꼬인다는 뜻이다. 그래서 이미 접근중인 경우 동시 접근하는 경우를 막아주기 위해 queue를 두고 순서를 매긴다.
💡 가만 보면 뭔가 좀 오래 걸리는 작업이 필요하면 process는 blocked 상태로 돌아간다. 이는 또한 자신이 초래한 작업이라는 것을 명심하자
운영체제 커널의 data에 이런 queue 자료구조를 만들어놓고 활용한다고 한다.
3) pcb
커널이 process마다 관리를 위해 만들어놓는 자료구조
이다.
- os가 관리상 사용하는 정보
- process state, id
- scheduling information.
- queue가 있다고 fifo는 아니고, 다른 우선순위 등에 의해 스케쥴링 될 수 있다.
- 후에 나오지만, pcb는 queue에 node로 들어가게 되기에, pointer도 존재한다.
- cpu 수행 관련 하드웨어 값. 문맥을 표시하기 위한 정보
- program counter, registers
- 메모리 관련
- 파일 관련
- process가 사용하는 파일이 어떤건지에 관한 것
위 외에도 다양한 것들이 포함될 것이다.
4) 문맥 교환 context switch
Process는 cpu를 얻었다 잃었다 하니까 context가 필요하다고 했다.
하나의 사용자 process에서 다른 사용자 process로 넘어가는게 context switch이다.
위에서 본 context와 관련된 정보 등등은 pcb에 저장한다고 했다. 새로 cpu를 얻는 process의 pcb에 저장된 정보를 하드웨어에 복원, cpu넘겨줌으로써 context switch를 완성한다.
주의!
좀 헷갈리기 쉬운 개념이 있다.
사용자 process에서 os로 cpu가 넘어갈 때는 context switch가 꼭 일어나는건 아니다.
- sys call이나 interrupt 후 다시 원래 process에게 cpu가 넘어가면(1) context switch가 아니다.
- 그냥 유저모드에서 프로세스가 실행중이다가, 커널모드 갔다가, 다시 오는것일 뿐이다.
- 물론 여기서도, os 함수와 관련된 isntr등을 실행하기 위해서는 cpu를 써야하기 때문에 register 값 같은 약간의 context은 저장되어야한다.
- 그러나 process 자체가 바뀌는거에 비해서는 오버헤드가 훨씬 적다.
- 보통 context switch가 일어나면 캐쉬를 싹 밀어버리지만 여기선 그렇게까지는 안한다고 한다.
- timer같이 process를 쫓아내는 상황이 발생하거나, io요청이 일어나 원래 프로세스가 blocked가 됐거나(2) 하면 context switch가 일어난다. 다른 process한테 cpu 줘야하기 때문이다.
5) queue
마구잡이로 processe들을 번갈아 실행시킬 수는 없다. 각 process들을 state에 따라 정리정돈 해야한다. 이를 위해 queue를 사용하며 여러 queue가 있다.
job queue
- 현재 시스템 내에있는 모든 process들의 집합
- ready, device q에 있는 것들을 다 포함한다
ready queue
- 메모리 내에 있으면서 cpu잡아서 실행되기를 기다리는 애들
device queue
- io device의 처리를 기다리는 process의 집합
시각화
이런식으로 pcb 포인터를 연결해 queue를 만든다!
6) Scheduler
여러 process들의 state를 바꾸고, 또 어떤 process들의 state를 바꿀지를 결정하고, 실제로 수행하는 친구이다.
당연히 코드 덩어리이다.
short-term scheduler(cpu scheduler)
- 다음에 어떤 process를 running으로 할지 결정
- cpu 주는 문제 관리
- 스케쥴링 단위가 ms단위
long-term scheduler(job scheduler)
- process에 메모리(및 각종 자원)을 주는 문제
- process가 시작될 때 new에서 ready로 가는데, 그 과정에서 admitted라는게 있다
- 이게 메모리에 올라가는 과정이다. 그걸 허락해주는게 admitted
- long term scheduler가 new 상태이 process에게 메모리를 할당해줄지 말지를 결정
- ready queue로 보내는거 결정하는 역할
- degree of multiprogramming을 제어
- 메모리에 여러 프로그램이 동시에 올라가는 것을 multiprogramming
- 즉 d o m은 메모리에올라가는 프로세스의 수 제어인것이다.
- 10개의 new가 있을 때 10개를 다 올리면 degree는 10, 1개면 1
- 메모리에 프로그램이 너무 많이 올라가도, 적게 올라가도 성능이 안좋아진다. cpu를 너무 놀려도 안되고, 오버헤드가 너무 발생하게 해도 안되니깐
- 근데 보통 우리가 사용하는 time sharing system에서는 장기 스케쥴러가 없고, 그냥 바로 ready로 전환시킨다고 한다.
- 이걸 요즘에는 medium-term scheduler로 해결한다!
medium-term scheduler(swapper)
- 요즘에는 일단 프로그램 시작하면 메모리는 할당해준다
- 메모리에 너무 많은게 동시에 있으면 문제가 되니까 얘가 일부 프로그램을 골라서 메모리에서 쫓아낸다. 이렇게 degree of multiprogramming을 제어
- 얘 때문에 추가되는 상태가 suspended(stopped)임.
새로운 state graph
- suspended(stopped)
- 외부적인 이유로 process 수행이 정지된 상태
- 자기 자신이 초래한 것(io 요청 등)에 의해 process 수행이 정지되는 blocked와는 다르다는 것을 명심하자
- process는 통째로 디스크에 swap out된다. medium-term scheduler가 뺏는 것
- 또한 사용자가 프로그램을 일시 정지시키는 경우(break key. ctrl z같이)에도 suspended가 된다. 사람이 다시 재개시키면 메모리에 올라간다.
- inactive / active 상태가 전환되는 것은 외부에서의 개입이 있어야 하는 것
- blocked, ready에서 suspended가 되는 경우 각각 상태가 suspended blocked/ready이다. 서로 다르다.
- 근데 suspended될 때 io 요청을 기다리는 중이었을 수도 있는 것 아닌가? 그래서 suspended blocked에서 suspended ready로 바뀔 수도 있다
- 메모리에 올라가있는건 아니라서 cpu 입장에서 뭘 할수는 없지만 이렇게 상태가 바뀔 수 있다
💡 blocked : 자신이 요청한 event가 만족되면 ready
suspended : 외부에서 resume해주어야 active
process의 상태는 운영체제가 얘들 관리하려고 나눠놓은거이다. 그래서 ‘운영체제가 running이다’ 라는 말은 안한다고 한다. syscall을 날렸을 때, 표현하기를, 아직 그 process가 running인데 kernel mode/monitor mode로 running 중이라고 표현한다. 또한 interrupt가 들어와도 running이라고 간주. 다만 kernel mode로 running하는 것이다.
2. 효율적일 수는 없을까?
메모리 올리고 내리고 뭐 신경쓸게 많다. 안그래도 힘든데 조금 더 가볍게 만들 수는 없을까? 또 process가 하나의 업무를 하다가 막혀 너무 오래걸리면, 그 동안 다른 일을 하게할 수는 없을까?
1) Thread
동일한 일을 하는 process가 여러개라면, 별도의 주소 공간을 갖게 되니 메모리가 낭비된다. 따라서, 같은 메모리 공간을 공유하지만 code의 다른 부분을 실행하게 하는 무언가가 필요했고, 그게 바로 thread
이다.
다르게 말하면, process내에, cpu 실행 단위만 다르게 둔 것들을 thread라고 한다.
또, cpu utilization의 단위 라고 할 수도 있다.
기본 구성
- PC, Register 값들(CPU수행과 관련된 정보)을 개별적으로 저장해야할 것이다.
- 각자 실행하고 있는 것들이 다르니, stack에 쌓이는 것들, 각 순간에서 stack의 상태도 다를 것이다. stack도 별도로 둬야할 것이다.
- code, data, OS 자원들고 같은 공유할 것이다.
- thread를 lightweight process라고 부르기도 한다.
공유할 것들은 최대한 공유하고, 나눌 것은 나눈다.
장점
Responsiveness
- multi thread로 구성된 task 구조에서는 하나의 서버 thread가 blocked이더라도 동일한 task 내의 다른 thread가 running되어 빠른 처리를 할 수 있다.
- 네트워크를 통해 웹페이지를 읽어오는 작업도 사실 io작업이라고. 그 동안 내 웹 브라우저는 blocked일 것. 아무일도 못하게 된다. 사용자 입장에서는 답답할 것이다.
- 여러 thread로 웹 브라우저를 만들면, 네이버 서버에서 뭘 가져오는 동안 다른 thread는 display라도 해주는 식으로 만들 수도 있다고
Resource Sharing
- 당연히 메모리 사용량을 줄일 수 있다. code, data등을 공유하므로
Economy
- thread 추가, thread간의 context switching은 오버헤드가 크지도 않다.
Utilization of MP(Multi Processor)
- 동일한 일을 수행하는 thread들이 협력하여 높은 throughput을 얻을 수 있다.
- 병렬성을 높일 수 있다.
- 1000*1000 행렬들을 곱하는 연산의 경우 cpu가 하나면 이걸 다 순차적으로 처리한다.
- cpu가 여러개면 이걸 병렬적으로 수행하고, 그냥 합쳐주기만 하면 된다.
2) Implementation of Threads
간략하게만 알아본다.
Kernel Threads
- 운영체제가 thread가 여러개가 있다는 사실을 알고 있다.
- thread에서 다른 thread로 cpu가 넘어가는 것도, kernel이 스케쥴링해서 관리해준다.
User Threads
- 라이브러리를 통해 지원된다. process에 thread가 여러개인 것은 운영체제가 모른다. process가 알아서 관리