운영체제
컴퓨터 구조를 이해하고 나면 이런 질문이 자연스럽게 따라온다. CPU는 명령을 실행하고, 메모리는 데이터를 담고, SSD는 파일을 저장한다. 그렇다면 프로그램은 이 부품들을 어떻게 함께 쓰는 걸까. 브라우저와 메신저와 음악 플레이어가 동시에 돌아가도 왜 어느 정도 질서가 유지될까. 어떤 프로그램은 파일을 읽고, 어떤 프로그램은 네트워크로 통신하고, 어떤 프로그램은 화면에 그림을 그리는데, 이 모든 일을 누가 조율할까.
그 역할을 맡는 소프트웨어가 운영체제다. 운영체제는 눈에 잘 보이지 않지만, 프로그램이 세상을 보는 방식을 결정한다. 우리가 파일을 연다, 메모리를 쓴다, 프로세스를 실행한다, 소켓으로 통신한다라고 말할 수 있는 이유는 운영체제가 하드웨어의 복잡함을 정리해 추상화된 형태로 제공하기 때문이다. 그래서 운영체제를 이해하면 프로그램이 왜 그렇게 보이는지, 왜 때로는 느려지고 왜 때로는 죽는지 절반은 설명할 수 있게 된다.

운영체제는 하드웨어 위에 질서를 만든다
운영체제가 없다면 각 프로그램은 CPU, 메모리, 디스크, 키보드, 화면 같은 장치를 직접 다뤄야 한다. 문제는 현실의 컴퓨터가 혼자 쓰는 계산기처럼 단순하지 않다는 데 있다. 여러 프로그램이 한꺼번에 실행되고, 서로 다른 장치에 접근하고, 같은 메모리를 마구 건드리면 금세 충돌이 난다.
운영체제는 이 혼잡한 상황에 규칙을 만든다. 가장 중요한 역할은 자원을 나눠주고, 위험한 접근을 막고, 공통된 인터페이스를 제공하는 것이다. 프로그램 입장에서는 "CPU 전체를 내가 쓰는 것처럼", "메모리가 넓게 펼쳐져 있는 것처럼", "파일이 이름 있는 상자처럼", "네트워크가 읽고 쓰는 통로처럼" 보인다. 하지만 실제 아래층에서는 운영체제가 여러 요청을 조정하고 있다.
운영체제가 제공하는 대표적인 추상화는 네 가지로 생각하면 이해가 쉽다.
CPU는 프로세스와 스레드라는 실행 단위로 추상화된다.
메모리는 각 프로그램만의 가상 주소 공간으로 추상화된다.
저장장치는 파일과 디렉터리로 추상화된다.
장치는 드라이버와 입출력 인터페이스로 추상화된다.
이 추상화 덕분에 개발자는 매번 SSD의 블록 구조나 네트워크 카드의 전기 신호를 직접 다루지 않아도 된다. 대신 파일을 열고, 바이트를 읽고, 프로세스를 실행하고, 소켓으로 데이터를 보내는 식으로 더 높은 수준에서 작업할 수 있다.
즉, 운영체제는 단순한 편의 기능 모음이 아니다. 하드웨어를 여러 프로그램이 안전하고 효율적으로 함께 쓰게 만드는 사회의 법과 비슷하다. 법이 없으면 모두가 자유로운 것이 아니라, 모두가 서로를 방해하게 된다. 운영체제는 바로 그 혼란을 막는다.
커널과 사용자 프로그램 사이에는 경계가 있다
운영체제를 이해할 때 가장 중요한 구분 중 하나는 사용자 공간과 커널 공간이다. 사용자 공간은 우리가 작성한 애플리케이션 코드가 실행되는 영역이다. 브라우저, 게임, 웹 서버, 파이썬 프로그램이 여기에 속한다. 커널 공간은 운영체제의 핵심 코드가 실행되는 특별한 영역이다.
왜 굳이 둘을 나눌까. 이유는 간단하다. 아무 프로그램이나 하드웨어를 마음대로 건드릴 수 있게 두면 시스템 전체가 너무 쉽게 망가진다. 어떤 프로그램이 실수로 엉뚱한 메모리를 덮어쓰거나, 디스크 전체를 잘못 건드리거나, 다른 프로그램의 데이터를 훔쳐볼 수 있다면 컴퓨터는 안정적으로 동작할 수 없다.
그래서 일반 프로그램은 제한된 권한으로 실행된다. 대신 꼭 필요한 일을 하려면 운영체제에게 요청해야 한다. 이 요청 창구가 바로 시스템 콜이다.
시스템 콜은 사용자 프로그램이 커널에게 "이 파일을 열어줘", "메모리를 더 할당해줘", "네트워크로 데이터를 보내줘", "새 프로세스를 만들어줘"라고 부탁하는 공식적인 문이다. 함수 호출처럼 보여도 의미는 훨씬 크다. 단순히 같은 프로그램 안에서 다른 코드를 부르는 것이 아니라, 권한이 다른 세계로 넘어가는 경계 통과이기 때문이다.
예를 들어 print()를 호출했다고 생각해보자. 화면에 글자를 보여주는 일은 결국 운영체제가 관리하는 출력 장치와 연결된다. 파일을 읽는 코드도, 네트워크 요청을 보내는 코드도, 메모리를 크게 잡는 일도, 밑으로 내려가면 운영체제의 도움을 받는다. 개발자가 직접 시스템 콜을 보지 못하는 경우도 많지만, 표준 라이브러리나 런타임이 그 일을 대신해주고 있을 뿐이다.
이 경계를 이해하면 성능과 안정성도 다르게 보인다. 시스템 콜은 공짜가 아니다. 사용자 공간에서 커널 공간으로 넘어가고 다시 돌아오는 데 비용이 든다. 또한 디스크나 네트워크 같은 장치는 CPU보다 훨씬 느리기 때문에, 그 뒤에 숨어 있는 운영체제 작업은 프로그램 전체의 속도에 큰 영향을 준다. 코드 몇 줄은 가벼워 보여도, 그 안에 파일 읽기나 소켓 대기가 들어 있다면 실제 비용은 전혀 다를 수 있다.
프로세스는 실행 중인 프로그램이고, 스레드는 그 안의 작업 흐름이다
운영체제 장에서 가장 자주 등장하는 단어가 프로세스와 스레드다. 둘은 비슷해 보이지만 개발자 관점에서 구분이 매우 중요하다.
프로세스는 실행 중인 프로그램의 독립된 인스턴스다. 프로그램 파일은 디스크에 저장된 설계도에 가깝고, 프로세스는 그 설계도가 메모리에 올라와 실제로 돌아가는 상태다. 각 프로세스는 보통 자신만의 가상 메모리 공간, 열린 파일 정보, 실행 상태를 가진다. 브라우저 하나를 켰다고 해도 내부적으로 여러 프로세스가 돌아갈 수 있다.
스레드는 프로세스 안에서 실제 명령을 실행하는 흐름이다. 한 프로세스 안에 스레드가 하나만 있으면 단일 흐름으로 실행되고, 여러 개 있으면 같은 메모리를 공유하면서 여러 작업 흐름이 함께 움직일 수 있다. 예를 들어 서버 프로그램은 한 스레드가 요청을 기다리고, 다른 스레드가 계산을 처리하고, 또 다른 스레드가 로그를 남길 수 있다.
비유하자면 프로세스는 하나의 회사이고, 스레드는 그 회사 안에서 일하는 직원들에 가깝다. 서로 다른 회사는 건물도, 서류함도, 예산도 따로 가진다. 반면 같은 회사 직원들은 사무실과 문서를 공유할 수 있다. 이 공유가 빠르고 효율적일 수는 있지만, 동시에 충돌 위험도 함께 가져온다.
개발자가 이 차이를 알아야 하는 이유는 문제의 성격이 완전히 달라지기 때문이다. 프로세스는 서로 격리되어 비교적 안전하지만, 생성 비용과 통신 비용이 더 크다. 스레드는 같은 메모리를 공유하므로 협력하기 쉽고 가볍지만, 한 스레드의 실수가 다른 스레드가 보는 데이터까지 망가뜨릴 수 있다. 다음 장에서 다룰 동시성 문제의 상당수는 바로 이 공유에서 시작된다.
스케줄링은 누가 언제 CPU를 쓰는지 정하는 일이다
우리는 여러 프로그램이 동시에 실행된다고 느낀다. 음악을 들으면서 브라우저를 쓰고, 메신저 알림도 받고, 백그라운드에서 파일 다운로드도 진행된다. 하지만 CPU 코어 수는 유한하다. 코어가 하나라면 진짜로 한 순간에 하나의 작업밖에 실행하지 못한다. 그럼에도 여러 작업이 동시에 움직이는 것처럼 보이는 이유는 운영체제가 아주 빠르게 실행 순서를 바꿔가며 CPU를 나눠주기 때문이다.
이 일을 스케줄링이라고 한다. 운영체제의 스케줄러는 어떤 프로세스나 스레드에게 CPU를 줄지 결정한다. 매우 단순하게 말하면 "지금 누구를 잠깐 돌릴 것인가"를 계속 판단하는 것이다.
여기서 중요한 개념이 컨텍스트 스위칭이다. 어떤 작업을 멈추고 다른 작업을 실행하려면, 현재 작업의 상태를 저장하고 다음 작업의 상태를 복원해야 한다. 레지스터 값, 프로그램 카운터, 스택 정보 같은 실행 문맥을 바꿔 끼우는 것이다. 이 과정을 통해 CPU는 마치 다른 일을 하던 사람으로 순간적으로 역할을 바꾸는 것처럼 동작한다.
하지만 컨텍스트 스위칭은 마법이 아니다. 이 역시 비용이 든다. 작업을 너무 잘게 쪼개 자주 전환하면 실제 일보다 갈아타는 비용이 커질 수 있다. 스레드를 무턱대고 많이 만든다고 빨라지지 않는 이유가 여기에 있다. 오히려 CPU 시간을 나누는 오버헤드와 동기화 비용 때문에 더 느려질 수도 있다.
개발자 관점에서는 다음과 같은 장면에서 스케줄링 감각이 중요해진다.
CPU를 많이 쓰는 작업이 다른 요청의 응답성을 떨어뜨릴 때
스레드를 너무 많이 만들어 오히려 성능이 나빠질 때
블로킹 I/O 때문에 스레드가 놀고 있는데도 시스템이 바빠 보일 때
예를 들어 웹 서버가 요청마다 스레드를 하나씩 만든다고 해보자. 요청 수가 적을 때는 단순하고 잘 동작할 수 있다. 하지만 동시 접속이 커지면 수많은 스레드가 생기고, 일부는 CPU를 쓰고 일부는 디스크나 네트워크를 기다리며, 운영체제는 이들을 계속 전환하느라 바빠진다. 이때 병목은 "내 코드의 계산량"이 아니라 "운영체제가 감당해야 하는 실행 단위 수"일 수 있다.
메모리 관리는 프로그램에게 넓고 안전한 세계를 보여준다
프로그램은 보통 자신만의 큰 메모리 공간이 있는 것처럼 행동한다. 배열을 만들고, 객체를 생성하고, 함수를 호출하면 스택에 지역 변수가 쌓인다. 하지만 실제 물리 메모리는 모든 프로그램이 함께 쓰는 유한한 자원이다. 운영체제는 이 복잡함을 숨기기 위해 가상 메모리라는 강력한 추상화를 제공한다.
가상 메모리의 핵심은 각 프로세스가 연속적이고 독립된 주소 공간을 가진 것처럼 보이게 만드는 것이다. 프로그램은 0번지부터 어딘가까지 자신만의 메모리가 있는 것처럼 접근하지만, 운영체제와 하드웨어는 그 주소를 실제 물리 메모리 위치로 변환해준다. 덕분에 서로 다른 프로세스가 같은 가상 주소를 써도 실제로는 다른 물리 메모리를 가리킬 수 있다.
이 구조는 단순히 편한 정도가 아니라 안전성의 핵심이다. 한 프로그램이 다른 프로그램 메모리를 함부로 읽거나 쓸 수 없게 막아주기 때문이다. 우리가 어떤 앱이 죽어도 운영체제 전체가 매번 함께 무너지지 않는 이유 중 하나가 바로 이 메모리 보호다.
가상 메모리는 보통 페이지라는 고정 크기 단위로 관리된다. 메모리를 잘게 나눈 블록이라고 생각하면 된다. 프로그램이 어떤 주소에 접근하면, 하드웨어와 운영체제가 그 주소가 어느 페이지에 속하는지 보고 실제 위치를 찾는다. 필요한 페이지가 아직 물리 메모리에 없다면 디스크에서 가져오는 일이 생길 수 있는데, 이때 큰 지연이 발생할 수 있다. 메모리가 부족할 때 시스템이 갑자기 굼떠지는 이유가 여기에 숨어 있는 경우가 많다.
이 장면을 이해하면 "메모리를 많이 쓴다"는 말의 무게도 달라진다. 단지 숫자 하나가 커지는 것이 아니라, 페이지 이동이 늘고 캐시 효율이 나빠지고, 심하면 디스크를 끌어다 쓰는 상황까지 이어질 수 있다. 그러면 CPU가 아무리 빨라도 전체 프로그램은 느려진다.
스택과 힙은 메모리 사용 방식이 다르다
메모리 관리에서 스택과 힙은 개발자가 반드시 구분해야 하는 두 공간이다. 둘 다 메모리지만, 쓰는 방식과 성격이 다르다.
스택은 함수 호출과 함께 생겼다가 사라지는 데이터를 담는 공간이다. 함수의 매개변수, 지역 변수, 복귀 위치 같은 정보가 여기에 쌓인다. 함수가 호출될 때 스택 프레임이 올라가고, 함수가 끝나면 내려온다. 구조가 단순하고 관리가 빠르다는 장점이 있다. 대신 크기가 제한적이어서 너무 깊은 재귀 호출이나 큰 지역 변수는 스택 오버플로를 일으킬 수 있다.
힙은 실행 중에 크기가 유동적으로 변하는 데이터를 위한 공간이다. 객체를 만들고, 큰 버퍼를 잡고, 런타임에 필요한 메모리를 동적으로 할당할 때 주로 사용된다. 자유도가 높지만 관리가 더 복잡하다. C처럼 수동 메모리 관리가 필요한 언어에서는 해제를 잘못하면 메모리 누수나 댕글링 포인터 같은 문제가 생긴다. 가비지 컬렉션 언어에서도 힙은 자동으로 정리될 뿐이지, 공짜는 아니다. 객체를 너무 많이 만들면 수집 비용이 커지고, 메모리 압박이 증가한다.
개발자에게 중요한 점은 "메모리를 쓴다"는 말이 추상적인 한 문장이 아니라는 것이다. 어떤 데이터가 스택에 있는지, 힙에 있는지, 얼마나 오래 살아남는지, 얼마나 자주 할당과 해제가 일어나는지에 따라 성능과 안정성이 달라진다. 프로그램이 갑자기 메모리를 많이 먹거나, 지연 시간이 튀거나, 이상한 충돌이 나는 원인을 추적할 때 이 감각이 큰 도움이 된다.
메모리 보호는 편의 기능이 아니라 생존 장치다
운영체제가 메모리를 관리할 때 중요한 목표는 단순히 효율만이 아니다. 보호가 똑같이 중요하다. 한 프로그램의 버그가 다른 프로그램의 데이터를 덮어쓰지 못하게 하고, 허용되지 않은 영역 접근을 막아야 한다.
우리가 흔히 보는 세그멘테이션 폴트나 접근 위반 오류는 이런 보호 장치가 작동한 결과다. 겉으로는 프로그램이 갑자기 죽는 불편한 상황처럼 보이지만, 사실은 더 큰 재앙을 막기 위한 브레이크에 가깝다. 운영체제가 아무 경계도 두지 않았다면 프로그램 하나의 실수가 파일 시스템 손상이나 다른 프로세스 데이터 오염으로 이어질 수 있다.
보호는 보안과도 연결된다. 악성 코드가 마음대로 커널 메모리나 다른 프로세스에 접근할 수 있다면 시스템 전체가 위험해진다. 운영체제가 권한과 주소 공간을 분리하는 이유는 안정성과 보안을 동시에 지키기 위해서다. 그래서 운영체제는 성능 관리자이면서도 경비원이다.
파일 시스템은 데이터를 오래 기억하게 만드는 규칙이다
메모리가 작업대라면 파일 시스템은 창고의 정리 규칙이다. SSD나 HDD는 그저 비트 저장 장치일 뿐인데, 운영체제는 그 위에 파일과 디렉터리라는 구조를 만든다. 덕분에 우리는 데이터를 이름으로 저장하고, 폴더로 묶고, 나중에 다시 찾을 수 있다.
파일 시스템이 중요한 이유는 단지 편리해서가 아니다. 데이터의 위치, 크기, 권한, 수정 시간 같은 메타데이터를 관리하고, 빈 공간을 배치하고, 시스템이 갑자기 꺼져도 손상을 줄이기 위한 규칙을 제공한다. 즉, 파일 시스템은 저장장치 위에 세워진 질서다.
개발자는 종종 파일을 읽고 쓰는 일을 단순한 함수 호출처럼 생각한다. 하지만 실제로는 생각보다 많은 단계가 숨어 있다. 경로를 해석하고, 권한을 확인하고, 파일 디스크립터를 만들고, 커널 버퍼를 거치고, 필요하면 실제 저장장치와 동기화한다. 여기에 네트워크 파일 시스템이나 데이터베이스의 저장 엔진까지 얹히면 경로는 더 길어진다.
그래서 파일 I/O는 많은 프로그램의 숨은 병목이 된다. CPU는 빠른데 프로그램이 느리다면, 디스크 읽기 대기나 작은 파일을 너무 자주 여닫는 패턴이 원인일 수 있다. 로그를 한 줄 쓸 때마다 즉시 디스크에 반영하려고 하면 안전성은 높아질 수 있지만 성능은 크게 떨어질 수 있다. 반대로 버퍼링을 쓰면 빨라질 수 있지만, 장애가 났을 때 일부 데이터가 아직 디스크에 안 써졌을 가능성도 생긴다. 여기서도 결국 트레이드오프가 나타난다.
I/O는 계산보다 느리고, 그래서 전체 시스템을 붙잡는다
입출력, 즉 I/O는 파일뿐 아니라 네트워크, 키보드, 화면, 데이터베이스 연결 같은 외부와의 모든 주고받기를 포함한다. 운영체제는 이 느린 세계와 CPU의 빠른 세계 사이를 중재한다.
문제는 속도 차이다. CPU는 눈 깜짝할 사이에 수많은 명령을 처리할 수 있지만, 디스크 접근이나 네트워크 왕복은 그에 비해 매우 느리다. 그래서 프로그램은 계산보다 기다림 때문에 느려지는 경우가 많다. 서버가 바빠 보이는데 CPU 사용률은 높지 않다면, 실제로는 수많은 요청이 I/O를 기다리며 묶여 있는 상황일 수 있다.
예를 들어 데이터베이스에서 결과를 가져오는 API를 생각해보자. 애플리케이션 코드에서는 함수 하나 호출한 것처럼 보이지만, 실제로는 네트워크 요청, 서버 처리, 디스크 읽기, 락 대기, 결과 전송 같은 과정이 얽혀 있다. 이때 병목은 코드의 알고리즘이 아니라 I/O 경로 어디엔가 있을 수 있다.
운영체제는 이런 상황을 다루기 위해 버퍼링, 캐싱, 비동기 I/O, 인터럽트 같은 메커니즘을 제공한다. 하지만 개발자가 이 원리를 모르고 있으면 이상한 현상을 설명하기 어렵다. 왜 파일을 조금씩 자주 읽는 것보다 한 번에 읽는 것이 유리한지, 왜 네트워크 호출을 순차적으로 기다리면 응답이 느려지는지, 왜 로그를 과하게 남기면 시스템이 버벅이는지가 모두 운영체제 수준의 I/O 특성과 연결된다.
운영체제를 알면 버그가 다르게 보인다
운영체제를 공부하면 단지 이론 지식이 늘어나는 것이 아니다. 개발 중 만나는 문제들의 얼굴이 선명해진다.
프로그램이 갑자기 죽는다면 단순히 "언어가 이상하다"가 아니라 메모리 보호, 스택 한계, 잘못된 접근을 떠올릴 수 있다. 서버가 느려진다면 단순히 "코드가 무겁다"가 아니라 스레드 수, 컨텍스트 스위칭, 블로킹 I/O, 파일 시스템 병목을 의심할 수 있다. 어떤 작업이 동시에 실행되는 것처럼 보이지만 미묘한 충돌이 난다면 프로세스와 스레드, 공유 메모리, 스케줄링의 영향을 생각하게 된다.
운영체제를 안다는 것은 커널 코드를 직접 짤 수 있다는 뜻이 아니다. 훨씬 중요한 것은 애플리케이션이 기대는 바닥의 규칙을 이해한다는 뜻이다. 그 바닥을 모르면 프로그램은 마치 허공에 떠 있는 것처럼 보인다. 반대로 그 바닥을 알면, 추상화 위에서 일하면서도 언제 아래층을 의심해야 하는지 감이 생긴다.
운영체제는 프로그램의 세계관을 만든다
지금까지 본 내용을 한 문장으로 정리하면 이렇다. 운영체제는 하드웨어를 관리하는 소프트웨어이면서, 동시에 프로그램이 세상을 바라보는 방식 자체를 정의하는 소프트웨어다.
프로그램은 운영체제 덕분에 CPU를 실행 단위로 나눠 쓰고, 메모리를 안전한 주소 공간처럼 사용하고, 파일을 이름 붙은 데이터 묶음처럼 다루고, 장치와 네트워크를 통일된 인터페이스로 접근한다. 시스템 콜은 사용자 코드와 커널 사이의 문이고, 프로세스와 스레드는 실행의 단위이며, 스케줄링과 컨텍스트 스위칭은 CPU 시간을 분배하는 방법이다. 가상 메모리와 페이지는 안전성과 유연성을 만들고, 스택과 힙은 데이터의 생애와 비용을 갈라놓는다. 파일 시스템과 I/O는 프로그램의 느린 뒷면을 구성한다.
운영체제를 이해하면 코드가 더 이상 텍스트만으로 보이지 않는다. 그 코드는 CPU 시간을 요구하고, 메모리 공간을 차지하고, 커널의 도움을 받고, 파일과 네트워크를 통해 바깥세상과 연결되는 살아 있는 실행체로 보이기 시작한다. 그 순간부터 개발자는 단순히 문법을 쓰는 사람이 아니라, 시스템 위에서 시스템답게 생각하는 사람이 된다.
다음 장에서는 이 운영체제 위에서 벌어지는 더 까다로운 문제, 즉 동시성과 병렬성을 다룬다. 여러 작업이 함께 움직이는 세상에서는 속도만 늘어나는 것이 아니라 복잡성도 함께 폭발한다. 운영체제가 무대를 만들었다면, 이제 그 무대 위에서 여러 배우가 동시에 뛰어다닐 때 무슨 일이 생기는지 볼 차례다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.
