우리의 컴퓨터 프로그램은 모두 하드웨어(HW) 기반 환경 위에서 실행된다.
하지만 하드웨어 자원은 한적되어있고,
이를 직접 다루기에는 복잡하고 비효율적일 수 있다.
그렇다면 이 하드웨어 위에서 프로그램들이 원활하게 돌아가도록 돕는 존재는 무엇일까? 바로 운영체제(OS)이다.
운영체제(Operating System, OS)는 컴퓨터 시스템의 핵심 소프트웨어(SW)로, 컴퓨터 하드웨어와 응용 프로그램 간의 상호작용을 관리하고 제어하는 역할을 한다.

운영체제의 주된 목적은 사용자와 하드에워 사이의 인터페이스를 제공하여,
응용프로그램이 보다 효율적이고 안정적으로 실행될 수 있도록 지원하는 것이다.
또한 운영체제는 시스템 자원을 효율적으로 관리해,
여러 응용 프로그램이 동시에 원활하게 동작할 수 있도록 보장한다.
이제 운영체제에서 중요한 개념들에 대하여 알아보자
운영체제는 단일한 프로그램처럼 보이지만, 실제로는 여러 구성 요소가 유기적으로 협력하며 동작하는 복합적인 시스템 소프트웨어이다!

운영체제의 구조는 크게 다음과 같은 계층으로 나눌 수 있다.
운영체제의 핵심 부분으로, 하드웨어와 직접 상호작용하는 역할이다.
즉, 커널은 운영체제의 “심장”과도 같은 존재이다
응용 프로그램이 운영체제의 기능을 직접 사용할 수 있도록 제공하는 인터페이스(API)이다.
예시를 보자
프로그램은 시스템 콜을 통해 파일 접근, 프로세스 생성, 네트워크 통신 등을 요청한다.
운영체제에서는 보안성과 안정성을 위해 CPU 실행 권한을 두 가지 모드로 구분해둔다.
즉, 일반 프로그램은 사용자 모드에서 동작하고, 하드웨어 자원에 접근할 때는 반드시 커널 모드의 도움을 받아야 합니다.
그럼 사용자모드와 커널모드가 서로 전환하려면 어떻게 하나?
1) 사용자 프로그램이 파일을 열기 위해 open() 호출
2) 시스템 콜 인터럽트 발생 → CPU 모드 전환 (User → Kernel)
3) 운영체제가 해당 요청 처리 (파일 핸들 생성, 접근 권한 확인 등)
4) 처리 완료 후 결과를 반환 → CPU 모드 전환 (Kernel → User)
5) 프로그램이 결과값을 사용하며 실행 계속
즉, 이들의 관계는 다음과 같다
사용자와 운영체제 사이의 접점으로, 우리에게는 익숙한 개념일 수 있다.
크게 두 가지 형태가 존재한다.
cli보다 gui 쪽이 좀더 시각적인 부분이라고 생각하면 된다
운영체제는 응용 프로그램이 편리하게 실행될 수 있도록 다양한 라이브러리와 유틸리티 프로그램을 제공한다.
운영체제의 구조에서 드라이버(Driver)는 하드웨어 장치와 운영체제(특히 커널) 사이를 이어주는 중간 계층 역할이다.
하드웨어는 종류도 다양하고 제조사마다 제어 방식도 다르고, 운영체제가 모든 장치의 세부 동작을 직접 알 수는 없기 때문에,
장치 제조사가 제공하는 드라이버 프로그램을 통해 하드웨어를 제어한다.
즉, 디바이스 드라이버는 운영체제에게는 표준화된 인터페이스를 제공하고, 하드웨어에게는 구체적인 제어 명령을 전달하는 "번역기" 같은 역할을 해준다!
다음과 같은 식으로 운영체제는 동작하는 것이라고 볼 수 있다.
응용 프로그램 → 시스템 콜 → 운영체제 커널 → 드라이버 → 하드웨어
드라이버 덕분에 운영체제와 응용 프로그램은 하드웨어의 복잡한 동작 방식을 몰라도 표준화된 방식으로 장치를 사용할 수 있음
운영체제는 설계 철학에 따라 여러 가지 구조를 가질 수 있다.
운영체제에서 가장 기본적이고 중요한 개념이 바로 프로세스(Process)이다.
프로세스는 컴퓨터에서 실행 중인 프로그램을 의미한다.
단순히 디스크에 저장된 정적인 프로그램 파일이 아니라, 메모리에 적재되어 CPU를 사용해 실제로 작업을 처리하는 "동적인 실행 단위"를 말한다.
운영체제로부터 CPU, 메모리(RAM), 파일, 네트워크 리소스 등 다양한 자원을 할당받아 독립적으로 실행되며, 각 프로세스는 자신만의 메모리 공간(Heap, Stack, Code, Data 영역)을 가진다.

예를 들어 보자.
크롬 브라우저를 실행하면 하나의 프로세스가 생성되고, 탭을 새로 열면 탭마다 추가적인 프로세스가 생성될 수 있다.
즉, "프로그램 단위"의 개별 실행 인스턴스가 곧 프로세스라고 할 수 있습니다.
그래서 우리가 보는 작업 관련 PID는 ProcessID이다.

위의 자신만의 메모리 공간에 대하여 가진다는 것을 알아보자.
실행할 프로그램의 명령어(기계어 코드)가 저장되는 영역
읽기 전용(Read-only) 속성을 가지므로, 실행 중에는 변경되지 않음
여러 프로세스가 같은 프로그램을 실행할 경우, 코드 영역은 공유될 수 있음
프로그램 실행 중 전역 변수(Global), 정적 변수(Static), 상수(Constant) 등이 저장됨
프로그램 시작 시 할당되고, 종료 시 해제됨
실행 중 값이 변경될 수 있음 (예: int counter = 0;)
동적 메모리 할당 영역
malloc, new 같은 함수 호출로 실행 도중 필요한 만큼 메모리를 할당받음
프로그램이 필요에 따라 크기가 늘어나거나 줄어들 수 있음
사용 후 반드시 해제(free/delete)하지 않으면 메모리 누수(memory leak) 발생
함수 호출과 지역 변수가 저장되는 영역
함수가 호출될 때마다 스택 프레임(stack frame)이 생성되고, 함수 종료 시 제거됨
LIFO(Last-In-First-Out) 구조로 동작
함수 호출이 깊어지면 스택 오버플로우(Stack Overflow)가 발생할 수 있음!
보통 메모리는 다음과 같은 형태로 구성

스택은 위에서 아래로 확장되고, 힙은 아래에서 위로 확장됨
스택과 힙이 서로 충돌하지 않도록 운영체제가 관리
운영체제는 각 프로세스의 상태와 실행 정보를 프로세스 제어 블록(PCB, Process Control Block)이라는 자료구조에 저장하여 관리한다.
PCB에는 다음과 같은 정보가 포함된다.
이러한 정보 덕분에 운영체제는 프로세스를 중단했다가 다시 이어서 실행하는 문맥 교환(Context Switching)을 수행할 수 있다.
운영체제는 각 프로세스의 상태와 정보를 프로세스 제어 블록(PCB)에서 저장하여 관리함
프로세스가 실행되기 위해서는 최소한 다음과 같은 자원이 필요하다.
프로세스는 프로세스를 실행하기 위한 최소 단위인 "태스크"를 실행하기 위하여 다음과 같은 자원을 필요로 한다
1. cpu 점유
2. 메모리(memory)
3. files
4. 입출력 장치(I/O devieces)

이처럼 사용자가 직접 실행하는 애플리케이션부터 시스템 내부의 관리 작업까지 모두 프로세스 형태로 운영체제에 의해 관리됩니다.
그러면 우리가 자주 들어본 프로그램이란 무엇일까??
프로그램은 특정 작업을 수행하기 위해 컴퓨터 언어로 작성된 명령어들의 정적인 집합이다.
즉, "아직 실행되지 않은" 상태에서 단순히 저장되어 있는 코드 덩어리를 의미한다.
그러면 스레드란,
스레드는 프로세스 내에서 실행되는 "가장 작은 실행 단위"이다.
하나의 프로세스 안에는 여러 개의 스레드가 존재할 수 있으며, 이들은 같은 메모리 공간을 공유한다! (멀티스레드)
스레드는 프로세스가 가진 코드, 데이터, 힙 영역을 당연히 이것도 공유한다

각 스레드는 독립적인 스택의 영역을 가진다.
프로세스 자원 공유 덕분에 스레드 간의 전환 비용이 낮다

멀티 스레드 프로그래밍을 사용하면 여러 작업을 병렬로 실행할 수 있어서 성능을 향상 시킬 수 있다!
예: 웹 브라우저의 멀티 스레드 처리( UI 렌더링 / 파일 다운로드 / 사용자 입력 처리)
→ 각각 별도의 스레드에서 동시에 실행되어 빠른 응답성을 제공한다.

같은 메모리를 공유하기 때문에 효율적일 수는 있지만, 동기화 문제(레이스 컨디션, 데드락 등)이 발생할 수 있다!
하나의 스레드가 문제를 일으키면 전체 프로세스에 영향을 줠 수 있다는 것이 가장 큰 단점이다.
위의 내용들을 다시 한 번 정리해보자.


그래서
프로세스는 독립성과 안정성, 그리고 스레드는 효율성과 속도를 강조한다!
cpu 집약적인 작업(ex. 이미지 처리, 연산 등)은 멀티프로세스가 확실히 유리하다.
하지만 빠른 응답성과 동시성 처리(ex. Web Server, 게임 엔진, 채팅 등)은 멀티스레드가 적합하다
실제 시스템에서는 상황에 따라 멀티 프로세스 + 멀티 스레드 혼합 구조를 채택한다.
이제 프로그램을 다루다보면 자주 마주칠 수 있는 것 중 하나인 인터럽트에 대하여 알아보자.
인터럽트란 무엇일까?
인터럽트의 의미 자체는 "CPU의 작업을 방해하는 신호"이다.
왜 CPU의 작업을 방해해야는지에 대하여 알아보자.
만약 인터럽트가 존재하지 않는 환경이라면,

CPU는 입출력 장치가 작업을 끝낼 때까지 무한 대기(busy waiting)를 해야 한다. 이는 CPU 자원을 낭비하는 비효율적인 방식이다.
하지만 하드웨어 인터럽트가 존재한다면??

위의 그림처럼 CPU는 다른 작업을 수행하다가도 입출력 장치가 완료 신호를 보냈을 때만 대응하면 된다.
즉, 대기 대신 다른 작업을 수행할 수 있는 구조로 변화할 수 있다.
인터럽트는 주로 크게
동기 인터럽트 / 비동기 인터럽트
두 가지로 구분 될 수 있다.

CPU가 명령어를 실행하는 과정에서 발생
예: 잘못된 메모리 접근(페이지 폴트), 0으로 나누기 예외(Divide by Zero)
CPU와 독립적으로 외부 장치가 발생시키는 이벤트
예: 키보드 입력, 디스크 입출력 완료 신호, 네트워크 패킷 도착
인터럽트를 발생시킬 수 있는 주체는 굉장히 다양하다.
대표적으로 입출력 장치(I/O Device)들이 있으며, 키보드, 마우스, 디스크, 네트워크 카드 등 거의 모든 장치가 인터럽트 요청을 보낼 수 있다.

각 인터럽트 요청은 고유한 메모리 주소(인터럽트 벡터)에 연결되어 있으며, CPU는 이를 참조하여 해당 장치에 맞는 인터럽트 서비스 루틴(ISR, Interrupt Service Routine)을 실행한다.
이 중에서 하드웨어 인터럽트에 대하여 더 깊게 알아보자.
1) 인터럽트 요청: 입출력 장치가 CPU에 인터럽트 신호를 보냄
2) 인터럽트 감지: CPU는 실행 사이클이 끝나고 명령어를 인출하기 전, 항상 인터럽트 발생 여부를 확인
3) 인터럽트 가능 여부 확인: CPU는 인터럽트 플래그를 체크하여 현재 인터럽트를 받아들일 수 있는지 판단
4) 현재 작업 백업: 인터럽트를 받아들일 수 있다면, CPU는 현재 레지스터 값과 프로그램 카운터(PC)를 백업
5) 인터럽트 서비스 루틴 실행: CPU는 인터럽트 벡터를 참조하여 해당 장치의 ISR을 실행
6) 원래 작업 복구: ISR이 끝나면 백업된 레지스터와 PC 값을 복원하여 이전 작업을 재개

즉, 인터럽트가 발생하면 위의 그림처럼
CPU의 일반적인 명령어 사이클(fetch-decode-execute)에 추가적인 인터럽트 사이클이 삽입된다.
이 덕분에 CPU는 실행 중이던 프로그램을 잠시 멈추고(interrupt), 인터럽트 처리를 수행한 뒤, 다시 원래 프로그램으로 돌아올 수 있다.
현대의 운영체제는 여러 프로그램과 입출력 장치를 동시에 다루어야 한다.
인터럽트는 실행 중이던 프로그램을 잠시 중단시키고, 긴급하게 처리해야 할 요청(예: 네트워크 패킷 도착)을 먼저 처리하게 한다.
이 메커니즘 덕분에 멀티태스킹(Multitasking)과 멀티프로세싱(Multiprocessing)이 안정적으로 구현된다.
여태까지의 내용이 하드웨어 비동기 인터럽트의 요청이었다면,
이제는 동기 인터럽트에 대하여 알아보자.
동기 인터럽트는 반면에 "예외"라고 불리기도한다.
내부 요인에 의하여 발생하는것으로,
CPU가 명령어를 실행하는 도중 옿류나 특수 상황을 만나면 발생한다!
예: 0으로 나누기(Divide by Zero), 잘못된 메모리 접근(Page Fault), 산술 오버플로우
동기적인 인터럽트라고 불리면서,
cpu가 특정 명령어를 실행하는 과정에서 "즉시"발생한다.
비교 정리
| 구분 | 인터럽트 (Interrupt) | 예외 (Exception) |
|---|---|---|
| 발생 원인 | 외부 장치(I/O, 타이머 등) | CPU 내부 실행 중 오류 |
| 발생 시점 | 비동기 (언제든 발생 가능) | 동기 (명령어 실행 중) |
| 목적 | 외부 이벤트 처리 | 오류 처리 및 복구 |
| 예시 | 키보드 입력, 디스크 완료 | 0으로 나누기, 페이지 폴트 |
| 처리 루틴 | ISR (Interrupt Service Routine) | Exception Handler |
REF