프로세스 — 격리된 실행 단위
OS가 CPU를 추상화한 것이 프로세스라고 했다. 좀 더 정확히 말하면, 프로세스는 실행 중인 프로그램의 독립된 인스턴스다. 같은 메모장 프로그램을 두 번 열면 프로세스가 둘이고, 둘은 서로의 메모리를 전혀 못 본다. 이 격리가 프로세스의 핵심 가치다 — 한 프로그램이 폭주해도 남에게 번지지 않는다. 그 격리가 어떻게 이뤄지고, 프로세스를 어떻게 만들고 전환하는지를 본다.
프로세스는 무엇으로 이뤄지나 — 주소 공간과 PCB
프로세스 하나에는 자기만의 가상 주소 공간이 통째로 주어진다. 이 공간은 보통 네 칸으로 나뉜다.
코드(실행할 명령)·데이터(전역 변수)·힙(동적 할당 객체가 사는 곳)·스택(함수 호출·지역 변수)이다. 그리고 OS는 각 프로세스를 **PCB(Process Control Block)**라는 자료구조로 관리한다 — PID(번호)·현재 상태·우선순위·레지스터 값·열린 파일·메모리 정보 등 그 프로세스의 신분증이자 저장된 상태다. (출처: Process vs Thread.)
격리의 비결은 가상 주소 공간이다. 프로세스 A의 0x1000번지와 B의 0x1000번지는 — 같은 번지처럼 보여도 물리 메모리의 전혀 다른 곳으로 번역된다(다음 가상 메모리 편). 그래서 A는 B의 메모리를 가리킬 방법조차 없다 — 하드웨어가 번역 단계에서 막는다. 이게 프로세스 격리의 실체다.
프로세스는 어떻게 태어나나 — fork와 exec
새 프로세스는 무에서 생기지 않고 — 기존 프로세스가 자신을 복제해 만든다. 유닉스 계열에서는 두 시스템 콜의 조합이다. **fork**는 부모를 거의 그대로 복제한 자식 프로세스를 만든다(주소 공간·열린 파일까지 복사 — 실제로는 copy-on-write로 바뀔 때만 복사해 효율적이다). **exec**는 그 프로세스의 내용을 다른 프로그램으로 덮어쓴다. 그래서 셸이 명령을 실행할 때 — fork로 자식을 만들고, 그 자식이 exec로 원하는 프로그램이 된다. 모든 프로세스는 이렇게 부모-자식 트리를 이룬다.
한 번에 하나뿐인 CPU를 나눠 쓰기 — 상태와 컨텍스트 스위치
CPU 코어는 한 순간에 한 프로세스만 실행한다. 그래서 프로세스는 여러 상태를 오간다 — 준비(ready, 실행 대기), 실행(running, CPU 점유 중), 대기(waiting, I/O 등을 기다림). OS는 이들을 빠르게 번갈아 실행해 동시에 도는 것처럼 보이게 한다. 한 프로세스에서 다른 프로세스로 CPU를 넘기는 작업이 컨텍스트 스위치인데 — 현재 프로세스의 레지스터·상태를 PCB에 저장하고, 다음 프로세스의 것을 복원한다.
문제는 — 프로세스 간 컨텍스트 스위치는 비싸다는 것이다. 주소 공간이 바뀌므로, CPU의 TLB(주소 변환 캐시)와 캐시를 비워야(flush) 한다 — 그러면 그다음 한동안은 캐시 미스로 느려진다. (출처: Context switch.) 바로 이 프로세스 전환의 비싼 값 때문에 — 더 가벼운 실행 단위가 필요해졌고, 그게 다음 편의 스레드다.
실무에서는 — 프로세스가 격리됐다는 건 서로 통신하기도 까다롭다는 뜻이다. 그래서 *프로세스 간 통신(IPC)*에는 파이프·소켓·공유 메모리·시그널을 따로 쓴다. 서비스를 별도 프로세스로 나누면 — 격리·독립 배포를 얻지만 통신 비용이 따른다는 트레이드오프가 있다. 거꾸로, 전역으로 하나만 공유되는 자원이라 해도 그건 한 프로세스 안의 이야기다 — 프로세스가 다르면 메모리도 다르니, 공유하려면 IPC가 필요하다.
정리하면, 프로세스는 실행 중인 프로그램의 독립 인스턴스로 — 자기만의 주소 공간(코드·데이터·힙·스택)과 PCB를 갖고, 가상 주소 공간으로 서로 완전히 격리된다. fork+exec로 부모에서 복제돼 태어나고, 준비·실행·대기를 오가며 컨텍스트 스위치로 CPU를 나눠 쓴다 — 단 프로세스 전환은 주소 공간·TLB를 갈아 비싸다. 격리는 안전을 주지만 통신(IPC)과 전환의 비용을 부른다. 이 비용을 줄이려는 더 가벼운 실행 단위, 스레드가 다음이다.