#1. CPU 가상화의 난제 (2~6p)
비유: “하나의 tv 채널로 수백 명이 각자 보고 싶은 프로그램을 동시에 보게 만드는 법”
설명: “실제 cpu 하드웨어는 하나지만, OS는 수백 개의 프로세스가 동시에 돌아가는 **환상(Illusion)을 만들어야 한다. 이를 위해 OS는 여러 프로세스를 아주 짧은 시간 단위로 번갈아 실행하는 시분할 기법을 사용한다. 각 프로세스는 자기만의 CPU를 가졌다고 착각하며 실행된다.



OS를 통해서 CPU 1개를 가지고 여러 프로세스를 동작한다. 프로세스끼리 알아서 교대하는 것처럼 보이지만, 사실 OS가 중간에 개입하여 제어권을 가져온 뒤 다음 프로세스에게 넘겨주는 방식이다. (시분할 방식)
#2. 프로세스란 무엇인가? (7~9p)
비유: “실행 중인 프로그램의 인스턴스”
설명: 디스크에 저장된 정적인 ‘프로그램’이 메모리에 올라가 살아 움직이면 ‘프로세스’가 된다. 프로세스는 고유한 **PID(Process ID)로 구분되며, 실행을 위한 코드(Code), 데이터(Data), 힙(Heap), 스택(Stack) 영역을 가진다. 또한 CPU의 현재 상태를 나타내는 레지스터 값(CPU context : Program Counter, Stack Pointer 등)도 포함한다.
자바로 비유하면 클래스는 "프로그램(정적)", 객체는 "프로세스(동적)"이다.

- 메모리
텍스트는 프로그램 코드를 포함한다.
스택은 함수의 매개변수, 반환주소, 지역 변수 등을 포함한다.
Data는 전역 변수와 정적 변수를 포함한다.
힙은 메모리 공간을 동적으로 할당한다.
- 레지스터
프로그램 카운터, 스택 포인터, 프레임 포인터를 가진다.
#3. 프로세스의 탄생 과정 (10~11p)
비유: “공연을 위해 무대를 세팅하고 배우를 대기시키는 과정”
설명:
- 로드(Load): 디스크의 코드를 메모리로, 프로세스의 주소 공간으로 가져온다. (필요할 때만 가져오는 Lazy 방식 선호)
- 스택/힙 생성: 지역 변수를 위한 스택(인수들 이용해 스택 초기화 => main함수의 argc, argv배열)과 동적 할당을 위한 힙을 만든다.(프로그램은 메모리 공간 할당을 위해 malloc(), free()로 해제.)
- 입출력 초기화: 기본적으로 표준 입력, 출력, 에러 채널을 연다.
- 시작: main() 함수로 CPU 제어권을 넘겨 실행을 시작한다.
#4. 프로세스의 상태 변화 (13~14)
비유: “은행 대기실의 세 가지 구역”
설명:
Running(업무 중): 실제로 cpu를 점유해 실행 중인 상태
Ready(대기 중): 실행 준비는 끝났지만, OS가 아직 순서를 안 준 상태
Blocked(일시 정지): 입출력(I/O) 요청 등을 하고 결과를 기다리느라 CPU를 써도 의미가 없는 상태
#5. 프로세스 생성과 복제: fork() 와 exec() (15~19, 30-33p)
비유: “세포 분열(fork) 후 성격 개조(exec)”
설명:
fork() : 현재 프로세스를 똑같이 복제하여 자식 프로세스를 만듭니다. 부모는 대부분의 자원들과 특권들을 상속한다. 자식은 또한 부모의 주소공간을 복제한다. 부모에게는 자식의 PID를, 자식에게는 0을 반환하여 구분합니다.
- fork의 첫 번째 의미 "복제" : 현재 실행 행중인 부모의 상태를 그대로 로복사하여 새로운 프로세스를 생성한다는 물리적인 의미.
- fork의 두 번째 의미 "분기" : fork()의 리턴값(0 또는 자식 PID)를 이용해 부모와 자식이 서로 다른 길을 가게 만든다는 논리적인 의미.
복제만 하고 끝난다면 똑같은 일만 반복하겠지만, fork() 이후 if-else문을 통해 자식은 새로운 프로그램으로 변신(exec)하고 부모는 자식을 기다리는(wait)등 역할 분담이 일어나기 때문.
exec() : 새로운 프로세스를 만드는 게 아니라, 현재 몸체에 새로운 프로그램 코드를 덮어씌워 완전히 다른 일을 하게 만듭니다.
+ 프로세스 종료
- Normal exit (자발적)
- Error exit (자발적)
- Fatal error (비자발적)
• 세그멘테이션 오류 – 잘못된 메모리 접근• 보호 오류• 할당된 리소스 초과 등- 다른 프로세스에 의해 종료 (비자발적) - 시그널을 받음으로써
- Zombie process: 종료, 그러나 삭제되지않음
+ 프로세스 계급
- 부모-자식 관계
하나의 프로세스가 다른 프로세스를 만들 수 있음.
Unix가 그 계급을 "process group"이라고 부름.
윈도우는 프로세스 계급의 개념이 없음.
- 프로세스들의 목록을 탐색: ps(Unix에서), Tsk Manager (tastmgr) (윈도우에서!)
fork() 코드의 예시>>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int pid;
if ((pid = fork()) == 0) //자식이다!
printf("Child of %d is %d\n", getppid(), getpid());
else //부모다!
printf("I am %d. My child is %d\n", getpid(), pid);
}
세포분열: fork()가 호출되면 OS는 부모 프로세스를 똑같이 복제해서 새로운 자식 프로세스를 만든다.
서로 다른 리턴값: 똑같은 코드지만, OS는 부모와 자식을 구분하기 위해 다른 값을 던져준다. 자식에게는 0을, 부모에게는 새로 태어난(복제된) 자식의 진짜 PID를 리턴한다.
동시 실행: 이제 두 프로세스(부모와 자식)은 if 문부터 각자 갈 길을 가기 시작한다.
프로세스는 스케줄링에 따라 누가 먼저 실행될지 알 수 없으므로 출력 순서는 두 가지 경우가 생긴다.

복기: fork() 는 자식에게 0을 반환하고, 부모 프로세스에게는 복제된 자식 프로세스의 PID 반환함.
fork()가 일어날 때, 커널 내부(do_fork())에서는 다음과 같은 일이 벌어집니다.
- 새 장부 만들기: 먼저 새로운 PID를 할당하고 , 새로운 PCB를 생성 및 초기화합니다.
- 내용 복사: 부모의 PCB 내용을 거의 그대로 복사하여 자식의 PCB를 채웁니다.
- Ready 큐 등록: 이제 이 자식의 PCB를 Ready 큐에 넣어서, 언제든 CPU를 쓸 수 있게 준비시킵니다.
+exec() : 보통 fork()로 복제만 하고 끝내지 않는다. 자식이 부모랑 똑같은 일만 하면 효율이 떨어짐. 복제된 자식 프로세스에서 exec() 호출하여 자식이 가지고 있던 부모의 코드를 다 버리고 완전히 새로운 프로그램으로 탈바꿈하게 한다.
그러나 자식 PCB에 저장한 부모 PCB내용을 다 버리지는 않는다. 중요한 PID, PPID, 파일 디스크립터, 상태 및 우선순위 등의 정보는 그대로 유지된다.
의문점: fork()를 쓰면 프로세스가 복제되는데, 복제된 자식 프로세스에서 또 복제 즉 무한 복제되는 것은 아닌가? 재귀함수처럼?
답: fork()를 읽는 순간 복제가 일어나고, 새로 생긴 자식 프로세스는 처음부터 읽는게 아니라 부모가 읽던 그 지점부터 읽기 시작함.
의문점처럼 무한 복제되는 코드는 "포크 폭탄(Fork Bomb)"
while(1) {
fork();
}
#6. 운영체제의 장부: PCB(Process Control Block) (Process Descriptor라고도 부름)(23~25p)
"존재하지 않는다(하드웨어아님)"
PCB는 메모리의 커널 영역(Kernel Space)에 존재하는 데이터 조각, 일부일 뿐이다.
프로세스가 종료되면 해당 PCB데이터는 메모리에서 삭제되어 사라진다.
=> PCB는 하드웨어적으로 존재하는 물건이 아니라, 운영체제가 프로세스를 관리하기 위해 메모리에 그려놓은 '가상의 관리 대장'
비유: “100대의 스마트폰을 관리하기위한 상세 관리 대장”
설명: OS는 프로세스를 관리하기 위해 **PCB(Process Control Block)라는 구조체를 유지합니다. 프로세스마다 하나씩 PCB를 생성한다.여기에는 cpu 문맥(context), 프로세스의 상태, 식별 정보(PID), 레지스터 값, 메모리 관리 정보, 현재 작업 디렉토리, 열린 파일 목록 등 모든 정보가 들어있습니다. 리눅스에서는 task_struct(3248 바이트 크기)라고 부릅니다.
"프로세스가 여러 개 실행된다"는 환상을 유지하기 위해 OS는 PCB를 끊임없이 갈아치운다.
저장 > 교체 > 복원 > 재개 로 진행되는데, 타임 아웃이 발생하면 하드웨어는 현재 실행 중인 프로세스의 레지스터 값들을 그 프로세스의 PCB에 저장한다. 그 후 OS는 다음에 실행할 프로세스를 결정 후 정한 그 프로세스의 PCB에 적혀 있던 에전 레지스터 값들을 다시 CPU로 불러온다.(복원) CPU는 예전에 자기가 그 프로세스를 어디까지 실행했는지 알게되고 그 지점부터 다시 재개한다.
이것이 문맥 교환!!!
#7. 문맥 교환: Context Switch (26~28p)
비유: “하던 일을 멈추고 다음 사람을 위해 모든 도구를 정리하고 새 도구를 꺼내는 작업”
설명: CPU가 프로세스 A에서 B로 넘어갈 때, A의 레지스터 상태를 PCB에 저장하고 B의 PCB에서 이전 상태를 복구하는 과정입니다. 이는 시스템에 부하를 주는 **오버헤드(overhead)지만, 초당 수백~수천 번 일어나며 우리를 속인다.
관리 오버헤드
• 레지스터 및 메모리 맵 저장 및 복원
• 메모리 캐시 플러시 및 재로드
• 다양한 테이블 및 목록 업데이트 등
+추가 정보
init 프로세스의 역할: "모든 프로세스의 조상님"
컴퓨터가 켜지고 운영체제가 메모리에 올라간 뒤, 가장 먼저 만들어지는 사용자 프로세스가 바로 **init**입니다. (PID가 보통 1번입니다.) 역할이 많은 이유는 이 프로세스가 시스템의 '관리자'이자 '부모' 역할을 도맡아 하기 때문입니다.
- 시스템 초기화: 부팅 과정에서 디스크 검사, 네트워크 설정, 터미널(로그인 창) 실행 등 시스템이 돌아가기 위한 모든 환경을 설정합니다.
- 고아 프로세스 입양 (Adoption): 부모 프로세스가 자식보다 먼저 죽어버리면 자식은 '고아 프로세스'가 됩니다. 이때 init이 이 자식들을 자신의 자식으로 입양하여, 나중에 자식이 종료될 때 그 종료 상태를 확인해 주고 자원을 정리해 줍니다.
- 서비스 관리: 백그라운드에서 계속 돌아가야 하는 프로그램(데몬)들을 실행시키고, 혹시 죽으면 다시 살려내는 등 시스템 전체의 생태계를 유지합니다.
인터럽트(Interrupt)와 타임 인터럽트(Timer Interrupt)
운영체제가 CPU 제어권을 다시 가져오기 위해 사용하는 '강제 중단 벨'.
- 예를 들어, 키보드를 누르거나 데이터 읽기가 완료되었을 때 발생함.
- 인터럽트 (Interrupt): 하드웨어나 소프트웨어가 CPU에게 "지금 하던 일 잠시 멈추고 이 급한 일부터 처리해 줘!"라고 보내는 신호입니다.
- 타임 인터럽트 (Timer Interrupt): CPU 가상화(시분할)를 위해 가장 중요한 장치입니다.
- 원리: 하드웨어 타이머가 수 밀리초(ms)마다 주기적으로 인터럽트를 발생시킵니다.
- 역할: 특정 프로세스가 CPU를 너무 독점하지 못하도록 OS가 개입할 틈을 만들어 줍니다. 이 신호가 오면 OS는 현재 프로세스를 멈추고(Running → Ready), 다음 차례인 프로세스에게 CPU를 넘기는 **문맥 교환(Context Switch)**을 수행합니다.
'CS > OS' 카테고리의 다른 글
| 운영체제(OS)-WEEK5 (2) [페이징(Paging)]+WEEK6 (1) (0) | 2026.04.03 |
|---|---|
| 운영체제(OS)-WEEK4 [메모리 가상화(Memory Virtualization)] (0) | 2026.04.01 |
| 운영체제(OS)-WEEK3(2) MLFQ 시뮬레이터 구현하기 (with C) (0) | 2026.03.26 |
| 운영체제(OS)-WEEK3(1) 스케줄링(Scheduling) (0) | 2026.03.17 |
| 운영체제(OS)-가상화 (week1) (0) | 2026.03.14 |