3. 운영체제 (1)
운영체제
운영체제란?
운영체제란, 사용자의 하드웨어, 시스템 리소스를 제어하고 프로그램에 대한 일반적 서비스를 지원하는 시스템 소프트웨어이다.
운영체제는 윈도우와 맥OS, 리눅스 안드로이드와 IOS 처럼 조율를 막론하고 제공하는 핵심적인 기능은 비슷하다.
이러한 운영체제의 핵심 기능을 담당하는 부분은 바로 커널
운영체제에는 크게 두가지 핵심 기능이 있다.
- 자원 할당 및 관리
- 프로세스 및 스레드 관리
운영체제의 역할
자원 할당 및 관리가 무엇인지 알아보기 전에 자원이 무엇인지에 대해 알아 볼 필요가 있다.
자원이란, 프로그램 실행에 마땅히 필요한 요소
실행에 필요한 ‘데이터’를 자원이라고 하기도 하고 ‘부품’을 자원이라고도 한다.
운영체제는 사용자가 실행하는 응용 프로그램을 대신해 CPU, 메모리와 같은 하드웨어에 접근하고, 각각의 하드웨어들이 효율적으로 사용되도록 관리한다.
또한, 응용 프로그램이 컴퓨터 부품들을 효율적으로 할당받아 문제 없이 실행할 수 있도록 응용 프로그램에게 자원을 할당한다.
CPU 관리: CPU 스케줄링
메모리에는 실행 중인 프로그램이 다수 적재가 가능하지만, CPU는 동시에 모두를 처리할 수는 없다.
CPU는 한정된 자원이므로, 할당받아 사용해야한다.
운영체제는 CPU의 할당 순서와 사용 시간을 결정한다.
이것이 CPU 스케줄링
메모리 관리: 가상 메모리
메모리는 실행하는 프로그램은 메모리에 적재하고, 종료되면 삭제한다. 이 과정 속에서 공간이 낭비되지 않도록 효율적으로 관리해야 한다.
운영체제는 가상 메모리
를 통해 실메 물리적인 메모리 크기보다 더 큰 메모리를 이용할 수 있도록 한다.
파일/디렉토리 관리: 파일 시스템
보조기억장치에 아무렇게나 정보를 저장하면 데이터에 접근하기 위해 오랜 시간이 소요된다.
운영체제는 파일 시스템을 통해 보조기억장치 내의 정보를 파일 및 디렉토리 단위로 접근 및 관리
하드웨어의 인터럽트 서비스 루틴이나 캐시 메모리의 일관성을 유지하는 일들 모두 운영체제의 역할
프로세스 및 스레드 관리
프로세스 : 실행 중인 프로그램
스레드 : 프로세스를 이루는 실행의 단위
- 메모리에는 여러 프로세스가 적재
- 운영체제는 이 프로세스에 필요한 자원을 할당
- 스레드는 프로세스가 할당받은 자원을 이용해 프로세스의 작업을 수행
운영체제는 동시다발적인 위 작업들을 올바르게 처리되도록 실행을 제어하고, 프로세스와 스레드가 요구하는 자원을 적절하게 배분해야한다.
시스템 콜과 이중 모드
커널 영역 : 운영체제가 실행되는 메모리 영역
사용자 영역 : 사용자가 실행하는 일반적인 프로그램이 실행되는 메모리 영역
사용자 영역의 프로그램은 자원에 직접 접근하거나 조작할 수 없다.
이를 대신하기 위해 시스템 콜
을 호출하여 운영체제 코드를 실행할 수 있다.
구분 | 시스템 콜 | 설명 |
---|---|---|
프로세스 관리 | fork() | 새 자식 프로세스 생성 |
execve() | 프로세스 실행(메모리 공간을 새로운 프로그램의 내용으로 덮어씌움) | |
exit() | 프로세스 종료 | |
waitpid() | 자식 프로세스가 종료할 때까지 대기 | |
파일 관리 | open() | 파일 열기 |
close() | 파일 닫기 | |
read() | 파일 읽기 | |
write() | 파일 쓰기 | |
stat() | 파일 정보 획득 | |
디렉토리 관리 | chdir() | 작업 디렉토리 변경 |
mkdir() | 디렉토리 생성 | |
rmdir() | 비어 있는 디렉토리 삭제 | |
파일 시스템 관리 | mount() | 파일 시스템 마운트 |
umount() | 파일 시스템 마운트 해제 |
fork()를 보면 알 수 있듯 프로세스는 프로세스를 통해 다른 프로세스를 생성하고 이런 식으로 늘려 나간다.
따라서 운영체제에서는 프로세스를 계층적으로 관리하는데 생성한 프로세스를 부모 프로세스
생성된 프로세스를 자식 프로세스
라고 한다.
운영체제에는 인터럽트를 발생시키는 특정 명령어가 있는데, 이렇게 발생되는 인터럽트가 소프트웨어 인터럽트이다.
시스템 콜이 소프트웨어 인터럽트의 일종으로 시스템 콜이 호출되면 CPU는 현재 작업을 백업하고, 커널 영역 내의 인터럽트를 처리하기 위한 코드를 실행한 뒤 다시 사용자 영역의 코드를 실행한다.
이때, 사용자 영역의 코드를 실행하는 모드를 사용자 모드
라 하고, 커널 영역에 적재된 코드를 실행 하는 모드를 커널 모드
라고 한다.
- 이 두 모드를 구분하여 실행하는 것을
이중모드
라고 함- 사용자 모드 : 운영체제 서비스를 제공받을 수 없는 실행 모드
- 커널 영역 접근 불가 → 자원 보호
- 커널 모드 : 운영체제 서비스를 제공받을 수 있는 실행 모드
- 자원 접근 가능
- 사용자 모드 : 운영체제 서비스를 제공받을 수 없는 실행 모드
프로세스와 스레드
프로세스 유형
- 포그라운드 프로세스 : 사용자가 보는 공간에서 사용자와 상호작용하며 실행
- 백그라운드 프로세스 : 사용자가 보지 못하는 곳에서 실행
- 데몬 : 사용자와 별다른 상호작용 없이 주어진 작업만 수행하는 프로세스
프로세스 유형과는 별개로 메모리 내의 정보는 크게 다르지 않다.
커널 영역
- 프로세스 제어블록
사용자 영역
정적 할당 영역
- 코드 영역
- 실행 가능한 명령어가 저장된 공간
- Read-Only
- 데이터 영역
- 프로그램이 실행되는 동안 유지할 데이터 저장 공간
- 정적 변수 or 전역 변수 저장
동적 할당 영역
- 힙 영역
- 사용자가 직접 할당 가능한 저장 공간
- 메모리 공간을 반환하지 않으면 메모리 누수 문제 초래
- 가비지 컬렉션으로 해결
- 스택 영역
- 일시적으로 사용할 값들이 저장되는 공간
- 매개변수 or 지역 변수 or 함수 복귀 주소 저장
- 스택 트레이스 : 특정 시점에 스택영역에 저장된 함수 호출 정보
Exception in thread "main" java.lang.NullPointerException
at com.example.myproject.Test.getThis(Test.java:16)
at com.example.myproject.Test2.getThat(Test2.java:25)
at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
PCB와 문맥 교환(Context Switching)
PCB (프로세스 제어 블록) : 프로세스를 식별할 수 있는 커널 영역 내 정보
- 프로세스와 관련한 정보를 내포하는 구조체
- 새로운 프로세스가 메모리에 적재 시 커널 영역에 만들어짐
- 끝나면 폐기
주로 아래 항목들을 명시한다.
- PID
- 레지스터 값,
- 프로세스 상태
- CPU 스케줄링(우선순위)정보
- 메모리 관련 정보
- 파일 및 입출력 장치 관련 정보
이때 여러 PCB들은 커널 내에 프로세스 테이블의 형태로 관리된다.
마찬가지로 실행되면 테이블에 추가되고, 종료 시 테이블에서 삭제된다.
이때, 비정상적으로 종료되어 종료된 프로세스가 PCB에 남는 경우가 있는데
이를 좀비 프로세스 라고 한다.
프로세스는 운영체제에 의해 CPU 자원을 할당받으며 실행된다.
프로세스의 CPU 사용 시간은 타이머 인터럽트에 의해 제한된다.
- 타이머(타임아웃) 인터럽트 : 시간이 끝났음을 알리는 인터럽트
이렇게 제한되면 다른 프로세스에게 자원을 양보하게 되는데, 지금까지 모든 중간 정보를 백업해야 한다.
이렇게 백업해야 하는 중간 정보를 Context(문맥)이라고 하며, 해당 프로세스 PCB에 명시된다.
백업을 마치면 뒤이어 실행할 프로세스의 Context(문맥)을 복구하는데
이렇게 기존 문맥을 PCB에 백업하고 문맥을 복구하여 새로운 프로세스를 실행하는 것을 Context Switching(문맥 교환) 이라고 한다.
컨텍스트 스위칭이 자주 발생하면 문맥을 로드하는 시간이 증가하므로 성능상 좋지 않다.
프로세스의 상태
- 생성 상태(new)
- 프로세스를 생성 중인 상태
- 메모리에 적재되어 PCB를 할당 받음
- 준비 상태(ready)
- 당장이라도 CPU를 받아 실행할 수 있지만, 차례가 되지 않아 대기 중
- CPU를 할당받으면 실행 상태가 되며, 준비 → 실행 으로 전환되는 것을 디스패치(dispatch)라고 한다.
- 실행 상태(running)
- CPU를 할당받아 실행 중인 상태
- 일정 시간 사용 후, 타임아웃이 발생하여 다시 준비 상태로 돌아감
- 입출력장치 작업에 의해 대기를 해야하면 대기상태로 돌아감
- 대기 상태(blocked)
- 프로세스가 입출력 작업을 요청하거나 바로 확보할 수 없는 자원을 요청하면 대기 상태가 됨
- 다시 실행 가능한 상태가 되면 준비 상태로 돌아가 대기함
- 종료 상태(terminated)
- 프로세스 종료
- 운영체제는 PCB와 프로세스가 사용한 메모리 정리
블로킹 입출력과 논블로킹 입출력
블로킹 입출력(blocking I/O) : 실행 도중 입출력 작업을 수행해야 한다면 프로세스는 대기 상태로 접어들고, 작업이 완료되면 실행 재개
논블로킹 입출력(non-blocking I/O) : 입출력장치에게 작업을 맡긴 뒤, 명령을 계속해서 수행
멀티프로세스와 멀티스레드
멀티 프로세스 : 동시에 여러 프로세스가 실행
- 다른 프로세스들과 자원을 공유하지 않고 독립적으로 실행됨
- 각각의 PID값이 다르고, 프로세스 별로 자원이 독립적으로 할당됨
멀티 스레드 : 프로세스를 동시에 실행하는 여러 스레드
- 하나의 스레드는 스레드 고유 스레드ID와 PC(프로그램 카운터), 레지스터 값, 스택 등 구성
- 스레드마다 PC와 스택을 갖고 있으므로 다음 실행할 주소를 갖고 있고, 연산 과정을 임시 저장도 할 수 있음
이 둘의 가장 큰 차이점은 자원의 공유 여부에 있다.
프로세스는 서로 다른 프로세스와 자원을 공유하지 않는다.
같은 프로세스를 실행하는 여러 스레드들은 프로세스의 자원을 공유한다.
따라서, 멀티스레드 환경은 쉽게 협력하고 통신할 수 있지만, 오류 전파도 쉽게 된다.
프로세스 간 통신
프로세스는 기본적으로 자원을 공유하지 않지만 공유할 수 있는 방법이 있다.
이를 프로세스 간 통신(IPC) 라고 한다.
- 공유 메모리 : 데이터를 주고받는 프로세스가 공통적으로 사용할 메모리 영역을 둔다.
- 메세지 전달 : 프로세스 간 데이터를 메세지 형태로 주고받는다.
공유 메모리
프로세스 간 공유하는 메모리 영역을 토대로 데이터를 주고받는 통신 방식
두가지 방법 존재
- 공유하는 메모리 영역을 확보하는 시스템 콜 기반 수행
- 프로세스가 공유하는 변수나 파일을 통해 통신
마치 자신의 메모리 영역처럼 읽고 쓰며 통신
- 즉, 프로세스가 데이터를 주고받는 과정에 커널의 개입이 거의 없음
- 따라서 통신 속도가 빠르다.
- 공유 메모리를 읽고 쓰기 때문에 데이터의 일관성이 훼손될 수 있음
- 레이스 컨디션
메세지 전달
프로세스 간 데이터가 커널을 거쳐 송수신되는 통신 방식
- 시스템 콜 send()와 recv()로 송수신 할 수 있다.
- 두 시스템 콜을 호출하며 커널을 통해 송수신
- 레이스 컨디션, 동기화 등의 문제가 상대적으로 적다.
- 공유 메모리 기반 IPC 보다 느림
대표적인 수단
- 파이프
- 단방향 프로세스 간 통신 도구 (큐 같은 느낌)
- 익명 파이프
- 부모 프로세스와 자식 프로세스 간 통신만 가능
- 지명 파이프
- 양방향 통신이 가능하며 임의의 프로세스 간 사용 가능
- 시그널
- 프로세스에게 특정 이벤트가 발생했음을 알리는 비동기적 신호
- 인터럽트 처리 과정과 유사
- 시그널 처리를 위한 시그널 핸들러 존재
- 시그널 발생 시 동작을 정의 하여 시그널을 보내고 동작을 시키는 흐름
- 직접적인 통신이 아님
- 코어 덤프 : 비정상적으로 종료하는 경우 생성되는 파일
- 소켓
- RPC
- 원격 코드를 실행하는 IPC 기술
- 언어, 플랫폼과 무관하게 성능 저하를 최소화하고 메세지 송수신 가능
- 대규모 트래픽 처리 환경, server to server 에서 특히 자주 사용
동기화와 교착 상태
공유 자원 : 프로세스 혹은 스레드가 공유하는 자원
임계 구역 : 코드 중 동시에 실행했을 때 문제가 발생할 수 있는 코드
동시에 파일을 수정하는 스레드는
- 파일을 읽고
- 원하는 내용을 저장
- 작성한 내용을 저장
의 순서로 작업을 진행한다.
이 때, 파일을 읽고 쓰는 도중 다른 스레드가 파일을 읽고 쓰는 과정을 동시에 한다면 저장하는 타이밍이 겹쳐 하나의 스레드에서의 작업이 반영되지 않을 수 있다.
이처럼 동시다발적으로 실행되는 프로세스 혹은 스레드가 동시에 임계 구역을 실행하여 문제가 발생하는 상황을 레이스 컨디션 이라고 한다.
레이스 컨디션을 방지하면서 임계 구역을 관리하기 위해서는
프로세스와 스레드가 동기화 되어야 한다.
- 실행 순서 제어 : 프로세스 및 스레드를 올바른 순서로 실행
- 공유 자원을 두고 올바른 순서로 실행되지 않아 레이스 컨디션 발생한 경우
- 상호 배제 : 동시에 접근해서는 안되는 자원에 하나의 프로세스 및 스레드만 접근
- 공유 자원에 동시에 접근해 레이스 컨디션이 발생한 경우
즉, 동기화에는 실행 순서 제어를 위한 동기화가 있고, 상호 배제를 위한 동기화가 있다.
동기화 기법
뮤텍스 락
뮤텍스 락은 동시에 접근해서는 안되는 자원에 동시 접근이 불가능하도록 상호 배제를 보장하는 동기화 도구
임계 구역에 접근하고자 한다면 반드시 락을 획득해야 하고, 임계 구역에서의 작업이 끝났다면 락을 해제해야 한다.
lock.acquire()
// 임계 구역
lock.release()
뮤텍스 락을 사용한다면 다음과 같이 프로세스가 실행된다.
- 프로세스 P1 acquire() 호출, 임계 구역 진입
- 프로세스 P2 acquire() 호출, lock을 획득하지 못해 임계구역 접근 불가
- 프로세스 P1 임계 구역 작업 종료, release() 호출
- 프로세스 P2 임계구역 진입
세마포어
뮤텍스 락은 하나의 공유 자원만 고려하는 동기화 도구이다.
한번에 여러개의 프로세스 및 스레드까지 특정 자원을 이용할 수 있는 상황에서는
세마포어를 사용한다.
세마포어는 변수 1개와 함수 2개로 구성되어있다.
- 변수 S : 사용 가능한 공유 자원의 개수를 나타냄
- wait() 함수 : 임계 구역 진입 전 호출하는 함수
- signal() 함수 : 임계 구역 진입 후 호출하는 함수
wait()
// 임계 구역
signal()
wait() 함수 호출시
- 변수 S를 1 감소
- 변수 S의 값이 0보다 작은지 확인
- 0 이상이면 공유 자원 남아 있으니 호출한 프로세스 임계구역 진입
- 0 미만이라면 대기 상태로 전환되어 진입 불가
wait() {
S--;
if(S<0){
sleep();
}
}
signal() 함수 호출 시
- 변수 S를 1 증가
- 변수 S의 값이 0이하인지 확인
- 0보다 크다면 공유 자원 개수가 남아있다는 것
- 0 이하라면 임계 구역에 진입하기 위해 대기하는 프로세스 존재
- 이 경우 대기 상태로 접어든 프로세스 중 하나를 준비 상태로 전환 (깨운것)
signal() {
S++
if(S<=0){
wakeup(p)
}
}
결국 wait()과 signal을 통해 다음과 같이 동기화를 하게 된다.
조건 변수와 모니터
조건 변수 : 실행 순서 제어를 위한 동기화 도구
- 특정 조건 하에 프로세스 실행/일시 중단을 통해 프로세스 혹은 스레드의 실행 순서 제어
조건 변수에 대해 wait()과 signal() 함수를 호출할 수 있다.
모니터는 공유 자원과 그 공유 자원을 다루는 함수로 구성된 동기화 도구
- 상호 배제를 위한 동기화뿐만 아니라 실행 순서 제어를 위한 동기화 가능
- 프로세스 및 스레드는 공유자원을 접근하기 위해 반드시 정해진 공유 자원 인터페이스를 통해 모니터 내로 진입해야함
- 진입하여 실행되는 프로세스 혹은 스레드는 무조건 하나여야 하므로 나머지는 큐에서 대기
만약 A 프로세스가 먼저 실행되어야 하는데 B 프로세스가 먼저 모니터에 진입한다면
- 조건변수에 대해 wait() 함수가 호출되어 B 프로세스를 대기 상태로 만듦
- 그 사이 A 프로세스를 실행
- 끝나면 signal()을 통해 대기 상태로 만든 B 프로세스를 호출
이 모든 과정을 자바에서는 synchronized 키워드로 제공하고 있다.
스레드 안전
멀티스레드 환경에서 어떤 변수나 함수, 객체에 동시에 접근이 이루어져도 실행에 문제가 없는 상태
자바에서는 Vector는 synchronized 블록 덕분에 스레드 안전한 상태이고,
ArrayList는 동기화가 되지 않아 레이스 컨디션이 발생할 수 있다.
교착 상태
일어나지 않을 사건을 기다리며 프로세스의 진행이 멈춰버리는 현상
교착 상태의 발생 조건
- 상호 배제
- 하나의 프로세스만 해당 자원을 이용 가능 할 때
- 점유와 대기
- 한 프로세스가 어떤 자원을 할당 받은 상태에서 다른 자원을 기다릴 때
- 비선점
- 어떤 프로세스도 다른 프로세스의 자원을 강제로 뺏지 못하는 경우
- 원형 대기
- 프로세스와 프로세스가 요청한 자원이 사이클을 이루는 경우
해결 방법
- 교착 상태 예방
- 위 4가지 발생 조건 중 하나를 충족하지 못하게 하는 방법
- 필요한 자원을 하나의 프로세스에 몰아준다.
- 할당 가능한 모든 자원에 번호를 매기고 오름차순으로 할당
- 위 4가지 발생 조건 중 하나를 충족하지 못하게 하는 방법
- 교착 상태 회피
- 교착 상태는 한정된 자원의 무분별한 할당으로 인해 발생하는 문제
- 교착 상태 검출 후 회복
- 자원을 할당하고 주기적으로 교착 상태의 여부 검사
- 교착 상태 검출 시
- 자원 선점을 통해 회복
- 프로세스 강제 종료를 통해 회복