운영체제·프로세스·스레드·CPU 스케줄링과 UNIX/Linux 기초
운영체제는 자원을 관리하고 실행 환경을 제공한다. 프로세스·스레드의 상태와 공유 자원, CPU 스케줄링의 선택 기준과 시간 계산, UNIX/Linux의 기본 명령·환경 변수·셸 스크립트를 함께 이해한다.
운영체제는 자원을 관리하고 실행 환경을 제공한다. 프로세스·스레드의 상태와 공유 자원, CPU 스케줄링의 선택 기준과 시간 계산, UNIX/Linux의 기본 명령·환경 변수·셸 스크립트를 함께 이해한다.
그림으로 확인하기

운영체제의 목적과 구조
운영체제는 자원 관리자이자 추상화 제공자다
운영체제는 하드웨어를 직접 사용하는 여러 프로그램 사이에서 자원을 배분하고 충돌을 통제한다. 대표 기능은 다음과 같다.
| 관리 영역 | 운영체제가 제공하는 기능 | 학습 포인트 |
|---|---|---|
| 프로세스·CPU | 생성·종료, 상태 관리, CPU 스케줄링, 프로세스 간 통신 | 어떤 실행 단위가 언제 CPU를 쓰는지 관리한다. |
| 메모리 | 주소 공간, 할당·회수, 보호, 가상 메모리 | 각 프로세스가 독립된 주소 공간을 가진 것처럼 보이게 한다. |
| 파일 시스템 | 파일·디렉터리, 이름, 권한, 저장 장치 매핑 | 바이트 저장 공간을 이름과 계층 구조로 추상화한다. |
| 장치·입출력 | 장치 드라이버, 버퍼링, 인터럽트 처리 | 응용 프로그램이 장치별 세부 제어를 직접 하지 않게 한다. |
| 보호·보안 | 사용자 식별, 접근 제어, 권한 분리, 감사 | 한 프로그램의 오류나 권한 남용이 전체 시스템으로 번지는 것을 줄인다. |
| 네트워크·서비스 | 통신 인터페이스, 자원 공유, 오류 보고 | 하드웨어와 응용 사이에 일관된 인터페이스를 제공한다. |
운영체제의 목표를 “처리량 최대화” 하나로만 설명하면 부족하다. 처리량, 응답성, 공정성, 자원 이용률, 신뢰성, 보호, 마감 시간 준수는 서로 충돌할 수 있다. 대화형 시스템은 빠른 응답이 중요하고, 배치 시스템은 처리량이 중요하며, 실시간 시스템은 평균 속도보다 정해진 마감 시간 안에 완료할 수 있는 예측 가능성이 중요하다.
커널·시스템 프로그램·사용자 인터페이스
커널(kernel)은 가장 높은 권한에서 CPU·메모리·장치·파일 시스템과 같은 핵심 자원을 관리한다. 시스템 프로그램은 파일 조작, 프로세스 관찰, 컴파일, 네트워크 설정처럼 운영체제 기능을 이용하기 쉽게 제공한다. 셸(shell)이나 GUI는 사용자의 요청을 해석해 프로그램과 운영체제 기능을 호출하는 인터페이스다.
셸은 커널이 아니다. 터미널 창을 닫는다고 커널이 종료되지 않으며, 셸에서 실행한 명령은 셸 내장 명령일 수도 있고 별도의 실행 파일일 수도 있다.
사용자 모드·커널 모드와 시스템 호출
CPU는 일반 응용 프로그램이 특권 명령을 임의로 실행하지 못하도록 권한 모드를 구분한다.
- 사용자 모드(user mode): 응용 프로그램이 제한된 권한으로 실행된다.
- 커널 모드(kernel mode): 커널이 장치 제어, 주소 공간 변경 같은 특권 작업을 수행할 수 있다.
응용 프로그램이 파일을 읽거나 새 프로세스를 만들려면 시스템 호출(system call)을 통해 커널에 요청한다. 일반적인 흐름은 다음과 같다.
- 사용자 프로그램이 라이브러리 함수나 시스템 호출 래퍼를 호출한다.
- 정해진 시스템 호출 번호와 인자를 준비한다.
- 특수 명령을 통해 커널의 진입점으로 제어를 넘긴다.
- CPU가 커널 모드로 전환되고 커널이 권한·인자·자원 상태를 확인한다.
- 커널이 작업을 수행하거나 오류를 기록한다.
- 결과를 사용자 프로그램에 돌려주고 사용자 모드로 복귀한다.
모든 라이브러리 함수가 시스템 호출은 아니다. 예를 들어 메모리에 있는 문자열 길이를 계산하는 함수는 사용자 모드에서 끝날 수 있고, 파일 읽기 함수는 내부에서 시스템 호출을 이용할 수 있다.
인터럽트·예외·시스템 호출
| 사건 | 발생 원인 | 동기성 | 예 |
|---|---|---|---|
| 하드웨어 인터럽트 | 현재 명령과 독립적인 외부 장치 사건 | 비동기 | 디스크 입출력 완료, 타이머 만료 |
| 예외 | 현재 실행 중인 명령에서 발생 | 동기 | 0으로 나누기, 페이지 부재, 잘못된 명령 |
| 시스템 호출 | 프로그램이 의도적으로 커널 서비스를 요청 | 동기 | 파일 읽기, 프로세스 생성, 네트워크 송수신 |
타이머 인터럽트는 선점형 스케줄링이 실행 시간을 통제하는 중요한 수단이다. 다만 인터럽트나 시스템 호출이 발생했다고 해서 항상 다른 프로세스로 문맥 교환되는 것은 아니다. 커널 처리가 끝난 뒤 같은 스레드가 계속 실행될 수도 있다.
운영체제 구조의 대표 형태
| 구조 | 핵심 원리 | 장점 | 주의점 |
|---|---|---|---|
| 단일체형 커널 | 파일·메모리·장치 등 많은 서비스를 커널 공간에서 수행 | 호출 경로가 짧아 성능상 유리할 수 있다. | 커널 내부 결함의 영향 범위가 커질 수 있다. |
| 계층형 | 상위 계층이 하위 계층의 기능을 이용 | 책임과 인터페이스를 단계적으로 나누기 쉽다. | 엄격한 계층 경계가 성능이나 설계를 제약할 수 있다. |
| 마이크로커널 | 최소 기능만 커널에 두고 다른 서비스를 사용자 공간 서버로 분리 | 격리·확장·교체에 유리할 수 있다. | 서비스 간 메시지 전달 비용이 증가할 수 있다. |
| 모듈형·혼합형 | 핵심 커널에 필요 기능을 모듈로 추가하거나 여러 구조를 혼합 | 성능과 확장성을 절충한다. | 실제 시스템은 교과서 분류 하나에 완전히 맞지 않을 수 있다. |
Linux는 일반적으로 모듈을 적재할 수 있는 단일체형 커널로 설명한다. “단일체형”은 모든 기능이 하나의 소스 파일이라는 뜻이 아니라, 많은 핵심 서비스가 같은 커널 주소 공간에서 실행된다는 뜻이다.
처리 방식 용어
| 용어 | 판단 기준 | 핵심 설명 |
|---|---|---|
| 일괄 처리 | 사용자 상호작용 없이 작업을 모아 처리하는가? | 처리량 중심이며 작업 완료까지 기다릴 수 있다. |
| 다중 프로그래밍 | 한 작업이 입출력을 기다릴 때 다른 작업이 CPU를 쓰는가? | CPU 유휴 시간을 줄이기 위해 여러 작업을 메모리에 둔다. |
| 시분할·다중 작업 | 짧은 시간 단위로 CPU를 나누어 대화형 응답을 제공하는가? | 여러 사용자가 동시에 쓰는 것처럼 보이게 한다. |
| 다중 처리 | CPU나 코어가 둘 이상인가? | 여러 실행 흐름이 물리적으로 병렬 실행될 수 있다. |
| 실시간 처리 | 마감 시간 준수가 정확성의 일부인가? | 하드 실시간은 마감 실패를 허용하기 어렵고, 소프트 실시간은 지연 시 품질이 저하된다. |
다중 프로그래밍과 다중 처리를 같은 뜻으로 보면 안 된다. 다중 프로그래밍은 한 CPU에서도 가능하지만, 다중 처리는 둘 이상의 처리 장치를 전제로 한다.
운영체제의 종류
| 운영체제 | 구분할 특징 |
|---|---|
| Windows | Microsoft의 운영체제 계열. GUI와 명령 환경을 제공 |
| UNIX | 다중 사용자·다중 작업 환경을 지원하는 운영체제 계열 |
| Linux | 유닉스 계열의 공개 커널. 배포판은 커널과 시스템 도구·패키지 등을 조합 |
| macOS | Apple의 데스크톱 운영체제로 유닉스 기반의 실행 환경 제공 |
| Android | Linux 커널을 기반으로 모바일 기기에 필요한 실행 환경과 서비스를 제공 |
GUI를 제공한다고 명령 인터페이스가 없는 것은 아니며, 공개 소스라고 사용 목적이 서버로 제한되는 것도 아니다. 운영체제와 특정 응용 프로그램은 구분한다.
프로그램·프로세스·스레드
프로그램과 프로세스
프로그램(program)은 저장 장치에 있는 실행 코드와 데이터의 집합이다. 프로세스(process)는 프로그램이 실행되어 운영체제로부터 주소 공간과 자원을 할당받은 인스턴스다. 같은 프로그램 파일을 여러 번 실행하면 서로 다른 PID와 상태를 가진 여러 프로세스가 생길 수 있다.
프로세스는 일반적으로 다음 정보를 가진다.
- 코드·데이터·힙·스택으로 구성되는 가상 주소 공간
- 프로세스 식별자(PID)와 부모 식별자(PPID)
- 현재 상태와 우선순위
- 프로그램 카운터와 CPU 레지스터를 포함한 실행 문맥
- 열린 파일과 입출력 상태
- 사용자·그룹 자격 정보와 접근 권한
- 사용한 CPU 시간 등 회계 정보
운영체제가 프로세스를 관리하기 위해 유지하는 자료 구조를 프로세스 제어 블록(PCB)이라고 부른다. 실제 운영체제마다 필드와 구현은 다르지만, 시험에서는 상태·식별자·레지스터·스케줄링·메모리·입출력 정보를 묶어 관리한다는 점을 본다.
프로세스의 주소 공간
가상 주소 공간의 대표 구역은 다음과 같다.
| 구역 | 주된 내용 | 특징 |
|---|---|---|
| 코드(text) | 실행 명령 | 읽기 전용으로 공유될 수 있다. |
| 데이터 | 초기값이 있는 전역·정적 데이터 | 프로세스 수명 동안 유지된다. |
| BSS | 초기값이 없거나 0으로 초기화되는 전역·정적 데이터 | 실행 파일에서 큰 0 영역을 모두 저장하지 않아도 된다. |
| 힙(heap) | 동적 할당 객체 | 일반적으로 실행 중 확장·축소될 수 있다. |
| 스택(stack) | 함수 호출 프레임, 자동 지역 변수, 반환 정보 | 스레드마다 별도 스택을 가진다. |
프로세스마다 가상 주소 공간이 분리되어도 동일한 실행 코드나 공유 라이브러리의 물리 페이지가 읽기 전용으로 공유될 수 있다. 따라서 “주소 공간 분리”와 “모든 물리 메모리 페이지가 반드시 중복”은 같은 말이 아니다.
스레드가 공유하는 것과 따로 가지는 것
스레드(thread)는 프로세스 안의 실행 흐름이다. 현대 운영체제에서는 CPU 스케줄러가 실제로 커널 스레드나 이에 대응하는 실행 단위를 선택하는 경우가 많다. 교과서에서 “프로세스 스케줄링”이라고 해도 실제 구현에서는 스레드가 스케줄 단위일 수 있다.
| 같은 프로세스의 스레드가 주로 공유 | 스레드마다 주로 독립 |
|---|---|
| 코드와 전역 데이터 | 프로그램 카운터 |
| 힙과 주소 공간 | CPU 레지스터 문맥 |
| 열린 파일 기술자 | 스택과 함수 호출 프레임 |
| 현재 작업 디렉터리와 프로세스 자격 정보 | 스레드 식별자 |
| 프로세스 단위 자원 제한과 신호 처리 설정 | 스레드별 신호 마스크·스레드 지역 저장소 |
스레드는 공유 메모리를 별도 복사 없이 사용할 수 있어 통신이 빠르지만, 둘 이상의 스레드가 같은 가변 데이터에 동시에 접근하면 경쟁 상태가 생길 수 있다. 이 문제의 세부 해결은 다음 이론인 동기화와 교착상태에서 다룬다.
프로세스와 스레드 비교
| 기준 | 프로세스 | 스레드 |
|---|---|---|
| 기본 역할 | 자원 소유와 보호의 경계 | 실행과 스케줄링의 흐름 |
| 주소 공간 | 다른 프로세스와 기본적으로 분리 | 같은 프로세스의 다른 스레드와 공유 |
| 통신 | IPC가 필요하며 상대적으로 복잡할 수 있다. | 공유 메모리를 직접 이용할 수 있다. |
| 장애 영향 | 한 프로세스의 일반 오류가 다른 프로세스 주소 공간으로 직접 번지기 어렵다. | 잘못된 포인터나 공유 상태 오류가 프로세스 전체에 영향을 줄 수 있다. |
| 전환 비용 | 주소 공간 전환과 캐시·TLB 영향이 더 클 수 있다. | 같은 프로세스 안의 전환은 상대적으로 가벼울 수 있지만 비용이 0은 아니다. |
“스레드는 항상 프로세스보다 빠르다”라고 단정하면 안 된다. 작업 크기, 동기화 비용, 캐시 경쟁, 운영체제 구현, CPU 코어 수에 따라 실제 성능은 달라진다.
사용자 수준 스레드와 커널 수준 스레드
- 사용자 수준 스레드는 사용자 공간의 라이브러리가 생성·전환을 관리한다. 커널 호출 없이 전환할 수 있어 가벼울 수 있지만, 커널이 개별 스레드를 모르는 다대일 구조에서는 하나의 블로킹 호출이 전체 프로세스를 멈추게 하거나 여러 코어를 활용하기 어렵다.
- 커널 수준 스레드는 커널이 각각을 인식하고 스케줄한다. 블로킹과 병렬 실행을 개별 스레드 단위로 관리할 수 있지만 생성·전환에 커널 관여 비용이 든다.
대표 매핑은 다대일, 일대일, 다대다다. 현대 범용 운영체제의 일반적인 POSIX 스레드는 일대일 매핑으로 구현되는 경우가 많지만, 언어 런타임의 경량 태스크가 항상 커널 스레드 하나와 일대일인 것은 아니다.
동시성과 병렬성
단일 코어에서도 여러 작업을 번갈아 실행하면 진행 구간이 겹치는 동시성(concurrency)을 만들 수 있다. 둘 이상의 코어에서 서로 다른 작업을 같은 순간 실행하면 병렬성(parallelism)이 생긴다.
- 동시성은 여러 작업을 조정하는 구조의 성질이다.
- 병렬성은 실제로 동시에 계산하는 실행 방식이다.
- 동시 프로그램이 반드시 병렬 실행되는 것은 아니며, 병렬 실행에는 동시성 제어가 필요할 수 있다.
프로세스 상태·스케줄러·문맥 교환
기본 5상태 모델
| 상태 | 의미 | CPU 사용 여부 |
|---|---|---|
| 생성(New) | 프로세스가 만들어지고 초기화되는 중 | 사용하지 않음 |
| 준비(Ready) | 실행에 필요한 자원은 갖추었고 CPU 배정만 기다림 | 사용하지 않음 |
| 실행(Running) | CPU에서 명령을 수행 중 | 사용함 |
| 대기·블록(Waiting/Blocked) | 입출력 완료나 사건 발생을 기다림 | 사용하지 않음 |
| 종료(Terminated) | 실행을 끝내고 정리되는 상태 | 사용하지 않음 |
준비와 대기는 둘 다 CPU를 사용하지 않지만 기다리는 대상이 다르다. 준비 상태는 CPU만 주어지면 실행할 수 있고, 대기 상태는 CPU가 주어져도 필요한 사건이 끝나지 않아 실행할 수 없다.
상태 전이
| 전이 | 원인 | 핵심 해석 |
|---|---|---|
| 생성 → 준비 | 입장 승인, 초기화 완료 | 실행 후보가 준비 큐에 들어간다. |
| 준비 → 실행 | 디스패치 | 스케줄러가 선택한 대상을 디스패처가 CPU에 올린다. |
| 실행 → 준비 | 시간 할당량 만료, 더 높은 우선순위 도착, 자발적 양보 | 작업은 끝나지 않았지만 CPU를 빼앗기거나 반납한다. |
| 실행 → 대기 | 입출력 요청, 잠금·사건 대기 | CPU 이외 조건이 충족될 때까지 실행할 수 없다. |
| 대기 → 준비 | 입출력 완료, 사건·신호 도착 | 곧바로 실행이 아니라 다시 CPU 선택을 기다린다. |
| 실행 → 종료 | 정상 종료, 처리되지 않은 치명 오류, 종료 신호 | 자원과 종료 상태를 정리한다. |
기본 모델에서 대기 → 실행은 직접 전이가 아니다. 사건이 완료되면 먼저 준비 상태가 되고, 스케줄러의 선택을 받아야 실행 상태가 된다.
중단 상태가 포함된 확장 모델
메모리 압박이나 운영체제 정책으로 프로세스를 주기억장치에서 내보내면 중단(suspended) 상태를 추가할 수 있다.
- 준비 중단(Ready Suspended): 실행할 준비는 되었지만 주기억장치 밖에 있어 바로 CPU를 받을 수 없다.
- 대기 중단(Blocked Suspended): 사건도 기다리고 있고 주기억장치 밖에 있다.
대기 중단 중 사건이 완료되면 준비 중단으로 이동할 수 있다. 중단 상태는 단순한 CPU 선점과 다르며, 장기적으로 메모리 적재 상태까지 바꾸는 중기 스케줄링과 관련된다.
장기·중기·단기 스케줄러와 디스패처
| 구성 요소 | 선택 대상·역할 | 호출 빈도 |
|---|---|---|
| 장기 스케줄러 | 작업 풀에서 메모리와 준비 큐로 들일 작업을 결정 | 비교적 낮음 |
| 중기 스케줄러 | 프로세스를 중단·재개하여 메모리 부하와 다중 프로그래밍 정도를 조절 | 필요할 때 |
| 단기 스케줄러 | 준비 큐에서 다음 CPU 실행 대상을 선택 | 매우 높음 |
| 디스패처 | 문맥 복원, 모드 전환, 실행 위치 이동을 수행해 실제 CPU를 넘김 | 선택 때마다 |
스케줄러는 “누구를 실행할지”를 결정하는 정책이고, 디스패처는 그 결정을 실제 상태 전환으로 수행하는 기구다. 선택에서 실제 실행 시작까지 걸리는 시간을 디스패치 지연이라고 한다.
문맥 교환
문맥 교환(context switch)은 현재 실행 단위의 CPU 상태를 저장하고 다른 실행 단위의 상태를 복원하는 과정이다. 보통 다음 정보가 관련된다.
- 프로그램 카운터와 스택 포인터
- 일반 레지스터와 상태 레지스터
- 스케줄링 상태
- 필요하면 주소 공간·메모리 관리 문맥
문맥 교환 자체는 사용자 작업을 직접 진전시키지 않는 오버헤드다. 주소 공간을 바꾸는 프로세스 전환은 같은 프로세스 내부의 스레드 전환보다 TLB·캐시 등에 더 큰 영향을 줄 수 있지만, 스레드 전환도 레지스터와 스택 문맥을 바꾸므로 비용이 0은 아니다.
모드 전환과 문맥 교환을 구분한다.
- 시스템 호출로 같은 스레드가 사용자 모드에서 커널 모드로 갔다가 돌아오면 모드 전환은 있지만 실행 주체가 바뀌지 않을 수 있다.
- 스케줄러가 다른 스레드를 선택하면 문맥 교환이 일어난다.
- 문맥 교환 과정에는 커널 모드 진입이 포함될 수 있지만 두 용어는 동의어가 아니다.
CPU 스케줄링
CPU 버스트와 입출력 버스트
프로세스는 CPU 계산을 수행하는 CPU 버스트와 입출력을 기다리는 I/O 버스트를 번갈아 가질 수 있다. CPU 집약 작업은 긴 CPU 버스트가 많고, I/O 집약 작업은 비교적 짧은 CPU 버스트 뒤에 자주 대기 상태로 간다. 스케줄러는 준비 큐에 있는 실행 가능한 대상을 고르며, 대기 큐의 프로세스를 CPU에 바로 배정하지 않는다.
평가 지표와 계산식
프로세스 i에 대해 다음 기호를 사용한다.
- 도착 시각:
Aᵢ - 첫 실행 시각:
Sᵢ - 완료 시각:
Cᵢ - CPU 서비스 시간 또는 총 버스트:
Bᵢ
단일 CPU, 문맥 교환 비용 0, 문제에 주어진 버스트를 전체 CPU 서비스 시간으로 보는 기본 계산에서는 다음 식을 쓴다.
반환 시간(Turnaround Time) = Cᵢ - Aᵢ
대기 시간(Waiting Time) = 반환 시간 - Bᵢ
응답 시간(Response Time) = Sᵢ - Aᵢ
| 지표 | 바람직한 방향 | 의미 |
|---|---|---|
| CPU 이용률 | 높음 | 전체 시간 중 CPU가 유용한 일을 한 비율 |
| 처리량 | 높음 | 단위 시간에 완료한 작업 수 |
| 반환 시간 | 낮음 | 제출·도착부터 완료까지 걸린 전체 시간 |
| 대기 시간 | 낮음 | 준비 큐에서 CPU를 기다린 총시간 |
| 응답 시간 | 낮음 | 요청·도착부터 첫 반응 또는 첫 CPU 실행까지의 시간 |
| 공정성 | 상황에 맞게 보장 | 특정 작업이 무기한 배제되지 않게 함 |
| 마감 준수 | 실시간 시스템에서 중요 | 정해진 기한 안에 작업을 완료함 |
응답 시간은 첫 실행까지만 본다. 한 번 빨리 실행되었다가 늦게 끝난 프로세스는 응답 시간은 짧고 반환 시간은 길 수 있다.
선점형과 비선점형
- 비선점형: 실행 중인 프로세스가 종료되거나 대기 상태로 들어가거나 자발적으로 CPU를 반납할 때까지 계속 실행한다.
- 선점형: 시간 할당량 만료, 더 높은 우선순위 작업 도착, 더 짧은 남은 시간 작업 도착 등의 조건에서 운영체제가 CPU를 회수할 수 있다.
선점형은 대화형 응답성과 긴급 작업 처리에 유리하지만 문맥 교환과 공유 데이터 동기화 부담이 커질 수 있다. 비선점형은 흐름이 단순하지만 긴 작업이 CPU를 오래 점유하면 짧은 작업의 응답이 나빠질 수 있다.
우선순위가 있다고 해서 자동으로 선점형인 것은 아니다. 우선순위 스케줄링은 선점형과 비선점형 모두로 설계할 수 있다.
대표 알고리즘 비교
| 알고리즘 | 기본 선점 여부 | 다음 대상 선택 기준 | 대표 장점 | 대표 한계 |
|---|---|---|---|---|
| FCFS | 비선점 | 준비 큐 도착 순서 | 단순하고 도착 순서가 명확하다. | 긴 작업 뒤 짧은 작업이 기다리는 호위 효과가 생긴다. |
| SJF | 비선점 | 준비된 작업 중 다음 CPU 버스트가 가장 짧음 | 정확한 버스트를 알면 평균 대기 시간을 줄일 수 있다. | 버스트 예측이 필요하고 긴 작업이 기아될 수 있다. |
| SRTF | 선점 | 남은 CPU 시간이 가장 짧음 | 새로 온 짧은 작업의 응답과 평균 대기를 줄일 수 있다. | 선점과 예측 부담이 있고 긴 작업이 기아될 수 있다. |
| Priority | 둘 다 가능 | 가장 높은 우선순위 | 중요·긴급 작업을 먼저 처리할 수 있다. | 낮은 우선순위 작업의 기아 가능성이 있다. |
| Round Robin | 선점 | 준비 큐를 시간 할당량만큼 순환 | 대화형 작업에 공정한 첫 응답을 제공하기 쉽다. | 할당량이 너무 작으면 문맥 교환이 증가한다. |
| HRN/HRRN | 비선점 | (대기 시간+서비스 시간)/서비스 시간 최대 | 짧은 작업과 오래 기다린 작업을 함께 고려한다. | 실행 시간 추정이 필요하고 매 선택 시 비율을 계산한다. |
| 다단계 큐 | 정책에 따라 다름 | 작업 종류별 고정 큐와 큐 간 우선순위 | 시스템·대화형·배치 등 성격별 정책을 분리한다. | 고정 큐 사이 이동이 없으면 낮은 큐가 굶을 수 있다. |
| 다단계 피드백 큐 | 대개 선점 | 실행 행동에 따라 큐와 시간 할당량을 변경 | 짧고 대화형인 작업을 우대하면서 행동에 적응한다. | 승격·강등·할당량 규칙 설계가 복잡하다. |
FCFS와 호위 효과
FCFS(First-Come, First-Served)는 먼저 도착한 작업부터 비선점으로 처리한다. 같은 시각 도착의 순서는 문제의 동률 규칙을 따라야 한다.
긴 CPU 집약 작업 뒤에 짧은 I/O 집약 작업들이 줄지어 기다리면 평균 대기와 장치 이용률이 나빠질 수 있다. 이를 호위 효과(convoy effect)라고 한다. FCFS는 기아가 잘 발생하지 않지만, 먼저 들어온 긴 작업의 영향이 크다.
SJF와 SRTF
SJF(Shortest Job First)는 현재 준비 큐에서 다음 CPU 버스트가 가장 짧은 작업을 선택하고, 선택된 작업이 끝나거나 대기할 때까지 실행하는 비선점 방식이다. 정확한 다음 CPU 버스트를 알고 있다는 전제에서는 평균 대기 시간을 최소화하는 성질이 있지만, 실제 시스템에서는 미래 버스트를 정확히 알기 어려워 과거 실행을 이용해 추정할 수 있다.
SRTF(Shortest Remaining Time First)는 SJF의 선점형이다. 새 작업이 도착했을 때 그 작업의 실행 시간이 현재 작업의 남은 시간보다 짧으면 선점할 수 있다. 원래 총 버스트가 아니라 현재 남은 시간을 비교한다.
두 방식 모두 짧은 작업이 계속 도착하면 긴 작업이 오래 기다릴 수 있다.
우선순위 스케줄링과 에이징
우선순위 스케줄링은 준비된 작업 가운데 가장 높은 우선순위를 선택한다. 숫자가 작을수록 높은지, 클수록 높은지는 시스템과 문제마다 다르므로 전제를 먼저 확인해야 한다.
낮은 우선순위 작업이 계속 밀리는 기아를 줄이기 위해 기다린 시간에 따라 우선순위를 점차 높이는 에이징(aging)을 사용할 수 있다. 에이징은 작업의 실제 서비스 시간이 짧다는 뜻이 아니라, 오래 기다린 작업을 점차 선택하기 쉽게 만드는 정책이다.
Round Robin과 시간 할당량
Round Robin(RR)은 준비 큐의 앞에서 작업을 꺼내 최대 시간 할당량(quantum)만큼 실행한다. 작업이 끝나지 않으면 준비 큐 뒤로 보낸다.
- 시간 할당량이 매우 크면 같은 조건에서 FCFS에 가까워진다.
- 시간 할당량이 작으면 첫 응답 기회는 빨라질 수 있지만 문맥 교환 횟수가 늘어난다.
- 시간 할당량보다 CPU 버스트가 짧으면 작업은 한 번에 끝날 수 있다.
- 새 작업의 도착 시각이 할당량 종료와 같을 때 큐에 넣는 순서는 문제에서 정한 규칙을 따라야 한다.
RR이 모든 작업의 반환 시간을 항상 줄이는 것은 아니다. 대화형 공정성과 응답성에 유리한 대신 완료 시간이 늘어날 수도 있다.
HRN의 응답비율
HRN(Highest Response Ratio Next)은 다음 비율이 가장 큰 작업을 비선점으로 선택한다.
응답비율 = (대기 시간 + 서비스 시간) / 서비스 시간
= 1 + 대기 시간 / 서비스 시간
서비스 시간이 짧을수록 비율이 커지기 쉽고, 같은 작업도 오래 기다릴수록 비율이 증가한다. 따라서 SJF의 짧은 작업 우대 성질을 유지하면서 긴 작업의 기아를 완화한다.
여기서 “응답비율”은 성능 지표인 응답 시간과 다른 개념이다.
다단계 큐와 다단계 피드백 큐
다단계 큐(Multilevel Queue)는 작업을 시스템·대화형·배치처럼 고정된 큐로 분류하고, 각 큐에 서로 다른 알고리즘을 적용한다. 일반적으로 프로세스는 분류된 큐 사이를 자유롭게 이동하지 않는다.
다단계 피드백 큐(Multilevel Feedback Queue)는 CPU를 오래 사용하는 작업을 낮은 큐로 내리고, 짧게 사용하거나 오래 기다린 작업을 높은 큐로 올리는 식으로 큐 이동을 허용한다. 여러 시간 할당량을 사용해 짧은·대화형 작업에 빠른 반응을 주면서 긴 작업도 처리하려는 방식이다.
“큐가 여러 개다”만으로 둘을 구분하면 안 된다. 핵심은 프로세스가 큐 사이를 이동할 수 있는가다.
계산 예제: 여섯 알고리즘 비교
다음 네 프로세스를 사용한다.
| 프로세스 | 도착 시각 | CPU 버스트 | 우선순위 |
|---|---|---|---|
| P1 | 0 | 5 | 2 |
| P2 | 1 | 3 | 1 |
| P3 | 2 | 1 | 3 |
| P4 | 4 | 2 | 2 |
계산 전제는 다음과 같다.
- 문맥 교환 시간은 0으로 본다.
- 동률이면 먼저 도착한 프로세스, 다시 동률이면 ID가 작은 프로세스를 선택한다.
- Priority는 작은 숫자가 높은 우선순위인 비선점 방식이다.
- RR의 시간 할당량은 2다.
- RR에서 새 프로세스가 할당량 종료 시각에 도착하면 새 도착을 큐에 넣은 뒤 만료된 프로세스를 뒤에 다시 넣는다.
실행 순서
FCFS : 0 P1 5 P2 8 P3 9 P4 11
SJF : 0 P1 5 P3 6 P4 8 P2 11
SRTF : 0 P1 1 P2 2 P3 3 P2 5 P4 7 P1 11
HRN : 0 P1 5 P3 6 P2 9 P4 11
Priority : 0 P1 5 P2 8 P4 10 P3 11
RR(q=2) : 0 P1 2 P2 4 P3 5 P1 7 P4 9 P2 10 P1 11
SJF와 Priority는 비선점이므로 시각 0에 P1이 선택된 뒤 더 짧거나 우선순위가 높은 프로세스가 도착해도 P1을 중단하지 않는다. SRTF에서는 시각 1에 P2가 도착했을 때 P1의 남은 시간 4보다 P2의 시간 3이 짧아 P2가 실행된다.
HRN은 시각 5에서 다음 비율을 계산한다.
| 후보 | 대기 시간 | 서비스 시간 | 응답비율 |
|---|---|---|---|
| P2 | 4 | 3 | 7/3 ≈ 2.33 |
| P3 | 3 | 1 | 4/1 = 4.00 |
| P4 | 1 | 2 | 3/2 = 1.50 |
따라서 P3가 선택된다. 시각 6에서는 P2의 비율이 8/3 ≈ 2.67, P4의 비율이 4/2 = 2.00이므로 P2가 선택된다.
평균 지표
| 알고리즘 | 평균 대기 시간 | 평균 반환 시간 | 평균 응답 시간 |
|---|---|---|---|
| FCFS | 3.75 | 6.50 | 3.75 |
| SJF | 3.00 | 5.75 | 3.00 |
| SRTF | 2.00 | 4.75 | 0.25 |
| HRN | 3.25 | 6.00 | 3.25 |
| Priority | 4.00 | 6.75 | 4.00 |
| RR(q=2) | 4.25 | 7.00 | 1.50 |
예를 들어 SRTF에서 P2는 시각 1에 도착해 곧바로 처음 실행되고 시각 5에 완료된다.
P2 반환 시간 = 5 - 1 = 4
P2 대기 시간 = 4 - 3 = 1
P2 응답 시간 = 1 - 1 = 0
이 자료에서는 SRTF의 평균 대기 시간이 가장 짧고 RR의 평균 응답 시간이 FCFS보다 짧다. 다른 도착·버스트 조합에서는 순위가 달라질 수 있으므로 한 예제의 결과를 알고리즘의 절대 성질로 일반화하면 안 된다.
UNIX/Linux 기초
UNIX·Linux·배포판
UNIX는 역사적 운영체제 계보이면서 표준·상표 체계와 관련된 이름이다. Linux는 UNIX와 유사한 인터페이스를 제공하는 오픈 소스 커널이다. 사용자가 설치하는 Linux 배포판은 Linux 커널에 셸, GNU 도구, 라이브러리, 패키지 관리자, 데스크톱 환경 등을 결합한 시스템이다.
“Linux는 UNIX 계열과 유사하다”와 “모든 Linux 배포판이 UNIX 인증을 받았다”는 같은 말이 아니다. POSIX 호환 인터페이스를 제공하는 정도와 공식 인증 여부도 구분한다.
커널·셸·터미널
- 커널: 프로세스·메모리·장치·파일 시스템을 관리한다.
- 셸: 명령을 읽고 변수 확장, 와일드카드, 리다이렉션, 파이프라인을 처리한 뒤 명령을 실행한다.
- 터미널: 문자 입력과 출력을 주고받는 장치 또는 그 인터페이스다.
- 터미널 에뮬레이터: GUI 환경에서 터미널처럼 동작하는 프로그램이다.
터미널에서 Bash를 실행할 수 있지만 터미널과 Bash는 같은 프로그램이 아니다. 셸을 바꾸어도 커널은 그대로일 수 있다.
경로와 파일 이름
| 표기 | 의미 |
|---|---|
/ | 파일 시스템의 루트 디렉터리 |
/var/log/app.log | 루트에서 시작하는 절대 경로 |
docs/report.txt | 현재 작업 디렉터리에서 시작하는 상대 경로 |
. | 현재 디렉터리 |
.. | 부모 디렉터리 |
~ | 셸이 확장하는 사용자 홈 디렉터리 표기 |
.profile | 이름이 점으로 시작하는 숨김 파일 관례 |
UNIX/Linux 파일 이름은 일반적으로 대소문자를 구분한다. 확장자는 파일 종류를 이해하는 관례로 쓰이지만, 확장자만으로 실행 가능 여부가 결정되는 것은 아니다. 실행 권한과 파일 형식·해석기가 함께 맞아야 한다.
명령에 /가 포함된 경로를 주면 셸은 그 경로를 직접 사용한다. 단순 명령 이름만 주면 셸이 내장 명령·함수·별칭과 PATH 검색 규칙에 따라 대상을 찾는다.
기본 명령의 목적
| 목적 | 대표 명령 | 핵심 기능 |
|---|---|---|
| 현재 위치·목록 | pwd, ls | 현재 디렉터리와 항목을 확인한다. |
| 디렉터리 이동·생성 | cd, mkdir, rmdir | 작업 위치를 바꾸거나 디렉터리를 관리한다. |
| 파일 생성·복사·이동·삭제 | touch, cp, mv, rm | 파일 시간 갱신·복사·이름 변경·삭제를 수행한다. |
| 내용 확인 | cat, head, tail, less | 전체 또는 앞·뒤·페이지 단위로 내용을 본다. |
| 검색·가공 | grep, sort, uniq, cut, wc, find | 행 검색, 정렬, 중복 정리, 필드 추출, 개수 계산, 경로 탐색을 수행한다. |
| 권한·소유자 | chmod, chown, chgrp | 접근 권한과 소유 정보를 변경한다. |
| 프로세스 | ps, top, pgrep, kill, nice, renice | 상태 확인, 신호 전송, 일반 작업의 우선순위 힌트를 조정한다. |
| 도움말·명령 확인 | man, help, command -v | 문서와 셸의 명령 해석 결과를 확인한다. |
rm은 일반적으로 휴지통으로 이동하지 않고 이름을 제거한다. 특히 재귀·강제 옵션은 대상 경로를 먼저 확인해야 한다. rmdir는 기본적으로 비어 있는 디렉터리에 사용한다.
파일과 디렉터리의 rwx 권한
권한은 보통 소유자(user), 그룹(group), 기타 사용자(other)의 세 집합으로 나뉜다.
| 권한 | 일반 파일 | 디렉터리 |
|---|---|---|
r | 파일 내용을 읽음 | 디렉터리 안의 이름 목록을 읽음 |
w | 파일 내용을 변경함 | 디렉터리 안에 항목을 생성·삭제·이름 변경함 |
x | 실행 파일로 실행함 | 경로를 통과하고 내부 항목에 접근함 |
디렉터리에서는 w만 있고 x가 없으면 실제 항목 조작이 제한될 수 있다. 파일 삭제 가능 여부는 파일 자체의 w뿐 아니라 그 파일이 들어 있는 디렉터리의 w와 x 권한에 크게 좌우된다.
숫자 표기는 r=4, w=2, x=1을 더한다.
chmod 754 run.sh
- 소유자
7 = rwx - 그룹
5 = r-x - 기타 사용자
4 = r--
chmod는 기존 파일의 권한을 바꾸고, umask는 새 파일·디렉터리를 만들 때 요청된 기본 권한에서 허용하지 않을 비트를 제외하는 데 사용된다. umask 022를 곧바로 “새 파일 권한은 022”라고 읽으면 안 된다.
특수 권한에는 실행 파일의 set-user-ID, set-group-ID와 공유 디렉터리에서 삭제를 제한하는 sticky bit가 있다. 숫자 표기의 앞자리 4, 2, 1에 대응하지만, 마운트 옵션과 보안 정책에 따라 효과가 제한될 수 있다.
표준 스트림과 파일 기술자
UNIX 프로세스는 관례적으로 다음 표준 스트림을 사용한다.
| 파일 기술자 | 이름 | 기본 역할 |
|---|---|---|
| 0 | 표준 입력 stdin | 키보드나 앞 단계에서 데이터를 읽음 |
| 1 | 표준 출력 stdout | 정상 결과를 출력함 |
| 2 | 표준 오류 stderr | 오류·진단 메시지를 출력함 |
정상 결과와 오류가 둘 다 화면에 보이더라도 서로 다른 스트림일 수 있다. 파이프는 기본적으로 앞 명령의 stdout만 연결하므로 stderr는 별도 리다이렉션을 하지 않으면 터미널에 남을 수 있다.
리다이렉션·파이프·제어 연산자
| 문법 | 의미 |
|---|---|
command > file | 표준 출력을 파일로 보내고 기존 내용을 덮어씀 |
command >> file | 표준 출력을 파일 끝에 추가함 |
command < file | 파일을 표준 입력으로 사용함 |
command 2> error.log | 표준 오류를 파일로 보냄 |
command 2>&1 | 표준 오류가 현재 표준 출력과 같은 대상을 가리키게 함 |
left | right | 왼쪽의 표준 출력을 오른쪽의 표준 입력으로 연결함 |
a && b | a가 성공했을 때만 b 실행 |
a || b | a가 실패했을 때만 b 실행 |
a ; b | a의 성공 여부와 관계없이 이어서 b 실행 |
command & | 명령을 백그라운드 작업으로 시작 |
리다이렉션은 왼쪽에서 오른쪽으로 적용되므로 순서가 결과에 영향을 줄 수 있다.
command >all.log 2>&1
위 명령은 먼저 stdout을 all.log로 바꾸고, 그다음 stderr가 현재 stdout과 같은 파일을 가리키게 한다.
command 2>&1 >out.log
위 명령은 먼저 stderr가 변경 전 stdout을 가리키게 하고, 이후 stdout만 out.log로 바꾼다. 두 명령은 같은 결과가 아니다.
파이프라인의 간단한 예는 다음과 같다.
printf 'beta\nalpha\nbeta\n' | LC_ALL=C sort | uniq
alpha
beta
|는 데이터 스트림을 연결하고, &&와 ||는 이전 명령의 종료 상태에 따라 다음 명령을 실행할지 결정한다.
셸 변수와 환경 변수
환경은 프로세스가 새 프로그램을 시작할 때 전달할 수 있는 이름=값 형태의 문자열 집합이다. 환경 변수 이름은 대소문자를 구분하며 값은 기본적으로 문자열이다.
Bourne 계열 셸에서는 다음처럼 구분한다.
REPORT=summary # 현재 셸의 변수
export REPORT # 이후 자식 프로세스의 환경에 포함
sh -c 'printf "%s\n" "$REPORT"'
REPORT=child sh -c 'printf "%s\n" "$REPORT"'
printf '%s\n' "$REPORT"
summary
child
summary
두 번째 자식 명령에만 REPORT=child가 전달되며, 부모 셸의 REPORT는 summary로 남는다. 자식 프로세스는 부모 환경의 복사본을 받으므로 자식에서 값을 바꾸어도 부모 환경을 직접 수정하지 못한다.
대표 환경 변수는 다음과 같다.
| 변수 | 대표 의미 | 주의점 |
|---|---|---|
PATH | 단순 명령 이름을 찾을 디렉터리 순서 | 앞쪽 디렉터리가 우선하며 현재 디렉터리는 자동 포함된다고 가정하지 않는다. |
HOME | 사용자의 홈 디렉터리 | 현재 작업 디렉터리와 다르다. |
PWD | 현재 작업 디렉터리 경로 | 셸이 관리하는 값과 실제 디렉터리 상태를 함께 고려한다. |
LANG, LC_* | 로케일·문자·정렬·메시지 규칙 | LC_ALL이나 개별 LC_*가 LANG을 덮을 수 있다. |
TERM | 터미널 종류 | 터미널 제어 방식에 영향을 준다. |
SHELL | 일반적으로 로그인 셸 경로 | 현재 실행 중인 셸과 항상 같다고 단정하지 않는다. |
PATH는 콜론으로 구분된 디렉터리를 앞에서부터 검색한다. 명령에 슬래시가 포함되면 PATH 검색 대상이 아니다. 현재 디렉터리를 검색하려면 명시적으로 .을 넣는 방식이 있지만, 신뢰할 수 없는 디렉터리를 PATH 앞쪽에 두면 같은 이름의 악성 실행 파일이 먼저 선택될 수 있다.
셸이 실제로 어떤 대상을 실행할지 확인할 때는 command -v 이름을 사용할 수 있다. 외부 명령만 찾는 도구는 셸 함수·별칭·내장 명령의 해석을 완전히 반영하지 못할 수 있다.
셸 스크립트의 기본 문법
셸 스크립트는 셸이 해석할 명령과 제어 구조를 파일에 작성한 것이다. 명령을 순서대로 실행하면서 변수, 조건문, 반복문 등을 사용할 수 있다. 다음은 POSIX 셸에서 실행 가능한 예이다.
#!/bin/sh
sum=0
for n in 1 2 3
do
sum=$((sum + n))
done
if [ "$sum" -eq 6 ]; then
printf '%s\n' "$sum"
else
printf '%s\n' '다른 값'
fi
출력은 6이다. sum=0처럼 대입 기호 양쪽에는 공백을 넣지 않는다. $sum 또는 ${sum}은 변수의 값을 확장하고, $((...))은 정수 산술식을 평가한다. [ ... ]의 괄호 주변에는 공백이 필요하며 -eq는 정수의 같음을 검사한다. 문자열 비교에는 =를 사용한다.
$1은 첫 번째 위치 인자, $#은 위치 인자의 개수, $?는 직전에 실행한 명령의 종료 상태이다. 일반적으로 종료 상태 0은 성공을 뜻하지만 구체적인 비영(0이 아닌) 값의 의미는 명령에 따라 다르다. 조건문은 화면에 출력된 글자가 아니라 명령의 종료 상태를 기준으로 판단한다.
셸 변수는 현재 셸에서 사용하며 export한 변수는 이후 실행하는 자식 프로세스의 환경에 전달된다. 자식 프로세스가 자기 환경을 바꾸었다고 부모 셸의 환경이 자동 변경되는 것은 아니다.
셸의 인용과 조건부 실행
작은따옴표 안의 $NAME은 변수값으로 확장되지 않고 문자 그대로 취급된다. 큰따옴표 안에서는 $NAME이 확장되며, 그 값 안의 공백을 유지해 하나의 인자로 전달할 수 있다. 예를 들어 NAME=Kim 이후 작은따옴표로 감싼 $NAME과 큰따옴표로 감싼 $NAME은 서로 다른 문자열을 전달한다.
명령1 && 명령2는 명령1이 성공 상태 0으로 끝났을 때 명령2를 실행한다. 명령1 || 명령2는 명령1이 실패 상태로 끝났을 때 명령2를 실행한다. true와 false는 각각 성공·실패 상태를 반환하는 명령이다. 명령의 출력 문자열이 비어 있는지 여부로 성공·실패를 판단하지 않는다.
파이프는 앞 명령의 표준 출력을 뒤 명령의 표준 입력에 연결한다. 표준 오류를 자동으로 포함하는 것은 아니다. 리다이렉션을 여러 개 쓰면 적용 순서를 고려해야 하며, 2>&1은 그 시점의 표준 출력 대상과 표준 오류를 연결한다.