문서 읽는 데 56분 · B1

B-1: 운영체제와 프로세스

목차 29
전체 12강 중 3강 · CS 기초지식
난이도 · 중급

ℹ️컴퓨터 구조·운영체제·네트워크 — 언어 밑에 깔린 원리와 기술 면접 CS 질문 대비. 코딩에 어느 정도 익숙해진 뒤가 좋아요.

안녕하세요, 홍순구 튜터입니다. CS 기초 세 번째 시간이에요.

지난 시간(A-2) 마지막에 우리는 인터럽트를 봤고, 약속 하나를 남겼죠. 우리가 쓰는 컴퓨터는 음악을 틀어놓고, 브라우저를 켜고, 메신저를 띄우고 — 수십 개 프로그램이 동시에 도는 것처럼 보이는데, CPU(코어)는 몇 개뿐인데 어떻게 이게 가능하냐고요. 그 답의 열쇠가 타이머 인터럽트라고 했습니다. 운영체제가 타이머 인터럽트로 CPU를 잠깐씩 되찾아, 프로그램들의 상태를 저장했다 복원하며 번갈아 실행한다고요. 오늘 그 약속을 지킵니다.

그 "프로그램들을 번갈아 돌리는 보이지 않는 지휘자"가 바로 운영체제(OS, Operating System)예요. 그리고 운영체제가 다루는 실행 단위 하나하나를 프로세스(process)라고 부릅니다. 지난 시간 마지막에 이름만 살짝 꺼냈던 그 프로세스를, 오늘 정식으로 만납니다.

운영체제 영역은 기술 면접에서 가장 깊고 자주 파고드는 곳이에요. "유저 모드와 커널 모드가 뭔가요"·"시스템 콜이란"·"프로세스 메모리 구조를 설명해보세요" — 이 셋은 운영체제 면접의 3대 단골입니다. 오늘 다 잡고 갑니다.

오늘 우리가 걸을 길은 이렇습니다.

텍스트
   오늘의 여정 — 운영체제 안으로, 그리고 프로세스로

   ① 운영체제란 — 하드웨어와 프로그램 사이의 매니저 (자원 관리자·커널)
   ② 유저/커널 모드 — 함부로 못 건드리게 권한을 둘로 나누기
   ③ 시스템 콜 — 응용 프로그램이 커널에게 일을 부탁하는 창구
   ④ 프로세스란 — "실행 중인 프로그램"의 정식 정의
   ⑤ 메모리 구조 — 프로세스가 메모리를 쓰는 네 칸 (코드·데이터·힙·스택)
   ⑥ PCB·상태 전이 — OS가 프로세스를 기록하고 갈아 끼우는 법
   ⑦ 프로세스 생성 — fork로 부모가 자식을 낳는다
   ⑧ IPC — 격리된 프로세스끼리 대화하는 법

①~③은 운영체제가 무엇이고 어떻게 보호받는지, ④~⑧은 운영체제가 다루는 주인공인 프로세스 이야기예요. 그리고 길목마다 기술 면접 단골 질문이 숨어 있습니다.

💡 오늘 수업의 핵심 — "운영체제는 하드웨어 자원을 나눠주는 관리자이고, 실행 중인 프로그램 하나하나를 프로세스로 다룬다" 🎯

🎯 학습 목표

  • 운영체제가 왜 필요한지(자원 관리자)와 커널·유저/커널 모드·시스템 콜을 이해하고, 면접 단골인 "유저 모드와 커널 모드의 차이"에 원리로 답합니다.
  • 프로세스가 프로그램과 어떻게 다른지 구분하고, 프로세스 메모리 구조(코드·데이터·힙·스택)를 그림으로 설명합니다.
  • PCB프로세스 상태 전이도로 운영체제가 프로세스를 관리하는 법을 이해하고, fork(생성)와 IPC(프로세스 간 통신)의 개념을 잡습니다.

Step 1: "운영체제란 무엇인가 — 자원을 나눠주는 매니저"

지난 시간에 본 컴퓨터의 풍경을 떠올려봅시다. CPU 하나(혹은 코어 몇 개), RAM 한 덩어리, 디스크, 키보드, 화면… 이게 전부 하드웨어예요. 그런데 우리가 컴퓨터로 하는 일은 음악 재생, 문서 작성, 웹 서핑처럼 수십 가지가 동시에 벌어집니다.

여기서 문제가 생겨요. CPU는 하나인데 프로그램은 여럿입니다. RAM도 한 덩어리인데 여러 프로그램이 다 쓰고 싶어 하죠. 누가 언제 CPU를 쓸지, 어느 프로그램이 RAM의 어느 칸을 쓸지 — 이걸 정해주는 중재자가 없으면 프로그램들이 서로 충돌해 컴퓨터가 엉망이 됩니다.

그 중재자가 운영체제(OS)예요. 한 줄로 정의하면, 하드웨어와 응용 프로그램 사이에서 한정된 자원을 여러 프로그램이 다툼 없이 나눠 쓰도록 관리하는 소프트웨어입니다.

비유하면 식당 매니저예요. 주방(CPU)은 하나, 테이블(메모리)은 한정돼 있는데 손님(프로그램)은 여럿이죠. 매니저가 없으면 손님들이 서로 주방에 쳐들어가 난장판이 됩니다. 매니저(OS)가 "이 손님부터, 다음은 저 손님" 하고 순서를 잡고 테이블을 배정해줘야 식당이 굴러가요.

운영체제가 나눠주는 자원은 크게 넷입니다.

  • CPU 시간 — 누가 언제 얼마나 실행할지
  • 메모리 — 어느 프로그램이 RAM의 어느 칸을 쓸지
  • 입출력 장치 — 디스크·프린터·네트워크를 누가 먼저 쓸지
  • 파일 — 디스크의 데이터를 어떻게 읽고 쓸지

그래서 운영체제를 한마디로 자원 관리자(resource manager)라고 부릅니다. 지난 시간에 본 타이머 인터럽트가 바로 이 "CPU 시간"을 나누는 구체적 수단이에요. 타이머가 일정 간격으로 CPU를 운영체제에 돌려주면, 운영체제가 "이제 다음 프로그램 차례" 하고 갈아 끼웁니다.

이 운영체제의 한복판, 가장 핵심부를 커널(kernel)이라 부릅니다. kernel은 영어로 "알맹이·핵"이라는 뜻이에요. CPU 분배·메모리 관리·장치 제어 같은 가장 중요한 일을 맡고, 컴퓨터가 켜져 있는 내내 메모리에 상주하는 핵심 코드입니다. 우리가 보는 바탕화면·아이콘은 운영체제의 겉껍데기고, 진짜 알맹이는 보이지 않는 커널이에요. 운영체제는 메모리를 커널이 쓰는 영역(커널 공간)과 응용 프로그램이 쓰는 영역(유저 공간)으로 나눠두는데, 이 구분이 다음 Step의 핵심이 됩니다.

텍스트
   ┌───────────────────────────────┐
   │   Application                 │──── 응용 프로그램 (브라우저·메신저·게임)
   ├───────────────────────────────┤
   │   Operating System (Kernel)   │──── 운영체제 = 자원 관리자 (중재 계층)
   ├───────────────────────────────┤
   │   Hardware                    │──── CPU·메모리·디스크·입출력 장치
   └───────────────────────────────┘
   응용 프로그램은 하드웨어를 직접 못 만지고, 반드시 운영체제를 거친다
🙋 학생 질문 — "운영체제도 결국 프로그램 아닌가요? 그럼 운영체제는 누가 관리해요?"

날카로운 질문이에요. 맞아요, 운영체제도 소프트웨어입니다. "그럼 운영체제는 누가 띄워주냐"는 닭이 먼저냐 달걀이 먼저냐 같은 고민이죠.

답은 부팅 과정에 있어요. 전원을 켜면 하드웨어에 박힌 작은 프로그램(펌웨어)이 먼저 돌고, 그게 디스크에서 운영체제 커널을 찾아 메모리에 올려줍니다. 정해진 순서로 하드웨어가 커널을 "최초로 한 번" 띄워주는 거예요.

일단 커널이 떠서 자리 잡으면, 그 다음부터는 커널이 최고 권한자로서 다른 모든 프로그램을 관리합니다. 커널을 관리하는 별도의 누군가는 없어요. 커널 자신이 시스템의 최종 관리자입니다. (전원을 켜는 순간부터 커널이 뜨기까지의 자세한 부팅 과정은 linux 과목에서 실전으로 다뤄요.)

🎯 면접에서는 이렇게 답하세요

"운영체제는 하드웨어와 응용 프로그램 사이의 중재자로, 한정된 자원을 여러 프로그램이 다툼 없이 나눠 쓰도록 관리합니다. 핵심 역할은 CPU 시간·메모리·입출력 장치·파일이라는 자원을 분배하는 것이고, 그래서 운영체제를 한마디로 자원 관리자라고 부릅니다. 이 관리의 가장 핵심부를 맡는 코드가 커널인데, 컴퓨터가 켜져 있는 내내 메모리에 상주하면서 CPU 분배와 메모리 관리, 장치 제어를 담당합니다."

💡 커널이 이렇게 중요한 일을 다 한다면, 아무 프로그램이나 커널 기능을 함부로 쓰게 두면 큰일나겠죠. 그래서 컴퓨터는 권한을 둘로 나눠둡니다. 다음 Step의 유저 모드와 커널 모드예요.


Step 2: "유저 모드 vs 커널 모드 — 함부로 못 건드리게"

만약 모든 프로그램이 하드웨어를 직접 주무를 수 있다면 어떻게 될까요? 내가 짠 작은 프로그램의 버그 하나가 디스크를 통째로 지우거나, 다른 프로그램이 쓰는 메모리를 덮어써서 컴퓨터 전체를 다운시킬 수 있어요. 실제로 옛날 운영체제에서는 이런 일이 흔했습니다.

그래서 현대 CPU와 운영체제는 실행 권한을 두 단계로 나눕니다.

  • 유저 모드(user mode) — 응용 프로그램이 도는 모드. 제한된 권한입니다. 자기에게 허락된 메모리만 쓸 수 있고, 하드웨어를 직접 제어할 수 없어요.
  • 커널 모드(kernel mode) — 커널이 도는 모드. 전권입니다. 모든 메모리에 접근하고, 하드웨어를 직접 제어하며, 위험한 특권 명령을 실행할 수 있어요.
텍스트
   ┌──────────────────┐
   │    User Space    │──── 응용 프로그램이 도는 공간 (권한 제한)
   ├──────────────────┤
   │   Kernel Space   │──── 커널이 도는 공간 (전권 — 하드웨어 직접 제어)
   └──────────────────┘

이걸 어떻게 구현할까요? CPU 안에 모드를 나타내는 비트 하나(모드 비트)를 둡니다. CPU는 명령을 실행하기 전에 이 비트를 보고 "지금 이 특권 명령을 실행해도 되나"를 판단해요. 유저 모드인데 디스크를 직접 건드리는 것 같은 특권 명령을 시도하면, CPU가 거부하고 예외를 일으켜 커널이 개입합니다.

비유하면 회사 보안 등급이에요. 일반 직원(유저 모드)은 자기 책상과 공용 구역만 드나들 수 있어요. 서버실이나 금고는 관리자 권한(커널 모드) 카드가 있어야 열리죠. 일반 직원이 서버실 문을 억지로 열려고 하면 경보(예외)가 울리고 보안팀(커널)이 출동합니다. 권한을 나눠두면, 직원 한 명의 실수가 회사 전체를 마비시키는 걸 막을 수 있어요.

🙋 학생 질문 — "모드를 나누면 매번 검사하느라 느려지지 않나요?"

합리적인 걱정인데, 모드 비트를 확인하는 건 CPU 하드웨어가 명령을 실행하면서 같이 처리해서 추가 시간이 거의 0이에요. 안전을 거의 공짜로 사는 셈이죠.

진짜 비용이 드는 건 모드를 전환할 때입니다. 유저 모드에서 커널 모드로 넘어갔다 돌아오려면 현재 상태를 저장하고 복원하는 절차가 필요하거든요. 그 전환이 일어나는 대표적인 순간이 바로 다음 Step의 시스템 콜이에요. 그래서 "검사 비용"이 아니라 "전환 비용"을 줄이는 게 성능의 관건이 됩니다.

🎯 면접에서는 이렇게 답하세요

"CPU는 실행 권한을 유저 모드와 커널 모드 두 단계로 나눕니다. 유저 모드는 응용 프로그램이 도는 제한된 권한 상태로, 자기에게 허락된 메모리만 쓸 수 있고 하드웨어를 직접 제어할 수 없습니다. 커널 모드는 운영체제 커널이 도는 전권 상태로, 모든 메모리와 하드웨어에 접근하고 특권 명령을 실행할 수 있습니다. 이렇게 나누는 이유는 안정성과 보안 때문이에요. 응용 프로그램의 버그나 악성 코드가 하드웨어를 직접 건드려 시스템 전체를 망가뜨리는 걸 막는 거죠. CPU 안의 모드 비트로 지금 어느 모드인지를 구분합니다."

💡 그런데 응용 프로그램도 파일을 저장하거나 화면에 글자를 찍으려면 결국 하드웨어를 써야 하잖아요. 유저 모드라 직접 못 만지는데 어떻게 할까요? 커널에게 "이것 좀 해줘" 하고 부탁합니다. 그 부탁 창구가 다음 Step의 시스템 콜이에요.


Step 3: "시스템 콜 — 커널에게 부탁하는 창구"

응용 프로그램이 파일을 읽고 싶어요. 그런데 유저 모드라 디스크를 직접 못 건드립니다. 방법은 하나, 커널에게 부탁하는 거예요. "디스크에서 이 파일 좀 읽어서 나한테 줘." 이 부탁을 넣는 공식 창구가 시스템 콜(system call)입니다.

시스템 콜이 일어나면 모드가 전환돼요. 흐름을 따라가 봅시다.

텍스트
   [유저 모드] 응용 프로그램 실행 중
        │
        │   "파일 좀 읽어줘"    read() 호출 (시스템 콜)
        ▼
   ── 모드 전환: User  Kernel ──
        │
        ▼
   [커널 모드] 커널이 디스크에 접근해 데이터를 읽는다
        │
        ▼
   ── 모드 전환: Kernel  User ──
        │
        ▼
   [유저 모드] 읽어온 데이터를 받아 다음 코드를 이어 실행

응용 프로그램이 유저 모드에서 시스템 콜을 부르면, CPU가 커널 모드로 전환되고 제어권이 커널로 넘어갑니다. 커널이 전권으로 실제 작업(디스크 접근)을 대신 해준 뒤, 결과를 들고 다시 유저 모드로 돌아와요. 그러면 응용 프로그램이 이어서 실행됩니다.

시스템 콜에는 파일 다루기(열기·읽기·쓰기·닫기), 프로세스 만들기, 네트워크로 보내고 받기, 메모리 할당 같은 것들이 있어요. 운영체제마다 수백 개씩 있습니다. 재미있는 건, 우리가 코드에서 부르는 "화면에 출력하기"나 "파일에 쓰기" 같은 편한 함수들도 속을 들여다보면 대부분 시스템 콜을 감싼 것이라는 점이에요. 우리는 편한 함수를 쓰지만, 그 밑바닥엔 커널에게 부탁하는 시스템 콜이 깔려 있습니다.

비유하면 은행 창구예요. 고객(응용 프로그램)은 금고(하드웨어)에 직접 못 들어갑니다. 창구 직원(시스템 콜)에게 "100만원 출금해주세요" 하고 요청하면, 직원이 금고 권한(커널 모드)으로 들어가 돈을 꺼내 고객에게 건네줘요. 고객은 금고가 어떻게 생겼는지 몰라도 되고, 은행은 금고를 안전하게 지킬 수 있죠.

🙋 학생 질문 — "시스템 콜이 그렇게 자주 일어나면 전환 비용이 쌓여서 느려지지 않나요?"

정확한 지적이에요. 시스템 콜은 공짜가 아닙니다. 모드를 전환하면서 현재 상태를 저장하고 커널로 갔다가 다시 돌아오는 오버헤드가 매번 들어요.

그래서 성능이 중요한 프로그램은 시스템 콜 횟수를 줄이는 쪽으로 짭니다. 예를 들어 데이터를 한 글자씩 천 번 쓰는 대신, 일단 메모리에 모아뒀다가(버퍼링) 한 번에 몰아서 쓰면 시스템 콜이 천 번에서 한 번으로 줄죠. "버퍼링으로 시스템 콜을 줄인다"는 최적화가 여기서 나옵니다. 시스템 콜이 비싸다는 걸 알면, 왜 입출력을 모아서 처리하는지가 이해돼요. (실전 성능 측정·튜닝은 linux나 성능 영역에서 더 깊이 다룹니다.)

🎯 면접에서는 이렇게 답하세요

"시스템 콜은 응용 프로그램이 운영체제 커널에게 특권이 필요한 작업을 요청하는 공식 창구입니다. 파일 읽기·쓰기, 프로세스 생성, 네트워크 통신처럼 하드웨어를 건드려야 하는 일은 유저 모드에서 직접 할 수 없기 때문에, 시스템 콜을 통해 커널에 부탁합니다. 시스템 콜이 호출되면 유저 모드에서 커널 모드로 전환되어 커널이 작업을 대신 수행하고, 끝나면 다시 유저 모드로 돌아옵니다. 우리가 쓰는 라이브러리 함수 상당수가 내부적으로 이 시스템 콜을 감싸고 있습니다."

💡 운영체제가 무엇이고(Step 1), 어떻게 보호받고(Step 2), 응용 프로그램과 어떻게 대화하는지(Step 3)까지 봤어요. 이제 운영체제가 다루는 주인공, 프로세스로 들어갑니다. 지난 시간 이름만 꺼냈던 그 프로세스의 정식 정의부터요.


Step 4: "프로세스란 — 실행 중인 프로그램"

지난 시간 마지막에 "실행 중인 프로그램 하나하나를 프로세스라 부른다"고 슬쩍 언급했죠. 이제 제대로 정의합니다. 핵심은 프로그램프로세스를 구분하는 거예요.

  • 프로그램(program) — 디스크에 저장된 정적인 파일입니다. 실행할 명령들이 적힌 "설계도"예요. 아직 돌지 않아요. 예: 설치만 해둔 카카오톡 앱 파일.
  • 프로세스(process) — 그 프로그램이 메모리에 올라와 실제로 실행되는 동적인 상태입니다. CPU를 받아 명령을 수행 중이고, 자기만의 메모리 공간과 상태를 가져요. 예: 지금 켜져서 돌아가는 카카오톡.

A-1에서 폰노이만 구조를 배울 때 썼던 요리책 비유를 다시 빌려볼게요. 프로그램은 요리책에 적힌 레시피예요. 글자로만 적혀 있고, 그 자체로는 아무 일도 안 일어나죠(정적). 프로세스는 그 레시피를 보고 실제로 요리하는 행위입니다. 재료를 꺼내고, 불을 켜고, 시간을 쓰며 진짜로 무언가가 만들어져요(동적).

이 비유가 중요한 결론 하나를 줍니다. 같은 레시피로 두 사람이 동시에 요리할 수 있죠? 마찬가지로, 하나의 프로그램에서 여러 개의 프로세스를 만들 수 있어요. 크롬을 여러 개 띄우거나 탭을 여러 개 열면, 같은 크롬 프로그램 파일에서 각자 독립된 메모리를 가진 여러 프로세스가 생깁니다. 그래서 탭 하나가 멈춰도 다른 탭은 멀쩡한 거예요.

프로세스는 자기만의 것을 몇 가지 가지고 있어요. 자기만의 메모리 공간(다음 Step에서 자세히), 프로그램 카운터(PC — A-2에서 본 "다음에 실행할 명령어 주소", 어디까지 실행했는지), 레지스터 값들, 그리고 현재 상태입니다.

🙋 학생 질문 — "같은 프로그램을 두 번 켜면 메모리에 똑같은 코드가 두 벌 올라가나요? 낭비 아니에요?"

똑똑한 질문이에요. 코드 영역은 실행 중에 바뀌지 않는 읽기 전용이라, 운영체제가 영리하게 한 벌만 메모리에 두고 두 프로세스가 함께 보도록 공유하기도 합니다. 똑같은 글자를 두 번 복사할 이유가 없으니까요.

하지만 데이터·힙·스택처럼 실행 중에 각자 바뀌는 부분은 따로 가져야 해요. 한 프로세스가 변수에 쓴 값이 다른 프로세스에 영향을 주면 안 되니까요. 그래서 두 프로세스는 "안 바뀌는 코드는 같이 보되, 각자의 작업 공간은 분리"된 상태가 됩니다. 이렇게 메모리를 똑똑하게 공유하고 분리하는 기법의 바탕인 가상 메모리는 B-3에서 깊이 다뤄요.

🎯 면접에서는 이렇게 답하세요

"프로그램은 디스크에 저장된 정적인 파일, 즉 실행할 명령이 적힌 설계도이고, 프로세스는 그 프로그램이 메모리에 올라와 실제로 실행되는 동적인 상태입니다. 비유하면 프로그램은 요리책의 레시피이고, 프로세스는 그 레시피로 실제 요리하는 행위예요. 그래서 하나의 프로그램으로 여러 개의 프로세스를 만들 수 있습니다. 크롬을 여러 개 띄우면 같은 프로그램에서 각자 독립된 메모리를 가진 여러 프로세스가 생기는 것처럼요. 프로세스는 자기만의 메모리 공간과 실행 상태(프로그램 카운터·레지스터 값)를 갖습니다."

💡 프로세스가 "자기만의 메모리 공간"을 갖는다고 했죠. 그 공간이 어떻게 생겼는지가 면접 최빈출 질문이에요. 코드·데이터·힙·스택 네 칸으로 나뉘는 프로세스 메모리 구조, 다음 Step에서 그림으로 봅니다.


Step 5: "프로세스의 메모리 구조 — 코드·데이터·힙·스택"

프로세스 하나가 메모리에 올라오면, 운영체제는 그 프로세스에게 자기만의 메모리 공간을 줍니다. 이 공간은 용도에 따라 네 칸으로 나뉘어요. 면접에서 "프로세스 메모리 구조를 설명해보세요"는 거의 반드시 나오는 질문이니, 이 그림을 통째로 머리에 넣어둡시다.

텍스트
   높은 주소 (high)
   ┌──────────────┐
   │    Stack     │──── 함수 호출 정보·지역 변수 (위  아래로 자람)
   ├──────────────┤
   │   (empty)    │──── 스택과 힙 사이의 빈 공간
   ├──────────────┤
   │     Heap     │──── 동적 할당 new/malloc (아래  위로 자람)
   ├──────────────┤
   │     Data     │──── 전역 변수·정적 변수
   ├──────────────┤
   │  Code (Text) │──── 실행할 기계어 명령 (읽기 전용)
   └──────────────┘
   낮은 주소 (low)

네 영역을 하나씩 볼게요.

  • 코드(Code, 또는 Text) — 실행할 기계어 명령이 담깁니다. 우리가 짠 코드가 컴파일된 결과예요. 실행 중에 바뀌지 않는 읽기 전용 영역입니다.
  • 데이터(Data) — 전역 변수와 정적 변수가 담깁니다. 프로그램이 시작될 때부터 끝날 때까지 쭉 살아있는 값들이에요.
  • (Heap) — 실행 중에 동적으로 할당하는 공간입니다. newmalloc 같은 걸로 필요할 때 받아 쓰고, 크기가 가변적이에요. 낮은 주소에서 높은 주소 방향(위로) 자랍니다.
  • 스택(Stack) — 함수 호출 정보와 지역 변수가 담깁니다. 함수를 부르면 쌓이고(push), 함수가 끝나면 사라져요(pop). 높은 주소에서 낮은 주소 방향(아래로) 자랍니다.

그림에서 가장 중요한 통찰은 힙과 스택이 서로를 향해 자란다는 점이에요. 힙은 아래에서 위로, 스택은 위에서 아래로, 가운데 빈 공간을 두고 마주 보며 자랍니다. 이렇게 하면 한쪽이 많이 필요할 때 빈 공간을 융통성 있게 나눠 쓸 수 있죠. 만약 둘이 자라다 만나서 충돌하면? 그게 바로 그 유명한 스택 오버플로(stack overflow)예요. 끝나지 않는 무한 재귀 같은 게 스택을 끝없이 쌓아 올려 충돌을 일으킵니다.

말로만 들으면 헷갈리니, 짧은 의사코드로 어떤 변수가 어디에 잡히는지 봅시다. (특정 언어가 아니라, 여러분이 아는 어떤 언어로도 읽히게 적었어요.)

텍스트
   globalCount = 0                  // 전역 변수     Data 영역

   function greet(name) {           // greet 코드    Code 영역
       message = "Hi, " + name      // 지역 변수     Stack
       user = new User(name)        // new로 만든 객체  Heap
       return user
   }

greet를 호출하면 name·message 같은 지역 변수는 스택에 잠깐 쌓였다가 함수가 끝나면 사라집니다. 반면 new User(...)로 만든 객체는 에 자리 잡아, 함수가 끝나도 (그 객체를 가리키는 참조가 남아있는 한) 계속 살아있어요. globalCount 같은 전역 변수는 데이터 영역에서 프로그램이 도는 내내 유지되고요. 같은 변수라도 어디에 잡히느냐가 수명을 가른다 — 이게 핵심입니다.

🙋 학생 질문 — "스택과 힙, 왜 굳이 나눠요? 다 같은 메모리인데."

수명과 관리 방식이 다르기 때문이에요.

스택은 함수 호출과 함께 자동으로 쌓이고, 함수가 끝나면 자동으로 정리됩니다. 누가 치울지 고민할 필요가 없어 빠르고 간단해요. 대신 크기가 미리 정해져 작고, 함수가 끝나면 사라져서 오래 둘 수 없습니다.

은 실행 중에 원하는 만큼 받아서, 원하는 동안 둘 수 있어 유연합니다. 대신 다 쓴 다음 반납을 챙겨야 하고(직접 하거나, 언어에 따라 가비지 컬렉터가 대신 정리해줘요), 스택보다 상대적으로 느려요.

그래서 기준은 이거예요. "함수 안에서 잠깐 쓸 값은 스택, 함수 밖까지 살아남아야 할 데이터는 ." 이 구분이 둘을 나누는 핵심입니다. 스택과 힙의 더 깊은 차이, 그리고 이 공간들이 실제로는 가짜 주소(가상 메모리) 위에 올라간다는 이야기는 다음다음 시간(B-3)에서 파고듭니다.

🎯 면접에서는 이렇게 답하세요

"프로세스의 메모리는 크게 네 영역으로 나뉩니다. 코드 영역에는 실행할 기계어 명령이, 데이터 영역에는 전역 변수와 정적 변수가 들어갑니다. 은 실행 중에 동적으로 할당하는 공간으로 낮은 주소에서 높은 주소로 자라고, 스택은 함수 호출 정보와 지역 변수가 쌓이는 공간으로 높은 주소에서 낮은 주소로 자랍니다. 힙과 스택이 서로 마주 보고 자라다 충돌하면 스택 오버플로가 발생합니다. 핵심은 변수의 수명이에요. 함수 안에서 잠깐 쓰는 지역 변수는 스택에, 함수가 끝나도 살아남아야 하는 데이터는 힙에 둡니다."

💡 프로세스가 메모리를 어떻게 쓰는지 봤어요. 그런데 운영체제는 이런 프로세스 수십 개를 동시에 다뤄야 하잖아요. 각 프로세스가 "지금 어느 상태고, 어디까지 실행했는지"를 어딘가에 적어둬야겠죠. 그 수첩이 PCB이고, 프로세스가 오가는 상태들이 상태 전이도입니다. 다음 Step.


Step 6: "PCB와 프로세스 상태 전이 — OS가 프로세스를 관리하는 법"

운영체제가 프로세스 수십 개를 번갈아 돌리려면, 각 프로세스에 대해 "이 녀석은 지금 어느 상태고, 어디까지 실행했고, 레지스터 값은 뭐였지"를 전부 기록해둬야 해요. 이 기록 수첩이 PCB(Process Control Block, 프로세스 제어 블록)입니다.

PCB에는 이런 것들이 담겨요. PID(프로세스 번호), 프로세스 상태, 프로그램 카운터(PC — 어디까지 실행했나), 레지스터 값들, 메모리 정보, 열어둔 파일 목록 등. 운영체제는 프로세스마다 PCB를 하나씩 만들어 관리합니다. 프로세스의 "신분증이자 저장함"인 셈이죠.

지난 시간 인터럽트에서 "현재 상태(PC와 레지스터)를 저장했다 복원한다"고 했던 거 기억나세요? 운영체제가 프로세스 A를 잠깐 멈추고 B로 갈아 끼울 때, A의 PC·레지스터 값을 A의 PCB에 저장하고, B의 PCB에서 값을 꺼내 복원해 CPU에 올립니다. 이 갈아 끼우기를 컨텍스트 스위칭(context switching)이라고 해요. PCB가 바로 그 "문맥(context)을 저장하는 수첩"입니다. (컨텍스트 스위칭이 왜 비용이 큰지, 스케줄러가 누구를 먼저 실행할지 정하는 법은 다음 시간 B-2에서 깊이 다뤄요. 오늘은 "PCB에 저장·복원한다"까지만.)

그럼 프로세스는 어떤 상태들을 오갈까요? 프로세스는 평생 한 상태에 머무는 게 아니라 다섯 상태를 오갑니다.

텍스트
   ① New (생성)        프로세스가 막 만들어져 메모리에 올라감
        │
        ▼
   ② Ready (준비)       CPU만 받으면 바로 실행 — 줄 서서 대기
        │   스케줄러가 CPU를 할당(디스패치)
        ▼
   ③ Running (실행)     CPU를 쥐고 명령을 실행하는 중
        │
        ├─ 시간 다 씀(타임 슬라이스 소진) ─▶ ② Ready 로 돌아감
        ├─ I/O·이벤트를 기다려야 함 ──────▶ ④ Waiting 으로
        └─ 일이 다 끝남 ────────────────▶ ⑤ Terminated 로
        ▼
   ④ Waiting (대기)     I/O가 끝나길 기다림 — 끝나면 ② Ready 로 복귀
        │
        ▼
   ⑤ Terminated (종료)  실행 완료, 운영체제가 자원을 회수

흐름을 읽어볼게요. 프로세스가 막 생기면 New, 메모리에 올라가 실행 준비를 마치면 Ready 줄에 섭니다. 스케줄러가 CPU를 배정(디스패치)하면 Running으로 명령을 수행해요. 실행 중에 세 갈래로 빠질 수 있습니다. 할당된 시간을 다 쓰면(타이머 인터럽트!) 다시 Ready로, 파일을 읽는 등 I/O를 기다려야 하면 Waiting으로, 일이 다 끝나면 Terminated로 갑니다. Waiting에 있던 프로세스는 기다리던 I/O가 끝나면 다시 Ready 줄로 돌아와 차례를 기다려요.

여기서 오늘의 핵심 통찰이 나옵니다. CPU는 (코어 수만큼) 하나뿐인데 Ready 줄에 선 프로세스는 여럿이에요. 그래서 Running 상태인 프로세스는 한 번에 (코어 수만큼) 하나뿐입니다. 나머지는 Ready 줄에서 대기하죠. 운영체제가 Running과 Ready를 부지런히 갈아 끼우니까(컨텍스트 스위칭) 사람 눈에는 수십 개가 동시에 도는 것처럼 보이는 거예요. 지난 시간에 남긴 약속 — "어떻게 동시에 도는 것처럼 보이는가" — 의 답이 여기서 완성됩니다.

🙋 학생 질문 — "Waiting에서 I/O가 끝났으면 바로 Running으로 가면 되지, 왜 굳이 Ready를 다시 거쳐요?"

핵심은 "CPU가 하나뿐"이라는 데 있어요.

I/O가 끝난 그 순간, CPU는 보통 다른 프로세스가 쓰고 있습니다. 그러니 "나 이제 준비 됐어!" 하고 바로 실행에 들어갈 수가 없어요. 누군가 쓰던 CPU를 그 자리에서 빼앗을 순 없으니까요.

그래서 일단 "실행 준비 완료" 줄(Ready)에 다시 서서 차례를 기다립니다. CPU가 하나뿐이라, "준비된 것"(Ready)과 "실제 실행 중인 것"(Running)을 구분하는 완충 단계가 꼭 필요한 거예요. 그럼 Ready 줄에 선 여러 프로세스 중 누가 먼저 CPU를 받느냐 — 그 순서를 정하는 게 바로 다음 시간에 배울 CPU 스케줄링입니다.

🎯 면접에서는 이렇게 답하세요

"프로세스는 보통 다섯 상태를 오갑니다. New(생성)에서 만들어져 메모리에 올라가고, Ready(준비)에서 CPU를 받기를 기다리며 줄을 섭니다. 스케줄러가 CPU를 배정하면 Running(실행) 상태로 명령을 수행하고, I/O가 필요하면 Waiting(대기)으로 빠졌다가 끝나면 다시 Ready로 돌아옵니다. 실행이 끝나면 Terminated(종료)됩니다. 핵심은 CPU가 하나뿐이라 Running은 한 번에 하나(코어 수만큼)이고, 운영체제가 각 프로세스의 상태와 문맥을 PCB에 저장해두고 Running과 Ready를 빠르게 갈아 끼우는데, 이 교체를 컨텍스트 스위칭이라고 합니다."

💡 프로세스가 어떻게 관리되고 상태를 오가는지 봤어요. 그런데 이 프로세스들은 애초에 어떻게 "생겨날까요"? 운영체제가 무에서 뚝딱 만들어낼까요? 아닙니다. 부모가 자식을 낳듯, 기존 프로세스가 새 프로세스를 만들어요. 다음 Step의 fork입니다.


Step 7: "프로세스 생성 — fork와 프로세스 계층"

새 프로세스는 어떻게 생길까요? 원리는 의외로 단순해요. 기존 프로세스가 자기를 복제해서 자식을 만든다. 이 복제 동작을 fork(포크)라고 부릅니다.

부모 프로세스가 fork를 호출하면, 운영체제가 부모와 거의 똑같은 복사본을 만들어 자식 프로세스로 띄웁니다. 자식은 부모의 메모리 내용(코드·데이터·힙·스택)을 복제해 가져요. 단, PID(프로세스 번호)는 다릅니다. 둘은 이제 별개의 프로세스예요.

복제만으로 끝나면 부모랑 똑같은 일만 하겠죠. 그래서 보통 자식은 복제 직후 exec라는 동작으로 다른 프로그램으로 갈아탑니다. fork로 일단 복사본을 만든 뒤, 그 자식이 "사실 난 다른 프로그램이 될 거야" 하며 자기 내용을 새 프로그램으로 덮어쓰는 거예요. 터미널(셸)이 명령어를 실행하는 방식이 정확히 이렇습니다. fork로 자식을 만들고 → exec로 그 자식을 여러분이 입력한 프로그램으로 변신시켜요.

비유하면 세포 분열이나 가계도 같아요. 하나의 세포가 둘로 갈라지듯, 한 프로세스가 자식을 낳습니다. 그래서 모든 프로세스는 부모를 거슬러 올라가면, 부팅 때 가장 먼저 뜬 최초 프로세스 하나로 모여요. 이게 프로세스 트리입니다.

텍스트
   systemd (PID 1)                  부팅 직후 운영체제가 띄우는 최초 프로세스
     │
     ├─ bash (PID 2310)             터미널 셸
     │    └─ python3 (PID 2480)     셸이 fork로 만든 자식 프로세스
     │
     └─ chrome (PID 3001)           브라우저 (부모 프로세스)
          ├─ chrome (PID 3002)      탭 하나 = 자식 프로세스
          └─ chrome (PID 3003)      또 다른 탭

부모와 자식은 부모가 자식이 끝나길 기다리거나(wait), 서로 따로 돌 수도 있어요. 만약 자식이 끝났는데 부모가 그 결과를 거둬가지 않으면, 자식은 사라지지 못하고 좀비 프로세스로 남기도 합니다(이런 프로세스를 셸에서 어떻게 찾고 정리하는지는 linux에서 다뤄요).

🙋 학생 질문 — "부모를 통째로 복제하면 메모리가 두 배로 드는 거 아니에요? 비효율적인데요."

맞는 걱정이에요. 그래서 현대 운영체제는 복사를 최대한 미루는 영리한 기법을 씁니다.

fork 직후에는 부모와 자식이 같은 메모리를 그냥 공유해요. 그러다 둘 중 하나가 어떤 부분을 바꾸려고 하는 바로 그 순간에만, 그 부분을 복사합니다. 이걸 "쓸 때 복사"(Copy-on-Write, CoW)라고 해요. 안 바뀌는 부분은 계속 공유하니까, 통째로 복사하는 것보다 fork가 훨씬 가벼워지죠.

덕분에 fork는 "복제"라는 말이 무색할 만큼 빠릅니다. 이 메모리 공유·복사의 바탕이 되는 가상 메모리는 B-3에서 자세히 다뤄요. 오늘은 "fork = 부모를 복제해 자식을 만든다"까지만 잡고 가면 충분합니다.

🎯 면접에서는 이렇게 답하세요

"새 프로세스는 기존 프로세스가 자신을 복제해서 만듭니다. 이 복제 동작이 fork인데, 부모 프로세스가 fork를 호출하면 운영체제가 부모와 거의 똑같은 자식 프로세스를 만들고, 둘은 PID만 다른 별개의 프로세스가 됩니다. 보통 fork로 복사본을 만든 뒤 exec로 자식을 다른 프로그램으로 덮어씌우는 식으로 새 프로그램을 실행합니다. 모든 프로세스는 이렇게 부모-자식으로 이어져, 부팅 때 뜬 최초 프로세스를 뿌리로 하는 트리 구조를 이룹니다. 참고로 fork는 메모리를 통째로 복사하지 않고, Copy-on-Write로 공유하다 필요할 때만 복사해 효율을 높입니다."

💡 이렇게 부모-자식으로 갈라진 프로세스들은 각자 독립된 메모리를 가져요(Step 4·5). 서로의 메모리를 직접 들여다보지 못합니다. 그런데 협력하려면 데이터를 주고받아야 할 때가 있죠. 격리된 프로세스끼리 어떻게 대화할까요? 마지막 Step, IPC입니다.


Step 8: "IPC — 격리된 프로세스끼리 대화하기"

지금까지 계속 강조한 게 하나 있죠. 프로세스는 각자 독립된 메모리를 가진다. 한 프로세스가 다른 프로세스의 메모리를 함부로 들여다보지 못해요(Step 2에서 본 보호의 핵심이죠). 안정성에는 더없이 좋은데, 두 프로세스가 협력해야 할 땐 곤란합니다. 서로 메모리를 못 보는데 어떻게 데이터를 주고받죠?

그래서 운영체제가 프로세스끼리 대화하는 공식 통로를 마련해줍니다. 이게 IPC(Inter-Process Communication, 프로세스 간 통신)예요.

텍스트
   ┌──────────┐             ┌──────────┐
   │ Process  │             │ Process  │
   │    A     │             │    B     │
   └────┬─────┘             └────┬─────┘
        │                        │
        └───────────┬────────────┘
                    ▼
       IPC: 공유 메모리 · 파이프 · 메시지 패싱
       (OS가 마련해 준 통로로만 데이터를 주고받는다)

대표적인 IPC 방법 세 가지를 비교해볼게요.

방법 동작 비유
파이프(Pipe) 한 프로세스의 출력을 다른 프로세스의 입력으로 흘려보냄 (한 방향) 한 사람이 옆 사람에게 쪽지를 한 방향으로 건넴
공유 메모리(Shared Memory) 두 프로세스가 같은 메모리 영역을 공유해 직접 읽고 씀 (가장 빠름) 공용 화이트보드에 같이 적고 함께 봄
메시지 패싱(Message Passing) 운영체제를 거쳐 메시지를 주고받음 (안전하지만 느슨) 우체국을 통해 편지를 주고받음

가장 친숙한 건 파이프예요. 터미널에서 ls | grep txt 할 때 그 | 기호가 바로 파이프입니다. ls의 출력이 grep의 입력으로 흘러 들어가죠. 공유 메모리가 가장 빠른 이유는, 운영체제를 매번 거치지 않고 같은 메모리를 직접 읽고 쓰기 때문이에요. 대신 둘이 동시에 같은 칸을 건드리면 값이 꼬이는 충돌이 생길 수 있어서, 순서를 맞추는 장치가 필요합니다(이 "충돌을 막는 동기화" 이야기가 B-4의 주제예요).

🙋 학생 질문 — "그냥 파일에 써두고 다른 프로세스가 읽게 하면 안 되나요? 그것도 통신이잖아요."

맞아요. 파일을 통한 통신도 넓게 보면 IPC의 일종이에요. 실제로 간단한 경우엔 그렇게도 씁니다.

다만 파일은 디스크를 거쳐서 느리고, "상대가 언제 다 썼는지, 내가 언제 읽어도 되는지" 그 타이밍을 맞추기가 번거로워요. 한쪽이 아직 쓰는 중인데 다른 쪽이 읽으면 반쪽짜리 데이터를 가져가겠죠.

그래서 운영체제는 빠르고 타이밍 조율까지 되는 전용 수단(파이프·공유 메모리·메시지 패싱)을 따로 제공합니다. 도구마다 속도·안전·편의의 트레이드오프가 달라서, 상황에 맞게 골라 써요. "가장 빠른 게 항상 정답"이 아니라, 안전이 더 중요하면 메시지 패싱을, 속도가 급하면 공유 메모리를 택하는 식이죠.

🎯 면접에서는 이렇게 답하세요

"프로세스는 각자 독립된 메모리를 가져 서로 직접 접근할 수 없기 때문에, 협력하려면 운영체제가 제공하는 IPC 통로가 필요합니다. 대표적으로 파이프는 한 프로세스의 출력을 다른 프로세스의 입력으로 흘려보내는 단방향 통신이고, 셸의 파이프 기호가 그 예입니다. 공유 메모리는 두 프로세스가 같은 메모리 영역을 공유해 직접 읽고 쓰는 방식으로 가장 빠르지만, 동시 접근 시 충돌을 막는 동기화가 필요합니다. 메시지 패싱은 운영체제를 거쳐 메시지를 주고받아 안전하지만 상대적으로 느립니다. 속도와 안전성의 트레이드오프에 따라 골라 씁니다."

💡 공유 메모리에서 "둘이 동시에 같은 칸을 건드리면 충돌"이라고 했죠. 이 문제가 나중에 B-4에서 배울 경쟁 상태·동기화로 이어집니다. 오늘 운영체제와 프로세스를 한 바퀴 돌았으니, 정리하고 다음 시간 다리를 놓을게요.


마무리

오늘 배운 핵심 세 가지

🎯 하나, 운영체제는 하드웨어와 응용 프로그램 사이에서 한정된 자원(CPU·메모리·입출력)을 나눠주는 자원 관리자이고, 그 핵심부가 커널입니다. 응용 프로그램은 유저 모드(제한)에서 돌고, 하드웨어가 필요하면 시스템 콜커널 모드(전권)에 부탁해요. 권한을 둘로 나눈 건 안정성과 보안 때문입니다.

🎯 , 프로세스는 디스크의 정적인 프로그램이 메모리에 올라와 실제로 도는 동적인 상태입니다. 프로세스의 메모리는 코드·데이터·힙·스택 네 칸으로 나뉘고, 함수 안에서 잠깐 쓰는 값은 스택에, 오래 살아남을 데이터는 힙에 잡혀요.

🎯 , 운영체제는 각 프로세스의 문맥을 PCB에 저장해두고, New → Ready → Running → Waiting → Terminated 상태를 갈아 끼우며(컨텍스트 스위칭) 여럿을 동시에 도는 것처럼 보이게 합니다. 프로세스는 fork로 부모가 자식을 낳아 트리를 이루고, 격리된 프로세스끼리는 IPC(파이프·공유 메모리·메시지 패싱)로 대화합니다.

다음 시간 예고

오늘 우리는 운영체제가 프로세스 여러 개를 번갈아 돌려 동시 실행처럼 보이게 한다는 걸 봤어요. 그런데 프로세스 하나를 통째로 갈아 끼우는 컨텍스트 스위칭은 생각보다 무거운 일입니다. 메모리 공간까지 통째로 바꿔야 하니까요.

그래서 사람들은 더 가벼운 실행 단위를 고안했어요. 하나의 프로세스 안에서 메모리를 공유하며 여러 갈래로 동시에 흐르는 실행의 흐름 — 이걸 스레드(thread)라고 합니다. 다음 시간(B-2)에는 프로세스와 스레드가 어떻게 다른지(면접 최단골!), 컨텍스트 스위칭이 왜 그렇게 비용이 큰지, 그리고 운영체제가 Ready 줄에 선 프로세스들에게 CPU를 어떤 순서로 나눠주는지 — CPU 스케줄링(라운드 로빈 등)을 봅니다. 오늘 본 상태 전이도가 거기서 다시 주인공으로 등장해요!


과제

[기초] 프로세스 메모리 구조 직접 그리고 변수 배치하기

연필과 종이만 있으면 됩니다.

  1. 오늘 배운 프로세스 메모리 네 칸(코드·데이터·힙·스택)을 보지 않고 직접 세로로 그려보세요. 각 칸 옆에 무엇이 들어가는지 한 줄씩 적습니다.
  2. 힙과 스택이 자라는 방향을 화살표로 표시하고, 둘이 충돌하면 무슨 일이 생기는지(힌트: 스택 오버플로) 한 줄로 적어보세요.
  3. 아래 짧은 코드에서 각 변수가 어느 영역에 잡히는지 표시해보세요.
    텍스트
    total = 100                  // (가)
    function add(a, b) {
        sum = a + b              // (나)
        result = new Result(sum) // (다)
        return result
    }
    
    (가)·(나)·(다)가 각각 데이터·스택·힙 중 어디인지 짝지어보세요.

[응용] 내 컴퓨터의 프로세스 들여다보기

자기 컴퓨터에서 실제로 도는 프로세스를 관찰하는 과제입니다. 설치할 건 없어요.

  1. 작업 관리자(윈도우) 또는 활성 상태 보기(맥)를 열어 지금 도는 프로세스 목록과 개수를 확인해보세요. 생각보다 많을 거예요.
  2. 같은 프로그램(예: 크롬·엣지 같은 브라우저)이 여러 개의 프로세스로 떠 있는지 찾아보고, 왜 하나가 아니라 여러 개인지 오늘 배운 내용으로 한 줄 설명해보세요.
  3. 프로세스마다 메모리 사용량이 다른 걸 확인하고, 그 이유를 오늘 배운 "프로세스는 각자 독립된 메모리 공간을 가진다"로 한 문단 설명해보세요.

[심화] 유저 모드 → 커널 모드 → 복귀의 흐름 따라가기

조사하고 정리하는 과제입니다.

  1. "파일에 한 줄을 쓰는" 동작이 일어날 때, 유저 모드 → 시스템 콜 → 커널 모드 → 다시 유저 모드로 돌아오는 흐름을 순서대로 글로 적어보세요.
  2. 시스템 콜이 자주 일어나면 왜 프로그램이 느려질 수 있는지, 모드 전환 비용의 관점에서 설명해보세요.
  3. (선택) "버퍼링으로 시스템 콜 횟수를 줄인다"는 말이 무슨 뜻인지, 데이터를 한 글자씩 천 번 쓰는 경우와 모아서 한 번에 쓰는 경우를 비교해 자기 말로 정리해보세요.

생각해볼 주제

1. 운영체제는 왜 권한을 둘로만 나눴을까?

오늘 유저 모드와 커널 모드, 딱 두 단계를 봤습니다. 그런데 회사 조직처럼 직원·팀장·부장·사장으로 더 잘게 나누면 더 안전하지 않을까요? 실제로 일부 CPU는 권한을 네 단계(링 0~3)까지 제공하지만, 대부분의 운영체제는 그중 두 단계만 골라 씁니다. 권한 단계를 늘릴 때 얻는 것(더 세밀한 보안)과 잃는 것(전환 비용·설계 복잡도)을 견줘보면서, 왜 현실의 운영체제가 "둘이면 충분하다"고 판단했을지 생각해보세요.

2. 프로세스를 격리하는 건 좋은 걸까, 나쁜 걸까?

프로세스는 각자 독립된 메모리를 가져서, 한 프로세스가 죽어도 다른 프로세스는 멀쩡합니다(안정성이 올라가죠). 하지만 그 대가로, 서로 협력하려면 IPC라는 번거로운 통로를 거쳐야 합니다(통신 비용이 듭니다). 크롬이 탭마다 프로세스를 따로 두는 선택이 어떤 득과 실을 가져오는지 — 한 탭이 멈춰도 브라우저 전체가 안 죽는 안정성과, 탭마다 메모리를 따로 먹는 비용 사이에서 — 안정성과 효율의 트레이드오프로 정리해보세요.

3. "동시에 도는 것처럼 보이는 것"과 "진짜 동시에 도는 것"

코어가 하나뿐인 컴퓨터에서도 프로그램 수십 개가 동시에 도는 것처럼 보입니다. 운영체제가 빠르게 번갈아 실행하기 때문이죠. 그런데 코어가 여러 개라면 진짜로 여러 프로그램이 같은 순간에 돌 수 있어요. 사람 눈에는 둘 다 "동시에 도는 것"으로 똑같아 보이지만, 그 속은 본질적으로 다릅니다. "번갈아 빠르게"와 "진짜 한꺼번에"가 어떻게 다른지, 그리고 둘을 구분하는 게 왜 중요한지 생각해보세요. (이 구분에 붙는 정확한 이름은 다음 시간에 만납니다.)

✅ 예시 답안정답 보기

과제 예시답안

🎯 [과제 1 예시답안] 프로세스 메모리 구조 직접 그리고 변수 배치하기

채점 포인트

항목 배점 기준
4영역 순서·내용 35% 코드·데이터·힙·스택 네 칸을 그리고 각각 무엇이 들어가는지 적었는가
자라는 방향 + 충돌 30% 힙(위로)·스택(아래로)이 마주 보고 자람을 화살표로 표시하고, 충돌=스택 오버플로를 짚었는가
변수 배치 35% (가)=데이터, (나)=스택, (다)=힙으로 올바로 짝지었는가

풀이 예시

네 칸을 세로로 그리고 각 칸이 무엇을 담는지 채우면 이렇게 됩니다.

텍스트
   높은 주소 (high)
   ┌──────────────┐
   │    Stack     │──── 지역 변수·함수 호출 정보 (위  아래로 자람)
   ├──────────────┤
   │   (empty)    │──── 빈 공간 (힙·스택이 서로를 향해 자람)
   ├──────────────┤
   │     Heap     │──── 동적 할당 (아래  위로 자람)
   ├──────────────┤
   │     Data     │──── 전역 변수·정적 변수
   ├──────────────┤
   │  Code (Text) │──── 실행할 기계어 명령 (읽기 전용)
   └──────────────┘
   낮은 주소 (low)

힙과 스택이 마주 보고 자라다 만나서 충돌하면 스택 오버플로가 납니다(무한 재귀가 대표적 원인).

변수 배치 정답:

텍스트
   total = 100                  // (가)  Data 영역  (전역 변수)
   function add(a, b) {
       sum = a + b              // (나)  Stack      (지역 변수)
       result = new Result(sum) // (다)  Heap       (new로 만든 객체)
       return result
   }
  • (가) total — 함수 밖 전역 변수라 프로그램 내내 살아있어야 하니 데이터 영역.
  • (나) sum — 함수 안에서 잠깐 쓰는 지역 변수라 스택. add가 끝나면 사라집니다.
  • (다) result(가 가리키는 객체)new로 동적 할당했으니 . 함수가 끝나도 참조가 남는 동안 살아있어요.

💡 튜터의 포인트

이 과제의 핵심은 "변수의 수명이 곧 메모리 위치를 정한다"를 손으로 한 번 짚어보는 거예요. (나)와 (다)가 헷갈릴 수 있는데, 기준은 단순합니다. "함수가 끝나면 사라져도 되는 값"은 스택, "함수가 끝나도 살아남아야 하는 값"은 힙. 이 한 장을 그려두면, 면접에서 "프로세스 메모리 구조 설명해보세요"가 나왔을 때 그림이 자동으로 떠오릅니다. 운영체제 면접의 3대 단골 중 하나라 꼭 손에 익혀두세요.


🎯 [과제 2 예시답안] 내 컴퓨터의 프로세스 들여다보기

채점 포인트

항목 배점 기준
프로세스 목록·개수 확인 30% 실제로 도는 프로세스 목록과 개수를 확인했는가
같은 프로그램 여러 프로세스 35% 브라우저 등이 여러 프로세스로 뜬 걸 찾고 왜 그런지 설명했는가
메모리 사용량 설명 35% 프로세스마다 메모리가 다른 이유를 "독립된 메모리 공간"으로 설명했는가

풀이 예시

확인 방법(설치 불요):

  • 윈도우: 작업 관리자(Ctrl+Shift+Esc) → 프로세스 탭. 도는 프로세스와 메모리 사용량이 보입니다.
  • 맥: 활성 상태 보기(응용 프로그램 → 유틸리티) → 메모리 탭.

관찰 예시:

텍스트
   현재 도는 프로세스 : 약 200개 (생각보다 훨씬 많음)
   chrome               1850 MB
   chrome               420 MB     같은 크롬인데 여러 개!
   chrome               310 MB
   Code (편집기)         980 MB
   ...

"왜 크롬이 여러 개인가" 설명 예시:

크롬은 탭이나 확장 프로그램마다 별도의 프로세스를 띄웁니다. 그래서 작업 관리자에 같은 이름이 여러 개 보여요. 이렇게 나누면 탭 하나가 멈추거나 죽어도 다른 탭과 브라우저 전체는 멀쩡합니다. 프로세스가 각자 독립된 메모리를 가져 서로 영향을 주지 않기 때문이에요.

"메모리 사용량이 제각각인 이유" 설명 예시:

프로세스는 각자 자기만의 메모리 공간(코드·데이터·힙·스택)을 갖습니다. 무거운 페이지를 띄운 탭은 힙에 많은 데이터를 쌓아 메모리를 많이 쓰고, 가벼운 탭은 적게 씁니다. 같은 프로그램에서 나온 프로세스라도 무슨 일을 하느냐에 따라 차지하는 메모리가 달라지는 거예요.

💡 튜터의 포인트

교과서 속 "프로세스는 독립된 메모리를 가진다"가 내 컴퓨터 안에 진짜로 있다는 걸 눈으로 확인하는 게 핵심이에요. 특히 같은 크롬이 여러 프로세스로 뜬 걸 보면, Step 4에서 배운 "하나의 프로그램 → 여러 프로세스"가 한 번에 와닿습니다. 참고로 프로세스를 셸에서 다루는 명령(ps·top·kill)은 linux 과목에서 실전으로 배워요 — 여기서는 "왜 이렇게 보이는지" 원리를 아는 데까지면 충분합니다.


🎯 [과제 3 예시답안] 유저 모드 → 커널 모드 → 복귀의 흐름 따라가기

채점 포인트

항목 배점 기준
흐름 순서 40% 유저 모드 → 시스템 콜 → 커널 모드 → 복귀 순서를 올바로 적었는가
모드 전환 비용 35% 시스템 콜이 잦으면 느려지는 이유를 전환 비용으로 설명했는가
버퍼링 이해 25% (선택) 시스템 콜을 모아서 줄인다는 개념을 자기 말로 정리했는가

풀이 예시

1. "파일에 한 줄 쓰기"의 흐름

① 응용 프로그램이 유저 모드에서 "이 줄을 파일에 써줘" 하고 쓰기 시스템 콜을 호출합니다. ② CPU가 커널 모드로 전환되고 제어권이 커널로 넘어갑니다. ③ 커널이 전권으로 디스크에 접근해 실제로 데이터를 씁니다. ④ 작업이 끝나면 다시 유저 모드로 복귀하고, 응용 프로그램이 그 다음 코드를 이어서 실행합니다.

2. 시스템 콜이 잦으면 왜 느려지나

시스템 콜은 호출될 때마다 유저 모드에서 커널 모드로 전환하고, 현재 상태를 저장했다가 끝나면 복원해 돌아오는 절차가 필요합니다. 이 전환 자체에 시간이 들어요. 그래서 작은 작업을 시스템 콜로 수없이 많이 호출하면, 정작 일보다 전환 비용이 더 커져 프로그램이 느려집니다.

3. 버퍼링이란

데이터를 한 글자씩 천 번 쓰면 시스템 콜이 천 번 일어나 전환 비용이 천 번 듭니다. 대신 천 글자를 일단 메모리(버퍼)에 모아뒀다가 한 번에 몰아서 쓰면 시스템 콜이 단 한 번으로 줄어요. 이렇게 시스템 콜 횟수를 줄이는 기법이 버퍼링입니다. "입출력은 모아서 처리하라"는 조언이 여기서 나옵니다.

💡 튜터의 포인트

이 과제는 Step 2·3에서 따로 배운 유저/커널 모드시스템 콜을 하나의 흐름으로 꿰는 연습이에요. "왜 입출력은 비싸다고 하는가"의 답이 여기 있습니다. 모드 전환 비용을 이해하면, 나중에 "파일을 한 줄씩 읽지 말고 한꺼번에 읽어라" 같은 성능 조언이 왜 나오는지 원리로 납득할 수 있어요. 면접에서 "시스템 콜이 비싼 이유"를 물으면, 바로 이 모드 전환 비용을 짚으면 됩니다.


생각해볼 주제 예시답안

🤔 [생각해볼 주제 1 예시답안] 운영체제는 왜 권한을 둘로만 나눴을까?

[문제 상황 요약]

운영체제는 유저 모드와 커널 모드 딱 두 단계로 권한을 나눕니다. 회사 조직처럼 더 잘게(직원·팀장·부장·사장) 나누면 더 안전할 것 같은데, 실제로 일부 CPU가 네 단계(링)를 제공해도 대부분의 운영체제는 두 단계만 씁니다. 왜일까요?

[튜터의 가이드 및 해설]

이건 "보안은 무조건 세밀할수록 좋은가"를 묻는 질문이에요. 답은 얻는 것과 잃는 것의 트레이드오프에 있습니다.

단계를 늘리면 얻는 것 — 권한을 잘게 나누면, 중간 권한이 필요한 코드(예: 장치 드라이버)에 딱 그만큼의 권한만 줄 수 있습니다. 문제가 생겨도 영향 범위가 그 단계에 갇혀 더 안전해 보이죠.

단계를 늘리면 잃는 것 — 그런데 권한 단계를 오갈 때마다 모드 전환 비용이 듭니다(과제 3에서 본 그 비용이에요). 단계가 많아질수록 전환이 잦아지고 설계도 복잡해집니다. 게다가 "이 작업은 어느 단계에 둬야 하나"를 일일이 정하는 일 자체가 큰 부담이고, 한 군데라도 잘못 설정하면 오히려 구멍이 됩니다.

그래서 대부분 둘만 쓴다 — 현실의 운영체제는 "전권을 가진 커널 / 제한된 응용 프로그램"이라는 두 단계만으로도 핵심 목표(응용 프로그램이 시스템을 망가뜨리지 못하게)를 충분히 달성한다고 봤습니다. 단계를 늘려 얻는 약간의 보안보다, 단순함과 성능을 지키는 쪽이 더 이득이라고 판단한 거예요. 게다가 운영체제는 여러 종류의 CPU에서 똑같이 돌아야 하는데, CPU마다 다른 권한 단계 수에 의존하지 않고 두 단계만 쓰면 이식성도 좋아집니다.

핵심은 "더 세밀한 게 항상 더 좋은 건 아니다"예요. 안전을 위해 복잡도와 비용을 얼마나 치를지를 저울질한 결과가 "두 단계"입니다.

🎯 면접관을 홀리는 핵심 멘트

"권한을 더 잘게 나누면 보안은 세밀해지지만, 단계를 오갈 때마다 전환 비용과 설계 복잡도가 늘어납니다. 일부 CPU가 네 단계를 제공해도 대부분의 운영체제가 유저/커널 두 단계만 쓰는 이유는, 그 둘만으로도 '응용 프로그램이 시스템을 망가뜨리지 못하게 막는다'는 핵심 목표를 충분히 달성하면서 단순함·성능·이식성을 지킬 수 있기 때문입니다. 보안은 무조건 세밀할수록 좋은 게 아니라, 얻는 안전과 치르는 비용의 트레이드오프로 봐야 합니다."


🤔 [생각해볼 주제 2 예시답안] 프로세스를 격리하는 건 좋은 걸까, 나쁜 걸까?

[문제 상황 요약]

프로세스는 각자 독립된 메모리를 가져, 한 프로세스가 죽어도 다른 프로세스는 멀쩡합니다(안정성↑). 하지만 협력하려면 IPC라는 번거로운 통로를 거쳐야 합니다(통신 비용↑). 크롬이 탭마다 프로세스를 따로 두는 선택을 두고, 격리는 득일까요 실일까요?

[튜터의 가이드 및 해설]

격리는 좋다/나쁘다로 잘라 말할 게 아니라, 안정성과 효율을 맞바꾼 설계 선택이에요. 양쪽을 다 봐야 합니다.

격리가 주는 득 — 안정성과 보안 — 프로세스는 서로의 메모리를 못 봅니다. 그래서 한 탭에서 악성 스크립트가 돌거나 페이지가 멈춰도, 그 피해가 그 프로세스 안에 갇혀요. 다른 탭과 브라우저 전체는 멀쩡합니다. 크롬에서 탭 하나가 "응답 없음"이 돼도 다른 탭은 잘 돌아가는 게 바로 이 격리 덕분이에요.

격리가 주는 실 — 비용 — 대가가 있습니다. 첫째, 프로세스마다 독립된 메모리를 가지니 메모리를 더 많이 씁니다(탭이 많으면 크롬이 메모리를 많이 먹는 이유). 둘째, 탭끼리 데이터를 주고받으려면 그냥 같은 메모리를 읽는 게 아니라 IPC라는 통로를 거쳐야 해서 통신이 더 번거롭고 느립니다.

그래서 선택은 상황에 달렸다 — 안정성이 중요한 브라우저는 메모리를 더 쓰더라도 탭을 프로세스로 격리하는 쪽을 택했어요. 반대로, 긴밀하게 자주 데이터를 주고받아야 하고 가벼워야 하는 작업이라면, 격리 대신 다음 시간에 배울 스레드(메모리를 공유하는 더 가벼운 단위)를 택하는 게 낫습니다. "격리하면 안전하지만 무겁고, 공유하면 빠르지만 한쪽이 무너지면 같이 위험하다" — 이 트레이드오프를 상황에 맞게 고르는 거예요.

🎯 면접관을 홀리는 핵심 멘트

"프로세스 격리는 안정성과 효율의 트레이드오프입니다. 프로세스가 각자 독립된 메모리를 가지면, 한쪽이 죽어도 다른 쪽이 멀쩡해 안정성과 보안이 높아집니다. 크롬이 탭마다 프로세스를 두는 이유죠. 대신 메모리를 더 쓰고, 데이터를 주고받으려면 IPC라는 별도 통로가 필요해 통신 비용이 듭니다. 그래서 안정성이 중요하면 프로세스로 격리하고, 긴밀하고 가벼운 협력이 필요하면 메모리를 공유하는 스레드를 택합니다. 무엇이 더 중요한가에 따라 고르는 설계 선택입니다."


🤔 [생각해볼 주제 3 예시답안] "동시에 도는 것처럼 보이는 것"과 "진짜 동시에 도는 것"

[문제 상황 요약]

코어가 하나뿐인 컴퓨터에서도 프로그램 수십 개가 동시에 도는 것처럼 보입니다. 운영체제가 빠르게 번갈아 실행하기 때문이죠. 그런데 코어가 여러 개면 진짜로 여러 프로그램이 같은 순간에 돌 수 있습니다. 사람 눈엔 똑같아 보이는데, 이 둘은 어떻게 다를까요?

[튜터의 가이드 및 해설]

겉보기엔 똑같지만 속은 본질적으로 다른 두 가지예요. 핵심은 "같은 순간에 진짜로 여러 개가 도느냐"입니다.

번갈아 빠르게 — 동시처럼 보이는 것 — 코어가 하나면 어느 한 순간에 실제로 도는 프로그램은 하나뿐이에요(Step 6에서 본 "Running은 한 번에 하나"). 운영체제가 A를 잠깐, B를 잠깐, C를 잠깐… 매우 빠르게 갈아 끼우며 번갈아 돌립니다. 전환이 워낙 빨라서 사람 눈에는 셋이 한꺼번에 도는 것처럼 보이지만, 현미경으로 들여다보면 사실은 줄을 서서 번갈아 도는 거예요. 마치 한 명의 요리사가 세 냄비를 빠르게 오가며 젓는 것과 같습니다.

진짜 한꺼번에 — 정말 동시에 도는 것 — 코어가 네 개면, 같은 순간에 진짜로 네 프로그램이 각자의 코어에서 돌 수 있어요. 요리사가 네 명이라 냄비 네 개를 정말 동시에 젓는 셈이죠. 이건 "빠르게 번갈아"가 아니라 "정말 한꺼번에"입니다.

왜 이 구분이 중요한가 — 코어가 8개라도, 프로그램이 일을 한 줄로만 처리하면 코어 7개는 놀아요. "진짜 동시에" 돌리는 이점을 누리려면, 프로그램이 일을 여러 갈래로 쪼개 여러 코어에 나눠줄 수 있어야 합니다. 그래서 "동시에 처리하는 것처럼 보이게 관리하는 능력"과 "진짜 여러 개를 한꺼번에 돌리는 능력"을 구분하는 게, 멀티코어 시대 프로그래밍의 출발점이에요. 이 두 가지에 붙는 정확한 이름(동시성과 병렬성)과 그걸 다루는 단위(스레드)를 다음 시간에 만납니다.

🎯 면접관을 홀리는 핵심 멘트

"코어가 하나면 운영체제가 프로그램들을 매우 빠르게 번갈아 실행해 동시에 도는 것처럼 보이게 합니다. 실제로는 어느 한 순간에 도는 건 하나뿐이에요. 반면 코어가 여러 개면 같은 순간에 진짜로 여러 프로그램이 한꺼번에 돕니다. 겉보기엔 똑같지만, 전자는 '번갈아 빠르게'이고 후자는 '정말 동시에'라는 본질적 차이가 있습니다. 그래서 멀티코어를 제대로 활용하려면 일을 여러 갈래로 쪼개 여러 코어에 나눠줄 수 있어야 하고, 이 구분이 동시성과 병렬성을 가르는 출발점입니다."

전체 목록 CS 기초지식

더 배우려면

실무 프로젝트까지 가고 싶다면

팀스파르타 백엔드 부트캠프에서 인스타그램 클론을 풀스택으로 완성합니다.