Home Operating System
Post
Cancel

Operating System

Operating System

목차

운영체제의 역할

운영체제는 하드웨어와 애플리케이션 사이에서 CPU, 메모리, 디스크, 네트워크 같은 자원을 관리하는 시스템 소프트웨어다. 애플리케이션이 하드웨어를 직접 만지게 두면 보안, 안정성, 동시 실행이 모두 어려워진다. 그래서 운영체제는 자원 접근을 중재하고, 프로그램들이 안전하게 실행될 수 있는 공통 규칙을 제공한다.

1
2
3
4
애플리케이션
-> 운영체제가 제공하는 인터페이스
-> 커널
-> 하드웨어

운영체제를 “프로그램 실행 관리자”로만 보면 좁다. 운영체제는 실행, 보호, 공유, 추상화를 모두 담당한다.

1
2
3
4
실행      프로그램을 프로세스로 실행
보호      사용자와 프로세스 사이 권한 분리
공유      CPU와 메모리 같은 자원을 여러 프로그램에 배분
추상화    파일, 프로세스, socket 같은 공통 모델 제공

예를 들어 사용자는 디스크의 물리 섹터를 직접 다루지 않고 파일이라는 추상화를 사용한다. 네트워크 카드의 전기 신호를 직접 다루지 않고 socket이라는 추상화를 사용한다. 이 추상화 덕분에 애플리케이션은 복잡한 하드웨어 차이를 덜 의식하고 동작할 수 있다.

프로세스와 스레드

프로그램은 디스크에 저장된 실행 파일이고, 프로세스는 실행 중인 프로그램의 인스턴스다.

1
2
프로그램 = 실행 가능한 파일
프로세스 = 메모리 위에서 실행 중인 프로그램

터미널에서 ls를 실행하면 디스크에 있던 ls 실행 파일이 프로세스로 올라와 실행된다.

1
2
3
/bin/ls 파일
-> 실행
-> ls 프로세스

스레드는 프로세스 안의 실행 흐름이다. 하나의 프로세스는 하나 이상의 스레드를 가질 수 있다. 스레드들은 같은 프로세스의 메모리 공간을 공유하지만, 각자 실행 위치와 스택을 가진다.

1
2
3
4
프로세스
├── 스레드 1
├── 스레드 2
└── 스레드 3

스레드가 필요한 이유는 한 프로그램 안에서 여러 일을 동시에 진행하기 위해서다. 예를 들어 웹 서버는 요청을 받는 동안 다른 요청도 처리해야 한다. GUI 프로그램은 파일을 다운로드하는 동안 화면도 멈추지 않아야 한다.

1
2
3
4
웹 서버 프로세스
-> 요청 A 처리
-> 요청 B 처리
-> 로그 기록

다만 스레드가 메모리를 공유하기 때문에 동시에 같은 데이터를 바꾸면 race condition이 생길 수 있다. 그래서 lock, mutex, atomic operation 같은 동기화가 필요하다.

메모리 관리

메모리는 프로그램이 실행 중 데이터를 저장하는 공간이다. 운영체제는 각 프로세스가 자기만의 메모리를 가진 것처럼 보이게 만든다. 이것을 가상 메모리라고 한다.

1
2
3
프로세스 A의 가상 주소 공간
프로세스 B의 가상 주소 공간
-> 운영체제와 MMU가 실제 RAM에 매핑

가상 메모리가 필요한 이유는 세 가지다.

1
2
3
격리    한 프로세스가 다른 프로세스 메모리를 함부로 건드리지 못하게 함
편의    프로그램이 연속된 큰 메모리를 가진 것처럼 사용하게 함
효율    실제 RAM과 디스크 swap을 조합해 관리 가능

프로세스의 메모리는 보통 여러 영역으로 나뉜다.

1
2
3
4
code    실행 명령
data    전역 변수
heap    동적 할당 메모리
stack   함수 호출과 지역 변수

예를 들어 함수 호출이 깊어지면 stack을 사용하고, 동적으로 만든 객체는 heap에 저장된다. 메모리 누수는 더 이상 필요 없는 heap 메모리를 계속 잡고 있을 때 생긴다.

파일 시스템

파일 시스템은 데이터를 파일과 디렉토리로 저장하고 찾기 위한 운영체제의 구조다. 애플리케이션은 디스크의 물리 위치를 직접 다루지 않고 경로와 파일 이름으로 데이터를 다룬다.

1
/home/shin/app.log

파일은 단순한 문서만 의미하지 않는다. 리눅스에서는 장치, 파이프, socket 같은 대상도 파일처럼 다루는 경우가 많다. 프로세스는 file descriptor라는 번호를 통해 열린 파일과 I/O 대상을 다룬다.

1
2
3
0  stdin
1  stdout
2  stderr

file descriptor가 모든 I/O의 기본이 되는 이유는 운영체제가 다양한 입출력 대상을 같은 방식으로 다루게 해주기 때문이다.

1
2
3
4
5
6
일반 파일
터미널
파이프
socket
장치 파일
-> file descriptor로 접근

예를 들어 cat file.txt는 파일을 열고 file descriptor를 통해 읽는다. cat < file.txt는 셸이 파일을 열어 stdin에 연결한다. 둘 다 결국 열린 파일 또는 입력 스트림을 읽는 구조다.

시스템 콜과 커널 모드

시스템 콜은 애플리케이션이 운영체제 커널에게 일을 요청하는 공식 통로다. 파일 열기, 프로세스 만들기, 네트워크 전송, 메모리 할당 같은 작업은 커널의 도움이 필요하다.

1
2
3
4
애플리케이션
-> system call
-> kernel
-> hardware/resource

시스템 콜이 필요한 이유는 애플리케이션이 하드웨어와 핵심 자원에 직접 접근하면 위험하기 때문이다. 운영체제는 권한을 검사하고, 자원을 안전하게 조작한 뒤 결과를 돌려준다.

1
2
3
4
5
open()   파일 열기
read()   데이터 읽기
write()  데이터 쓰기
fork()   프로세스 생성
exec()   프로그램 실행

커널 모드와 사용자 모드는 CPU 권한 수준을 나눈 것이다. 일반 애플리케이션은 사용자 모드에서 실행되고, 운영체제 커널은 커널 모드에서 실행된다.

1
2
user mode    일반 프로그램 실행
kernel mode  운영체제 핵심 작업 수행

이렇게 나누는 이유는 보호 때문이다. 버그가 있는 일반 프로그램이 디스크 전체를 망가뜨리거나 다른 프로세스 메모리를 읽지 못하게 막아야 한다.

동시성과 스케줄링

동시성은 여러 작업이 같은 시간대에 진행되는 것처럼 다루는 개념이다. CPU 코어가 하나뿐이어도 운영체제는 여러 프로세스를 빠르게 번갈아 실행해 동시에 실행되는 것처럼 보이게 할 수 있다.

1
2
3
시간 1: 프로세스 A 실행
시간 2: 프로세스 B 실행
시간 3: 프로세스 A 실행

스케줄링은 어떤 프로세스나 스레드에게 CPU를 줄지 결정하는 일이다. 운영체제 스케줄러는 우선순위, 대기 시간, CPU 사용량 등을 고려해 실행 대상을 고른다.

context switching은 CPU가 실행 중인 작업을 바꾸는 과정이다. 이때 현재 프로세스의 레지스터, 실행 위치 같은 상태를 저장하고 다른 프로세스의 상태를 복원해야 한다. 그래서 context switching은 비용이 든다.

1
2
3
프로세스 A 상태 저장
-> 프로세스 B 상태 복원
-> 프로세스 B 실행

스레드나 프로세스를 무조건 많이 만들면 빨라지는 것이 아니다. 동시성이 늘어나면 전환 비용, lock 경쟁, 메모리 사용량도 함께 증가한다.

I/O와 인터럽트

I/O는 입력과 출력이다. 디스크 읽기, 네트워크 수신, 키보드 입력, 터미널 출력 모두 I/O다. CPU 속도에 비해 I/O는 느린 경우가 많기 때문에 운영체제는 I/O 대기와 CPU 실행을 효율적으로 섞어야 한다.

1
2
3
4
프로세스가 디스크 읽기 요청
-> 커널이 디스크 I/O 시작
-> 프로세스는 대기 상태
-> I/O 완료 후 다시 실행 가능

인터럽트는 하드웨어나 시스템이 CPU에게 “처리할 일이 생겼다”고 알리는 신호다. 예를 들어 네트워크 카드에 패킷이 도착하거나 디스크 I/O가 끝나면 인터럽트가 발생할 수 있다.

1
2
3
4
장치에서 이벤트 발생
-> interrupt
-> 커널의 interrupt handler 실행
-> 대기 중인 프로세스 깨움

서버 프로그램에서 select, epoll 같은 I/O multiplexing을 사용하는 이유도 여기에 연결된다. 많은 socket 중 준비된 것만 처리하면, I/O가 올 때까지 CPU를 낭비하며 기다리지 않아도 된다.

1
2
3
여러 socket 감시
-> 준비된 socket만 처리
-> 많은 연결을 효율적으로 관리
This post is licensed under CC BY 4.0 by the author.