운영체제: 컴퓨터의 관리자
컴퓨터에서 음악을 들으며 웹 서핑을 하고, 메신저 알림을 받으면서 문서를 쓴다. CPU 코어는 몇 개뿐인데 어떻게 수십 개의 프로그램이 동시에 돌아갈까? 서로 메모리를 침범하지 않고, 같은 SSD를 함께 쓰면서도 파일이 뒤섞이지 않는 이유는 뭘까? 그 뒤에는 운영체제라는 부지런한 주방장이 있다.
- 운영체제(OS)의 세 가지 역할(자원 관리, 추상화, 보호)을 설명한다.
- 커널 모드와 사용자 모드, 시스템 콜이 왜 필요한지 안다.
- 전원을 켠 뒤 운영체제가 실행되기까지의 부팅 과정을 따라간다.
- 프로세스와 스레드를 구분하고, CPU 스케줄링 방식에 따른 차이를 시뮬레이터로 비교한다.
- 인터럽트가 키보드·네트워크 같은 사건을 CPU에 알리는 방법을 이해한다.
- 여러 스레드가 같은 데이터를 고칠 때 생기는 경쟁 상태와 잠금(lock)을 체험한다.
운영체제가 없다면
운영체제가 없는 세상을 상상해 보자. 사진 앱을 만드는 회사는 SSD 제조사마다 다른 명령어로 블록을 읽는 코드를, 화면 칩마다 다른 방식으로 픽셀을 찍는 코드를 직접 짜야 한다. 사진 앱과 음악 앱이 동시에 실행되면 둘 다 CPU와 메모리를 독차지하려고 싸운다. 악성 프로그램은 다른 앱의 메모리를 마음대로 읽을 수 있다.
운영체제(OS, Operating System)는 하드웨어와 응용 프로그램 사이에 앉아 이 문제들을 해결한다. 윈도우, macOS, 리눅스, 안드로이드, iOS가 모두 운영체제다.
| 역할 | 하는 일 | 주방장이라면 |
|---|---|---|
| 자원 관리 | CPU 시간, 메모리, 저장장치, 네트워크를 여러 프로그램에 나눠 준다. | 누가 언제 화구를 쓰고 조리대 어디를 쓸지 정한다. |
| 추상화 | “블록 5번, 6번” 대신 “파일”, “CPU 코어” 대신 “프로세스”처럼 쉬운 개념을 제공한다. | 요리사에게 “냉장고 3번 칸 왼쪽 아래” 대신 “양파 가져와”라고 말할 수 있게 한다. |
| 보호 | 프로그램끼리 서로의 메모리와 파일을 침범하지 못하게 막는다. | 다른 요리사의 조리대에 손대지 못하게 한다. |
커널과 시스템 콜: 창구를 통해서만
운영체제의 핵심 부분을 커널(kernel, 알맹이)이라 한다. CPU에는 두 가지 실행 모드가 있다. 커널은 모든 하드웨어를 직접 만질 수 있는 커널 모드에서, 일반 앱은 권한이 제한된 사용자 모드에서 실행된다. 사용자 모드에서 하드웨어를 직접 건드리는 명령을 실행하면 CPU가 거부하고 커널에 알린다.
그래서 앱이 파일을 저장하거나 네트워크로 데이터를 보내려면 커널에게 정식으로 부탁해야 한다. 이 부탁을 시스템 콜(system call)이라 한다. 은행 금고에 손님이 직접 들어갈 수 없고 창구 직원에게 요청해야 하는 것과 같다.
시스템 콜 따라가기: “파일 저장” 버튼을 누르면
SSD, 프린터, 그래픽 카드는 제조사마다 조작 방법이 다르다. 각 장치를 다루는 방법을 담은 커널의 부품을 드라이버(device driver)라 한다. 새 장치를 꽂았을 때 “드라이버를 설치하세요”라는 말이 나오는 이유다. 드라이버 덕분에 앱은 장치의 종류를 몰라도 된다.
부팅: 운영체제가 깨어나기까지
전원 버튼을 누르면 메모리는 텅 비어 있다. 운영체제도 SSD에 파일로 잠들어 있을 뿐이다. 그렇다면 누가 운영체제를 메모리로 불러올까? 자기 자신을 끌어올린다는 뜻의 부팅(booting, bootstrap)은 다음 순서로 진행된다.
- 전원 공급: 전원부가 안정되면 CPU가 깨어나 미리 정해진 주소의 명령부터 실행한다.
- 펌웨어(BIOS/UEFI): 메인보드의 작은 플래시 칩에 들어 있는 프로그램이 메모리, 키보드, 저장장치를 점검한다.
- 부트로더: 펌웨어가 저장장치에서 부트로더라는 작은 프로그램을 찾아 실행한다.
- 커널 적재: 부트로더가 운영체제 커널 파일을 메모리에 올리고 실행을 넘긴다.
- 초기화: 커널이 드라이버를 불러오고, 메모리 관리를 시작하고, 첫 번째 프로세스를 띄운다.
- 로그인 화면: 서비스와 화면 관리 프로그램이 차례로 실행되고, 마침내 로그인 화면이 나타난다.
프로세스와 스레드
SSD에 저장된 앱은 그저 명령어와 데이터가 담긴 파일, 즉 프로그램이다. 레시피 책에 비유할 수 있다. 이 프로그램을 실행하면 운영체제가 메모리에 올리고 실행 상태를 관리하는데, 이렇게 실행 중인 프로그램을 프로세스(process)라 한다. 레시피를 보고 실제로 요리가 진행 중인 상태다. 같은 브라우저를 두 번 실행하면 프로그램은 하나지만 프로세스는 두 개다.
운영체제는 프로세스마다 다음 정보를 기록해 둔다: 지금 어디까지 실행했는지(PC 값), 레지스터 값들, 사용 중인 메모리 영역, 열어 둔 파일, 상태(실행 중/대기 중/준비됨). 이 기록 덕분에 프로세스를 잠시 멈췄다가 정확히 그 자리에서 다시 이어 갈 수 있다.
한 프로세스 안에서도 여러 일을 동시에 하고 싶을 때가 있다. 웹 브라우저는 페이지를 그리면서 동시에 파일을 내려받고 동영상을 재생한다. 한 프로세스 안에서 메모리를 공유하며 따로 실행되는 흐름을 스레드(thread, 실)라 한다. 한 주방(프로세스) 안의 여러 요리사(스레드)가 같은 조리대(메모리)를 함께 쓰는 셈이다. 함께 쓰니 빠르고 편하지만, 나중에 보겠지만 부딪칠 위험도 있다.
CPU 스케줄링: 누구에게 먼저 CPU를 줄까
준비된 프로세스는 수십 개인데 CPU 코어는 몇 개뿐이다. 운영체제는 아주 짧은 시간(수 밀리초)씩 번갈아 CPU를 나눠 줘서 모두가 동시에 실행되는 것처럼 보이게 한다. 누구에게 언제, 얼마나 줄지 정하는 규칙을 스케줄링(scheduling)이라 한다.
창구가 하나인 은행에 손님들이 온다. 먼저 온 순서대로 처리할까(선착순)? 금방 끝나는 손님부터 처리할까(짧은 일 먼저)? 아니면 한 사람당 2분씩만 처리하고 아직 안 끝났으면 줄 맨 뒤로 보낼까(돌아가며)? 어떤 방식이냐에 따라 손님들의 평균 대기 시간과 불만이 크게 달라진다.
CPU 스케줄러 비교
실제 운영체제는 이들을 섞은 정교한 방식을 쓴다. 키보드 입력을 기다리는 대화형 프로그램은 우선순위를 올려 반응을 빠르게 하고, 오래 계산만 하는 프로그램은 뒤로 돌린다. 작업을 바꿀 때마다 레지스터와 PC를 저장하고 다른 프로세스의 것을 불러오는 문맥 교환(context switch) 비용이 들기 때문에 너무 자주 바꾸는 것도 손해다.
인터럽트: “잠깐만요!”
CPU가 계산에 몰두하는 동안 키보드가 눌렸다. CPU는 이를 어떻게 알까? 계산하다 말고 1초에 수천 번 “키보드 눌렸어?” 하고 물어보는 방식(폴링)은 낭비가 크다. 대신 키보드가 CPU에게 직접 신호를 보낸다. 이것이 인터럽트(interrupt, 끼어들기)다.
인터럽트를 받은 CPU는 하던 일의 위치(PC와 레지스터)를 저장하고, 운영체제에 미리 등록된 처리 코드(인터럽트 처리기)를 실행한 뒤, 원래 하던 일로 정확히 돌아간다. 요리하다가 초인종이 울리면 불을 줄이고 택배를 받은 뒤 다시 요리를 이어 가는 것과 같다.
인터럽트 타임라인
경쟁 상태: 동시에 고치면 생기는 일
두 스레드가 같은 메모리에 있는 숫자 count를 각각 1씩 늘린다고 하자. 결과는 당연히 2가 늘어야 한다. 그런데 “1 늘리기”는 CPU에게 한 번의 동작이 아니라 세 단계다: ① 메모리에서 레지스터로 읽기, ② 레지스터에서 더하기, ③ 메모리에 쓰기(4장의 LOAD, ADD, STORE). 스케줄러가 그 사이 아무 때나 스레드를 바꿀 수 있다면?
두 스레드, 하나의 계좌
번갈아 실행하면 두 스레드가 모두 100을 읽고, 각자 101을 계산해 쓴다. 1이 사라졌다! 이처럼 실행 순서에 따라 결과가 달라지는 버그를 경쟁 상태(race condition)라 한다. 은행 앱에서 이런 일이 생기면 돈이 사라진다. 더 무서운 점은 이 버그가 대부분의 실행에서는 드러나지 않다가 아주 가끔만 터진다는 것이다.
해결책은 잠금(lock, mutex)이다. 공유 데이터를 고치는 구간에 들어가기 전에 잠금을 걸고, 끝나면 푼다. 다른 스레드는 잠금이 풀릴 때까지 기다린다. 화장실 문을 잠그는 것과 같다. 위에서 잠금을 켜고 다시 해 보자. 단, 잠금을 너무 많이 쓰면 스레드들이 서로 기다리느라 느려지고, 서로 상대의 잠금을 기다리며 영원히 멈추는 교착 상태(deadlock)가 생길 수도 있다.
핵심 정리
- 운영체제는 자원 관리, 추상화, 보호를 맡는 하드웨어와 앱 사이의 관리자다.
- 커널은 커널 모드에서, 앱은 사용자 모드에서 돈다. 앱은 시스템 콜로 커널에 부탁해야 하드웨어를 쓸 수 있다.
- 프로세스는 실행 중인 프로그램, 스레드는 프로세스 안에서 메모리를 공유하는 실행 흐름이다.
- 스케줄러는 CPU를 짧게 나눠 여러 프로세스에 준다. 방식에 따라 대기 시간과 반응성이 달라진다.
- 인터럽트는 장치가 CPU에 사건을 알리는 신호다. 타이머 인터럽트로 OS가 CPU를 되찾는다.
- 여러 스레드가 공유 데이터를 동시에 고치면 경쟁 상태가 생긴다. 잠금으로 막는다.
확인 퀴즈
앱이 SSD에 파일을 쓰고 싶을 때 하는 일은?
“프로그램”과 “프로세스”의 차이로 옳은 것은?
라운드 로빈 스케줄링의 가장 큰 장점은?
타이머 인터럽트가 없다면 생길 수 있는 문제는?
두 스레드가 잠금 없이 같은 count를 1씩 늘렸더니 2가 아니라 1만 늘었다. 원인은?