Bullshitting Blog

개소리하는 블로그

OS - 1) 프로세스

Tags = [ os ]

진지하게? 는 역시 어렵다. 사실 시도도 안했다. 회고록같은건 진지하게 써놓고 스터디는 안 진지하게 간다고? 그래야 스터디에 흥미를 안 잃기 때문이다. 회고같은 과거로 돌아가는 짓거리는 한 번이면 족하다. 그래서 진지하고 재미없게 써야 한다.

첫 단원은 프로세스다. OS에게 그 짓거리(가상화)를 시키게 한 주범이다. 프로그램을 실행하면 그게 프로세스다. 프로그램은 Disk에 잠들어 있는 프로세스의 전신이다. 대충 관짝에 잠들어 있는 미라를 생각하면 된다. 그걸 실행하면 프로세스로서 도는 것이다. 미라가 든 관짝을 툭툭 쳐서 주문을 읊는 것이다. 약속된 방법으로 읽어다가 프로그램을 메모리에 로드하고, CPU로 연산을 진행하면 프로세스다. 프로세스를 동시에 여러개 돌리고 싶으니, CPU를 시간분할하고, 메모리를 공간분할한다. 마찬가지 이유로, 컴퓨터가 프로그램을 하나만 가지고 있으면 나머지 공간이 아까우니, 공간분할을 통해 복수의 파일을 보관할 수 있게 한다.

분할

분할의 종류를 아주 간단하게 설명하면 아래와 같다.

  • 시간 분할(Time Sharing): 이거 하다가, 저거 하다가, 그거 하다가, …
  • 공간 분할(Space Sharing): 여기부터 여기까진 이거, 저기부터 저거까진 저거, …

프로세스는 시간분할의 예로 나와 있긴 한데, 그냥 뭐든 분할의 개념이 뒤섞여 있다. 프로세스들이 올라간 메모리의 각 위치는 공간 분할의 결과다. 프로세스들의 실제 연산은 시간분할을 통해 병렬인 것처럼 돌아간다.

디스크도 마찬가지다. 공간분할의 예로 나오지만, 디스크의 읽기/쓰기 동작은 시간분할이다. 얘도 어쨌든 하드웨어가 뭔 짓을 해야 일을 할 것 아닌가.

그래서 뭐는 뭐! 하는 사고방식은 오개념을 낳기 아주 쉽다고 본다.

시간 분할의 레벨

  • High-level(Policy): Scheduling - 이럴땐 이렇게… 지금이닷! Context Switching!
  • Low-level(Mechanism); Context Switching - 눼, 실행! @_@

그래서 프로세스가 뭔데?

책에서는 실행중인 프로그램에 대해 제공하는 추상화를 프로세스라 했다. 사실 프로세스가 뭐냐 물으면 실행중인 프로그램이라고 할텐데, 다음 설명을 위한 밑밥으로 저렇게 어려운 표현을 쓴 듯 하다. 근데, 이렇게 생각하면 재미가 없다. 추상화는 주판과 계산기를 생각하면 직관적으로 이해하기 쉽다. 사람이 주판 사용법을 익혀서 손으로 하나하나 튕구는건 불편하다. 그 과정은 따로 배워야 할 정도로 복잡하다. 그런데 그 과정을 묶어다가 버튼 기반 인터페이스를 올린다면? 물론 계산기 내부가 주판으로 되어 있는건 아니겠지만 말이다. 여튼 계산기는 a + b를 구하기 위해 손가락을 이래저래 튕길 필요 없이, a + b를 버튼으로 입력하기만 하면 된다. 계산하는 과정을 모아다가 사람이 쓰기 좋게 추상화한 결과인 것이다.

프로세스의 구성 요소

프로세스에서 접근할 수 있는 Machine State의 범위라고 보면 된다. Machine State는 CPU + 메모리 + I/O 장치 전체의 현재 상태를 뜻한다. 컴퓨터 켜는데 최소로 필요한게 CPU, 메모리, 디스크다. 그것들의 현재 상태라는 이야기다. 그냥 컴퓨터의 지금 상태다. 그리고 거기서 프로세스가 접근할 수 있는 범위는 아래와 같다.

  • 프로세스가 접근 가능한 메모리 영역(address space)

    • 프로그램을 메모리에 로드했을 때 그 구역이다.
    • 익히 들은 스택, 힙, 코드 영역 등이 그거다.
  • 레지스터(register)

    • 대충 CPU가 연산 중에 값을 잠시 보관해두는 곳이다. CPU에선 아래와 비슷하게 사용한다.
    MOV AX, 10 ; 10을 AX로 보내라
    MOV BX, 20 ; 20을 BX로 보내라
    ADD AX, BX ; BX를 AX에 더해라
    MOV BX, AX ; AX를 BX로 보내라
    ...
    
  • 영구저장장치(persistent storage device)

    • 디스크와 같이 영구저장을 위한 장치들이다. SSD, HDD, Flash 메모리가 대표적.
    • 근데, NVRAM은? → GPT선생이 가라사대, 그렇게 분류할 수는 있는데, OS 교재에서는 그거 취급 안한다 하였다.

Process API

아래는 OS에서 프로세스 관련 API로 제공해야 하는 것들이다.

  • Create
  • Destroy
  • Wait
  • Miscellaneous Control
  • Status

켜고(create) 끄는건(destroy) 당연히 있어야 하고, 부모가 자식 프로세스가 끝날 때까지 기다릴 수 있어야 하고(wait), 근데 이런저런 기능도 했으면 좋겠고(miscellaneous control), 프로세스의 현재 상태도 볼 수 있어야(status) 한다는 것이다.

Process Creation

잠든 미라를 깨워보자. 이 미라는 광부였다. 일단 디스크에서 파일이라는 관짝을 열어다가 미라를 갱도의 담당 구역으로 옮기는데(물론 실제로는 이동이 아니라 복사다), code와 static data을 메모리의 적절한 address space에 로드하는 것이다. 다음엔 담당 구역에 표시를 해준다. 스택과 힙 영역을 할당하는 것이다. 이후 미라를 깨워서 곡괭이를 손에 쥐어주고, “일해라” 하면, 그 미라는 일을 하게 될 것이다. CPU가 메인 함수를 시작으로 로직을 실제로 실행하는 것이다. 이 때, 메인함수여야 하는 이유는 간단하다. “일해라” 해야 일에 필요한 모든 것을 한다. “내려쳐라” 하면 그냥 그 자리에서 곡괭이를 아래로 내려치고 멈출 것이다. 다음은, 채광한 광물을 어딘가 저장하고 옮겨야 한다. 그래서 수레를 사용하면 이게 I/O 작업이다. 그 수레를 옮겨 광물을 창고로 넣으면 이건 영구저장장치에 저장한 무언가가 된다.

Process States

프로세스의 상태는 세 가지가 있다. 이거 state다. Status 아니다.

  • Running: 내 일 지금 진행되고 있음. 땡큐
  • Ready: 나 이제 준비됨. 시간 되면 내 일도 해줘.
  • Blocked: 아 뭐 기다리는 중임. 딴거 먼저 하세요. 아 건들지 말라고!

Running과 Ready 상태 사이의 전환은 스케줄러가 해준다. “피카츄, 너로 정했다! 가랏 백만볼트!” 해주는 것이다.

여기서, Blocked가 뭔지 궁금할 수 있다. 간단히 설명하면 정말 지금 다음껄 실행하면 안되는 상태다. 예를 들어, 파일의 내용을 읽는 작업이 있다고 치자. 그 작업을 디스크에 요청하면, 그 디스크가 그 작업을 끝내거나 중단할 때까지 프로세스의 다음 절차를 실행할 수 없다. 읽으라 해놓고 읽는걸 기다리지도 않고 “빨리 설명해봐!“ 하면 미친놈이다. 그래서 그걸 기다려달라고 표시하는 상태가 blocked다. Ready는 그냥 뒷전으로 밀려나 있는 일거리일 뿐, 언제든 실행될 수 있다.

하지만 실제로 예시로 제공된 xv6의 enum을 살펴보면, 다른 상태들도 몇 개 있다. 미사용(UNUSED), init 과정(EMBRYO), task가 다 돌고 난 이후(ZOMBIE)가 또 있다. 그리고 리눅스껀 또 다르다. 결국 이것도 구현하기 나름이라는 것이다.

UNUSED는 PCB(process control block)의 프로세스를 올릴 수 있는 슬롯이 미사용중이니 여기다 넣어도 된다는걸 나타내는 상태이다. 그래서 더 파보니, xv6같은 단순 OS는 미리 이 슬롯들을 정해진 크기로 할당해 둔 상태이기 때문에 있는 상태였다. 실제로 리눅스와 같이 실제로 이용하는 OS들은 동적으로 할당하는 방식이라 UNUSED 상태가 없다.

Data Structures

OS도 결국 다른 프로그램처럼 하나의 프로그램이기 때문에, 내부에 각종 데이터구조가 있다. 프로세스 구조체는 직접 그 소스를 보면 이해할 수 있다. 그중에서 가장 중요한 것은 context라 생각한다. 그리고 그 context 구조체를 보면, 그냥 레지스터의 값을 담는 구조체다. context switching이 발생하면, 직전에 running 상태였던 프로세스는 자신의 context에 레지스터를 저장하고, deregistered될 것이고, 이 프로세스는 context에 있던 레지스터의 값들을 레지스터로 다시 배치해서 EIP에 있는 코드를 실행할 듯 하다. 물론 context에 매 절차 마다 레지스터를 저장하는지, 컨택스트 스위칭이 일어날 때 저장하는지는 아직 모른다. 그건 OS를 개발하는 사람이 선택하면 될 일이기 때문에, 뚜렷한 답은 없을 것이고, 구현의 차이가 있을 듯 하다. 매번 저장하고 있으면 성능에 손해를 보기 때문에 아마 대부분 context switching이 일어날 때 저장하지 않을까 싶다. 물론 또다른 이유로 저장 주기를 타이트하게 가져갈 수도 있다. 장애를 대비한다던가? 우주방사선(????)을 대비한다던가?

다음으로, 프로세스의 실행 공간에 대한 정보가 있다. address space에 대한 것이다. 그 외에, 프로세스의 현재 상태에 관한 정보들이 포함되어 있다. process state 뿐만 아니라, 다른 더 필요한 것들도 있다. 쥐고 있는 파일들이라던가, 현재 디렉터리라던지 말이다.