Tech Interview - 운영체제

wintee·2025년 6월 16일

※ 질문 출처: https://github.com/VSFe/Tech-Interview

1. 시스템 콜

응용 프로그램이 운영체제의 커널의 기능을 사용하기 위한 인터페이스. 보안상의 이유로 고급 언어로 작성된 프로그램은 커널의 기능을 직접적으로 호출할 수 없어 커널의 기능을 사용하기 위해서는 시스템 콜을 사용하여야 한다.

시스템 콜 예시

파일 조작에서 사용하는 open(), close(), 프로세스를 생성하는 fork()와 같은 함수가 시스템 콜이다.

시스템 콜의 실행 과정

  1. 응용 프로그램이 시스템 콜에 해당하는 사용자 함수를 호출
  2. CPU 상태를 사용자 모드에서 커널 모드로 변경
  3. 현재 실행 중인 프로세스의 문맥을 커널 스택에 저장
  4. 시스템 콜의 번호를 확인하여 어떤 시스템 콜인지 확인
  5. 시스템 콜 작업 수행
  6. 값을 반환하고 문맥을 복원
  7. CPU 상태를 사용자 모드로 전환

시스템 콜의 유형

  • 프로세스 제어
    • 프로세스의 생성, 종료, 동기화, 스케줄링 관리
    • fork(), exit(), wait(), getpid()
  • 파일 조작
    • 파일과 디렉토리의 생성, 삭제, 읽기, 쓰기 등 파일의 조작
    • open(), read(), close()
  • 장치 관리
    • 하드웨어 장치와의 저수준 입출력을 제어
  • 정보 유지
    • 시스템 전반 또는 프로세스 별 각종 정보 조회
    • time(), uname()
  • 통신
    • 프로세스 간 데이터 교환 및 동기화
    • pipe(), socket(), bind(), listen(), accept()

이중 동작 모드

CPU가 동작할 수 있는 두 가지 권한 수준을 정의하여 응용 프로그램과 OS 커널이 격리된 상태에서 안전하게 동작할 수 있도록 하는 매커니즘.

일반적인 응용 프로그램은 사용자 모드에서 동작하여 하드웨어나 다른 프로세스의 메모리를 훼손하는 것을 막음.

일반적으로 CPU 내부에 Mode Bit를 하나 두고 이를 통해 모드를 구분함.

  • 사용자 모드
    • 일반적인 응용 프로그램이 실행
    • 제한된 명령어만 사용 가능
    • 직접적인 하드웨어 접근, 시스템 레지스터 조작과 같은 특정 명령어는 실행 불가
  • 커널 모드
    • 운영체제의 커널 코드가 실행
    • CPU의 모든 명령어를 사용 가능
    • 메모리 관리, 하드웨어 제어 등 중요도가 높은 작업을 처리

시스템 콜의 구분법

시스템 콜은 커널 내부에서 고유의 번호가 할당되어 있음. 시스템 콜은 이 번호를 통해 식별됨


2. 인터럽트

인터럽트란 특정 장치가 CPU로 신호를 보내 긴급한 예외 상황이 발생했다는 것을 CPU에 알리고 프로그램의 실행을 잠시 중지하고 해당 상황의 처리를 하도록 하는 매커니즘.

CPU는 인터럽트를 감지하면 즉시 실행 중인 프로그램을 중단하고 인터럽트를 처리하기 위한 코드로 점프하여 일을 수행함.

인터럽트 처리 방법

  1. 현재 실행 중인 프로그램 중단 및 문맥 저장
  2. 인터럽트 서비스 루틴(ISR) 호출
  3. ISR 내부에서 인터럽트 처리
  4. 프로그램 문맥 복구 및 실행 재개

Polling

특정 이벤트가 발생했는지를 지속적으로 확인하는 기법. 인터럽트와 묶여서 언급될 때는 CPU가 특정 예외 상황이 발생했는지를 지속적으로 검사하는 것을 나타냄.

단순하고 특정 기기가 인터럽트를 지원하지 않아도 사용할 수 있지만 이벤트가 발생하지 않는 상황에서도 CPU가 지속적으로 자원을 소비하여 특정 이벤트가 발생했는지를 체크하므로 자원적인 손실이 발생함. 폴링 주기가 짧으면 오버헤드가 커지고 길면 응답에 대한 처리가 늦어짐.

HW 인터럽트/SW 인터럽트

  • HW 인터럽트
    • 외부 하드웨어가 인터럽트를 보내는 것
    • 키보드 입력, 디스크 I/O 등
  • SW 인터럽트
    • 프로그램이 의도적으로 인터럽트를 발생시키는 것
    • 시스템 콜, 예외 상황(0으로 나누기 등)

인터럽트의 동시 발생

인터럽트에는 우선순위가 존재한다. 만일 인터럽트가 동시에 발생했다면 우선순위가 더 높은 인터럽트부터 순차적으로 처리된다.

심지어 ISR을 실행 중이더라도 더 높은 우선순위의 인터럽트 발생 시 해당 인터럽트의 ISR을 먼저 실행하게 된다.

일반적으로 HW 인터럽트가 SW 인터럽트보다 우선순위가 더 높다.


3. 프로세스

프로세스란 실행 중인 프로그램을 의미.

커널 내부에 존재하는 준비 큐, 대기 큐, 실행 큐의 관리를 받으며 생성, 준비, 실행, 대기, 종료의 라이프사이클을 지님

프로그램, 프로세스, 스레드

  • 프로그램
    • 저장 장치에 저장되어 있는 실행 코드를 의미
  • 프로세스
    • 구동 중인 프로그램
    • 프로그램이 메모리에 적재되어 실행되고 있는 상태
    • OS에 의해 메모리, CPU 시간 등이 할당됨
    • 서로 다른 프로세스는 메모리 영역을 공유하지 않음
    • 최소 1개 이상의 스레드를 보유
  • 스레드
    • 프로세스 내부의 실행 단위
    • 같은 프로세스 내의 스레드는 메모리를 공유(단 스택 영역은 공유하지 않음)
    • 프로세스보다 가벼워 관련 작업의 오버헤드가 적음

PCB

PCB는 프로세스 제어 블록의 줄임말로 프로세스와 관련된 메타 정보가 저장되어 있는 커널의 자료 구조이다.

프로세스 ID, 프로세스 상태, 프로그램 카운터, CPU 레지스터 상태 등의 정보가 저장된다.

문맥 교환 시 현재 프로세스의 실행을 멈추고 다른 프로세스를 실행해야하는데, 이때 중단될 프로세스의 정보를 PCB에 저장하고 실행할 프로세스의 정보를 PCB에서 불러온다.

TCB

TCB는 스레드 제어 블록의 줄임말로 스레드와 관련된 메타 정보가 저장되어 있는 커널의 자료 구조이다. 각 스레드마다 하나씩 관리된다.

스레드 ID, 스레드 상태, 스택 포인터, 프로그램 카운터, 소속 PCB 포인터 등의 정보가 저장된다.

TCB 역시 PCB와 같이 문맥 교환 시 스레드의 정보를 저장하고 불러올 때 사용된다.

좀비, 고아 프로세스

  • 좀비 프로세스
    • 자식 프로세스가 exit()로 종료되었을 때 자식 프로세스의 정보는 커널에 남아있음
    • 이는 부모 프로세스가 wait()를 호출하여 자식 프로세스의 정보를 얻을 수 있게하기 위함
    • 결과적으로 프로세스는 죽었지만 프로세스의 정보는 커널에 남아있는 상태가 되며 이를 좀비 프로세스라고 함
    • 부모가 wait() 함수를 호출하면 좀비 프로세스가 정리되며 호출하지 않으면 무기한 유지됨
  • 고아 프로세스
    • 자식 프로세스는 살아있는데 부모 프로세스가 먼저 죽은 경우
    • 부모가 죽은 자식 프로세스라고 하여 해당 자식 프로세스를 고아 프로세스라고 함
    • 고아 프로세스 발생 시 OS가 자동으로 자식 프로세스의 부모를 init 프로세스로 지정

데몬 프로세스

데몬 프로세스는 백그라운드에서 실행되며 사용자가 상호작용하지 않는 시스템 서비스용 프로세스이다. 사용자와 상호작용하지 않기 때문에 터미널과 연결되지 않는다.

데몬 프로세스는 데몬 프로세스임을 뜻하는 d가 이름의 끝에 존재하며 init 프로세스를 부모 프로세스로 가진다.

프로세스 루트

리눅스에서는 프로세스의 구조가 트리 형태로 이루어져 있으며 해당 트리 구조에서의 최상단 루트 프로세스를 init 프로세스라고 한다.

시스템이 종료될 때까지 계속하여 실행되는 데몬 프로세스이며 자동으로 고아 프로세스를 입양한다.

최상단 루트 프로세스이기 때문에 PID는 1로 고정된다.


4. 프로세스 주소공간

프로세스가 접근할 수 있는 메모리 영역을 의미. 프로세스마다 독립적으로 존재하며 서로의 주소 공간에 접근이 불가능

프로세스 주소공간은 아래의 영역들로 구성되어 있음. 이는 프로세스에 저장되어 있는 데이터들의 특성이 다르기 때문에 특성에 맞게 영역을 할당시켜 효율적으로 관리하고 프로세스의 안정성을 높이기 위함이다.

  • 코드 영역
    • 실행할 프로그램의 코드가 저장
    • 읽기 전용
  • 데이터 영역
    • 초기화된 전역 변수, 정적 변수 등이 저장
  • BSS 영역
    • 초기화되지 않은 전역 변수, 정적 변수가 저장
    • 프로세스 실행 시 해당 변수들의 초기화 작업 수행됨
  • 힙 영역
    • 프로그래머가 동적으로 할당한 데이터가 저장
    • 주소가 증가하는 방향으로 확장
  • 스택 영역
    • 지역 변수, 매개변수, 반환 주소 등이 저장
    • 주소가 감소하는 방향으로 확장

스택과 힙의 크기

스택의 경우 크기가 고정되어 있으며 컴파일 타임에 결정됨.

힙은 크기가 고정되어 있지 않고 프로그램 실행 중 메모리 크기가 결정됨. 프로그램이 실행되며 메모리가 할당되면 크기가 커지고 메모리가 해제 되면 크기가 작아짐. 다만 무한한 공간을 보장하진 않고 OS가 설정한 최대 크기까지만 확장 가능.

스택과 힙의 접근 속도

접근 속도는 스택이 더 빠르다. 스택은 크기가 고정되어 있어 OS에서 메모리를 할당할 때 연속된 메모리 공간을 할당받는다. 따라서 CPU 캐시의 지역성을 활용할 수 있고 스택 구조인 메모리 할당, 해제 시에는 단순히 스택 포인터의 값만 조절하면 된다.

반면 힙은 해당 메모리까지 접근하기 위한 포인터를 따라가야 하며 지역성을 활용할 수 없어 캐시 미스가 발생할 확률이 높아 상대적으로 스택과 비교하여 접근 속도가 느리다.

스레드 주소공간

스레드는 프로세스 내부에 위치한 실행 흐름이므로 스택 영역을 제외한 다른 모든 주소공간을 부모 프로세스와 공유한다. 단 스택 영역의 경우 스레드 별로 가진다.

자료구조와의 관계

스택 영역은 스택 자료구조로 설계되어 있기 때문에 직접적인 관련이 있다고 볼 수 있다.

하지만 힙 영역의 경우 자료구조 힙과 이름만 같을 뿐 연관이 없다. 자료구조에서의 힙은 우선순위가 가장 큰 값을 빠르게 찾기 위한 자료구조인데, 힙 영역은 힙 자료구조로 설계되어 있지 않고 단순한 메모리 풀로 설계되어 있다.

힙이라는 이름이 붙은 이유는 초기 OS 개발자들이 큰 메모리 덩어리라는 의미의 힙(무더기)이라고 이름을 붙였다고 한다.

공유 메모리

공유 메모리는 IPC 기법 중의 하나로, 독립된 주소 공간에 공유 메모리 영역을 할당한 후 프로세스에서 미사용 메모리 주소공간 중 하나를 공유 메모리 영역과 매핑하여 여러 프로세스가 자신의 메모리 공간인 것처럼 사용하는 기법이다.

공유 메모리를 할당받을 때만 커널에 요청하고 이후의 작업은 자신의 메모리를 사용하듯 이용할 수 있기 때문에 IPC 기법 중 가장 빠르다.


5. 스케쥴러

일반적으로 하나의 CPU 코어에서 여러 개의 프로세스가 번갈아가면서 실행된다. 이때 어떤 프로세스를 먼저 실행할 것인지 결정해주는 것이 바로 스케쥴러이다.

스케쥴러는 고유의 알고리즘을 사용하여 어떤 프로세스를 실행시킬 지 결정하며 컴퓨팅 자원을 효율적으로 분배하게 된다.

스케쥴러의 종류는 아래의 3가지가 있다.

  • 장기 스케쥴러
    • 시스템으로 진입한 프로세스를 언제 메모리로 적재할 지 결정
    • 작업 큐 --> 대기 큐
    • 현대 OS에서는 거의 사용되지 않음. 프로세스 실행 시 곧 바로 메모리에 적재됨
  • 중기 스케쥴러
    • 메모리에 상주하는 프로세스를 일시적으로 디스크로 내보내거나 다시 적재함
    • 현대 OS에서는 프로세스 전체 스와핑은 거의 수행하지 않으며 페이지 단위로 스와핑을 수행
  • 단기 스케쥴러
    • 준비 큐에 있는 프로세스 중 어느 것을 다음 CPU로 보낼 지 결정
    • 디스패처를 호출해 문맥 교환 수행
    • 스케쥴링 알고리즘은 단기 스케쥴러에서 수행

프로세스 스케쥴링 상태

  • New
    • 프로세스가 생성되었지만 메모리에 적재되지 않은 상태
  • Ready
    • 실행 준비가 완료되어 CPU 할당을 기다리는 상태
    • 대기 큐에서 대기
  • Running
    • CPU를 할당받아 명령을 수행 중인 상태
  • Blocked
    • I/O 완료, 이벤트 발생 등을 기다리며 CPU를 반납한 상태
  • Terminated
    • 프로세스의 실행이 종료된 상태
    • PCB를 해제하고 자원을 반환

만일 프로세스 전체 스와핑을 지원한다면 아래 2가지 상태가 추가로 존재하게 된다.

  • Ready Suspended
    • 메모리 부족 등의 이유로 Ready 상태의 프로세스를 스와핑 아웃한 상태
  • Blocked Suspended
    • Waiting 상태의 프로세스를 디스크로 스와핑 아웃한 상태

6. 컨텍스트 스위칭

컨텍스트 스위칭은 현재 실행 중인 실행 단위를 교체하기 위해 현재 실행 중인 문맥을 저장하고 다음에 실행할 문맥을 불러오는 과정을 뜻한다.

문맥이란 현재 실행 단위가 지니고 있는 정보(레지스터 상태, 프로그램 카운터, 스택 포인터 등)을 의미한다.

CPU가 다음 실행 단위를 실행시켜야 할 경우 현재 문맥을 PCB에 저장하고 스케쥴러를 호출하여 다음 실행 단위를 불러온 해당 실행 단위의 PCB를 기반으로 문맥을 복원 후 실행을 재개한다.

OS가 설정한 타임 슬라이스 만료, I/O 혹은 이벤트 대기, 인터럽트 발생, 단기 스케쥴러의 동작 등의 원인으로 발생한다.

프로세스와 스레드의 컨텍스트 스위칭

프로세스와 스레드 모두 컨텍스트 스위칭의 대상이며 스레드의 컨텍스트 스위칭 비용이 훨씬 적다.

스레드의 대부분의 정보는 프로세스와 공유하므로 주소 공간의 전환이 불필요하며 TCB의 스택 포인터와 레지스터 정보 정도만 복원하면 되기 때문이다.

반면 프로세스의 컨텍스트 스위칭의 경우 주소 공간이 달라지므로 페이지 테이블을 전환하여하고 그에 따라 TLB 또한 플러시하고 다시 채우게 된다. 뿐만 아니라 코드, 데이터, 힙 등 다른 주소 공간의 모든 정보를 저장하고 불러와야 하기 때문에 스레드와 비교하여 컨텍스트 스위칭의 비용이 크다.


7. 프로세스 스케쥴링 알고리즘

프로세스 스케쥴링 알고리즘은 단기 스케쥴러에서 CPU 자원을 여러 프로세스에게 효율적으로 분배하기 위한 알고리즘이다.

이름은 프로세스 스케쥴링 알고리즘이지만 실제 운영체제는 프로세스가 아닌 스레드를 스케쥴링한다.

주요 알고리즘은 아래와 같다.

  • FCFS(First-Come, First-Served)
    • 가장 먼저 도착한 프로세스부터 실행
    • 구현이 간단하나, 긴 작업이 먼저 들어오면 뒷단의 짧은 작업들이 긴 시간동안 대기
    • 실행 순서가 지켜져야 할 경우 사용 가능
  • SJF(Shortest Job First)
    • 실행 시간이 가장 짧은 프로세스를 먼저 실행
    • 평균 대기 시간이 최소가 되지만 프로세스의 실행 시간을 예측하기 어려움
    • 또한 작업 시간이 긴 프로세스는 자원을 할당받지 못할 가능성 존재
  • Priority Scheduling
    • 각 프로세스에 우선순위를 부여 후 우선순위 기준으로 실행
    • 우선순위가 낮은 프로세스는 할당 자원을 할당받지 못할 가능성 존재
      • 에이징 기법 사용 가능
  • RR(Round Robin)
    • 특정한 시간(타임 슬라이스)을 정해두고 해당 시간마다 프로세스를 교체
    • 타임 슬라이스가 작은 경우
      • 응답성을 높이고 자원 사용 효율은 낮추는 경우
      • 대기 중인 프로세스가 빠르게 CPU를 할당받을 수 있아 작업 가능
      • 문맥 교환 오버헤드가 커짐
    • 타임 슬라이스가 큰 경우
      • 응답성을 낮추고 자우너 사용 효율을 높이는 경우
      • 문맥 교환 오버헤드는 작아짐
      • 대기 중인 프로세스의 대기 시간 증가
      • 타임 슬라이스가 충분히 크다면 FCFS와 다를바가 없이 동작하게 됨
  • Multilevel Queue Scheduling
    • 프로세스를 여러 개의 큐로 분류
    • 각 큐 별로 다른 스케쥴링 알고리즘을 적용하고 큐 간에는 우선순위 지정
  • Multilevel Feedback Queue Scheduling
    • Multilevel Queue Scheduling의 확장판
    • 실행 상황에 따라 프로세스가 상, 하위 큐로 이동
    • CPU 사용량이 많으면 낮은 우선순위 큐로 보내고 기다린 시간이 길면 높은 우선순위 큐로 올림

동시성과 병렬성

  • 동시성
    • 여러 작업을 동시에 진행하는 것처럼 구성하는 기법
    • 단일 CPU 에서도 가능
    • 작업을 작은 단위로 쪼개고 이들 사이를 빠르게 오가며 수행
    • 실제로는 짧게 번갈아서 실행되지만 사용자 입장에서는 동시에 돌아가는 것처럼 보임
  • 병렬성
    • 여러 작업을 실제로 동시에 수행하는 것
    • 멀티코어 CPU에서 가능
    • 각 작업이 서로 다른 코어에서 동시에 실행

8. 뮤텍스와 세마포어

  • 뮤텍스
    • 상호 배제를 위한 잠금 매커니즘
    • 한 번에 하나의 스레드만 자원을 사용할 수 있게 함
    • lock, unlock 두 가지 상태 존재
    • 소유 개념이 존재(뮤텍스를 획득한 스레드만 해제 가능)
  • 세마포어
    • 정수 값을 가지며 자원의 개수를 추적
    • wait(자원 요청)와 signal(자원 반환)을 통해 접근 제어
    • 이진 세마포어의 경우 뮤텍스처럼 동작하나, 소유의 개념이 있지는 않음

Spin Lock

Lock을 기다리는 동안 CPU를 사용하며 루프를 도는 방식.

while (lock == LOCKED) {
	//대기
}

구현이 간단하고 컨텍스트 스위칭 없이 락을 대기할 수 있어 락을 짧게 보유할 때 효과적임. 하지만 락을 길게 보유하게 될 경우 CPU 자원을 지속적으로 사용하므로 비효율적


9. Deadlock

여러 프로세스가 서로의 자원을 점유한 상태로 다른 자원을 기다리면서 영원히 진행되지 못하는 상태

EX)
(프로세스가 자원을 공유하며 실행되는 상태)
1. 프로세스 A가 자원1을 점유하고 자원2가 필요한 상태
2. 프로세스 B가 자원2를 점유하고 자원1이 필요한 상태

A, B 모두 상대의 자원이 풀리기만 대기하며 영원히 멈춰있게 됨

발생 조건

데드락은 Coffman 조건이라는 4가지 조건을 모두 만족하여 발생. 하나라도 만족하지 않으면 데드락이 발생하지 않음.

  • 상호 배제
    • 자원을 한 번에 한 프로세스만 사용 가능
    • 상호 배제를 만족하지 않으면 여러 프로세스가 자원을 사용할 수 있으므로 데드락이 발생하지 않음
  • 점유 상태로 대기
    • 자원을 점유한 채 (해당 자원을 사용하지는 않고) 다른 자원을 대기
    • 점유 상태로 대기를 만족하지 않으면 애초에 프로세스가 자원을 점유하지 않으므로 데드락이 발생하지 않음
  • 선점 불가
    • 점유한 자원을 강제로 빼앗을 수 없음
    • 선점 불가를 만족하지 않으면 다른 프로세스가 사용 중인 다른 자원을 강제로 빼앗아 데드락이 발생하지 않음
  • 순환 대기
    • 프로세스들이 자원을 순환적으로 대기
    • 순환 대기를 만족하지 않으면 자원의 요청의 사이클이 생성되지 않는다는 뜻이므로 시간이 지남에 따라 언젠가는 자신에게 자원이 할당되게 되므로 데드락이 발생하지 않음

예방 방법

데드락의 발생 조건 중 하나를 시스템적으로 제거하여 데드락이 절대 발생하지 않도록 하는 방법.

EX)
자원 요청 시 모든 자원을 모두 확보 후 실행하도록 시스템 레벨에서 조정

회피 방법

OS가 데드락이 발생했는지 여부를 지속적으로 검사하며 데드락이 발생하지 않도록 회피하는 방법

  • 은행원 알고리즘
    • 은행 대출 심사 과정에서 이름이 유래
    • 자원을 허용했을 때를 시뮬레이션한 후 데드락이 발생하지 않는다면 자원을 할당
    • 프로세스의 자원 요구량을 미리 알아야하고 자원을 반드시 반납 해야함, 자원 사용 효율이 낮음 등의 이유로 현대 OS에서는 사용되지 않음
  • Wait-Die
    • 프로세스에 타임 스탬프를 부여하고 타임 스탬프가 작을 수록(나이가 많을 수록) 우선순위를 높이는 방식
    • A 프로세스가 B 프로세스의 자원을 요청하는 경우
      • A 프로세스가 더 늙었다면 A는 대기
      • A 프로세스가 더 젊다면 A는 죽은 후 추후 다시 시작
    • 오래된 프로세스만이 대기가 가능하므로 사이클이 발생하지 않아 데드락의 발생을 원천 차단
  • Wound-Wait
    • Wound-Wait와 동일하지만 타임 스탬프가 클 수록 우선순위를 높이는 방식

탐지 및 회복 방법

데드락은 언제든 발생할 수 있다고 가정, 주기적으로 데드락을 검사하고 발생 시 처리.
처리 방법은 프로세스 강제 종료, 자원 강제 회수 등의 방법이 있음

현대 OS의 경우

현대 OS는 데드락을 무시하고 프로그래머의 책임으로 넘김.

데드락의 해소하는 과정의 오버헤드가 너무 큼 + 시스템 자원 구조가 현대에서는 너무나도 복잡함 + 실질적인 데드락 가능성이 극히 낮기 때문.

다만 커널 내부, 중요한 자원을 관리할 때는 제한적으로 데드락 방지, 탐지 기법을 제한적으로 사용

Wait-Free와 Lock-Free

데드락 없는 동시성 보장이 필요할 때 사용하는 알고리즘들을 의미. 락을 사용하지 않고 원자적 연산(CAS)를 사용하여 구현

  • Wait-Free
    • 모든 스레드가 유한한 단계 안에 작업 완료 가능 -> 모든 스레드는 무한히 대기하지 않음
    • 일반적으로 더 복잡하고 구현 비용이 큼
  • Lock-Free
    • 시스템 전체적으로 항상 적어도 한 스레드가 진행 가능 -> 시스템 전체가 데드락 없이 전진 가능
    • 일부 스레드에서는 기아 상태 발생 가능

wait-free가 이상적인 상황이긴 하나, 이를 구현하기가 힘들고 성능 측면에서도 떨어지는 경우가 많으며 일반적인 경우 lock-free로도 충분하기 때문에 wait-free를 적용하는 경우는 드묾


10. 컴파일

컴파일은 소스 코드를 기계가 이해할 수 있도록 번역하는 과정이다. 컴파일은 아래와 같은 과정을 통해 수행된다.

  • 소스 코드 작성
    • 사람이 이해할 수 있는 언어로 프로그램을 작성
  • 컴파일
    • 컴파일러가 소스 코드를 읽어 기계어로 변환
    • 구문 오류나 타입 오류가 있으면 이 시점에서 잡아냄
  • 링크
    • 소스코드에서 사용한 여러 파일, 라이브러리를 하나의 실행 파일로 묶음
  • 로드
    • 운영체제가 실행 파일을 메모리에 올려 실행을 준비
  • 실행
    • CPU가 메모리에 적재된 명령어를 읽고 실행

컴파일과 인터프리트

  • 컴파일
    • 소스 코드를 컴파일러가 한 번에 기계어 코드로 변환 후 실행
    • 프로그램 실행 속도가 빠름
    • 실행 전 컴파일 단계에서 오류를 잡을 수 있음
  • 인터프리트
    • 소스 코드를 한 줄씩 읽고 즉시 해석하여 실행
    • 실행 속도가 컴파일보다 느림
    • 코드 수정 시 즉시 실행 가능

컴파일과 인터프리트 방식을 둘 다 사용하는 하이브리드 방식 언어(대표적인 것이 Java)도 존재

JIT 컴파일

프로그램을 실행하는 런타임 시점에서 소스 코드를 기계어로 컴파일하는 기법을 의미.

JIT 컴파일을 사용하는 대표적인 언어는 자바. 자바는 .java 파일을 컴파일하여 바이트코드(.class) 파일로 만든 후 JVM이 .class 파일을 해석(인터프리팅)하며 실행하게 됨.

이때 JVM 내부에 JIT 컴파일러가 존재하여 자주 실행되는 코드 부분은 JIT 컴파일하여 빠르게 실행하게 됨.

컴파일은 인터프리트와 달리 한 번에 모든 코드를 번역하므로 반복 실행되는 코드에 JIT 컴파일을 적용시키면 속도가 향상되는 효과를 누릴 수 있음.

또한 런타임 중에 실행되므로 정적 컴파일보다 더 효율적인 최적화가 가능

시스템 콜과 로더

  • fork()
    • 현재 프로세스를 복제하여 새로운 프로세스를 생성
    • 부모 프로세스의 메모리를 그대로 복사
    • 새 프로세스는 부모와 동일한 상태로 시작하여 다른 프로그램을 로드하지 않음
    • 결론적으로 2개의 프로세스가 실행
  • exec()
    • 현재 프로세스의 메모리 공간을 완전히 새 프로그램으로 덮어쓰기함
    • 현재 프로세스의 메모리를 해제하고 로더를 이용하여 새 프로세스를 적재한 후 실행
    • 실질적으로 OS의 로더를 실행
    • 결론적으로 1개의 프로세스가 실행

11. IPC

IPC는 하나의 프로세스가 다른 프로세스와 데이터를 주고 받기 위해 사용하는 기법이다. 프로세스는 각각 독립적인 메모리 공간을 가지기 때문에 데이터를 직접 공유할 수 없어 IPC를 사용하여 정보를 교환하거나 데이터를 공유한다.

  • 파이프
    • 익명 파이프
      • 부모-자식 프로세스 간 데이터 전달에 사용
      • 일방향 통신
      • 같은 시스템 안에서만 사용 가능
    • 명명 파이프
      • 관련 없는 프로세스 간 데이터 전달에 사용
      • 양방향 통신
  • 메시지 큐
    • 운영체제가 관리하는 큐에 데이터를 넣고 꺼내는 방식으로 통신
    • 여러 프로세스가 메시지를 주고받기 좋음
    • 양방향 통신
  • 공유 메모리
    • 두 프로세스가 같은 메모리 영역을 공유하여 데이터를 주고 받음
    • 가장 속도가 빠른 IPC 기법
    • 대용량 데이터 전달에도 유리
    • 여러 프로세스가 읽고 쓰는 과정에서 충돌 발생 가능(따라서 세마포어, 뮤텍스 등의 기법을 사용하는 것을 권장)

12. Thread Safe

멀티스레드 환경에서 여러 스레드가 동시에 같은 데이터에 접근하더라도 프로그램의 동작이 올바르고 일관되게 유지되는 상태를 의미

Thread Saft하지 않으면 멀티 스레드 환경에서 데이터가 손상되어 race condition이 발생할 수 있음

임계 영역에 락을 사용하거나 원자적 연산을 제공하는 것으로 달성 가능.

원자적 연산

실제 우리가 수행하는 많은 연산은 사실 여러 개의 명령어가 하나로 합쳐져서 실행되게 됨.

예를 들어 C = A + B를 수행한다고 하면 (1) A와 B의 값을 메모리에서 읽어들인 후 (2) 실제 연산을 수행하고 (3) 이를 C에 할당하는 것으로 동작하게 됨.

만일 3의 과정을 수행하기 전 다른 스레드가 C의 값을 변경했을 경우 의도치 않은 결과가 발생할 수 있음.

따라서 원자적 연산에서는 2를 수행한 후 현재 C의 값이 처음과 동일했을 때만 3을 수행하고 그렇지 않으면 수행하지 않음으로서 원자적인 연산이 가능하도록 함

Peterson 알고리즘

두 프로세스가 임계 영역에 동시에 들어가지 않도록 보장하는 동기화 알고리즘

두 개의 공유 변수(flag 배열, turn)을 사용하여 임계 영역에 진입하기 전 의사를 표시하고 상대방의 상태를 고려하여 진입 여부를 결정

  • flag 배열
    • 각 프로세스가 임계 영역에 들어가고 싶다는 의사를 나타냄
  • turn 변수
    • 상대방에게 우선권을 주는 변수
//의사 코드

flag[0] = true //임계 구역 진입 의사표시
turn = 1 //상대방에게 양보
while (flag[1] && turn == 1) {대기} //상대방이 사용 중이므로 대기함

//임계구역

flag[0] = false //임계 구역을 지났으니 flase로 설정

두 개 초과의 프로세스에는 적용하기 힘들고 스핀락을 사용하기 때문에 CPU 자원을 낭비하게 됨


13. Thread Pool, Monitor, Fork Join

  • Thread Pool
    • 여러 개의 스레드를 사용할 것을 대비하여 미리 스레드를 여러 개 생성시켜 스레드의 풀을 구성해두는 것
    • 매번 스레드를 생성, 소멸하면 리소스가 많이 들기 때문에 풀에서 스레드를 가져다쓰고 다 썼다면 풀에 반납하는 구조
    • CPU 바운드 작업 위주라면 스레드의 개수를 CPU 코어의 수와 비슷하도록 설정
    • IO 바운드 작업 위주라면 스레드의 개수를 코어 개수보다 더 늘려도 무방
  • Monitor
    • 여러 스레드가 공유 자원에 안접하게 접근할 수 있도록 보장하는 동기화 기법
    • 객체 단위의 락을 사용해 한 번에 하나의 스레드만 모니터에 들어와 작업하도록 제어함
    • 자바의 경우 synchronized 블록이 모니터 락임
  • Fork Join
    • 큰 작업을 작은 단위로 쪼개(fork) 병렬 처리한 후 그 결과를 합치는(join) 병렬 프로그래밍 패턴
    • 큰 데이터 집합이나 복잡한 계산을 병렬 분할 처리할 때 사용

데이터 정렬 전략

특정 데이터를 정렬해야 할 경우 고려해야할 요소들에 따라 정렬 알고리즘을 결정

만일 데이터가 클 경우 메모리에 모든 데이터를 로드할 수 없을 수 있으므로 병합 정렬 기반의 외부 정렬을 사용

데이터가 메모리 내부에 있으면서 속도가 중요하다면 평균적으로 가장 빠른 속도를 자랑하는 퀵 소트 사용

그게 아니라면 대부분의 경우 파이썬이나 자바의 기본 정렬 알고리즘인 팀 소트를 사용하면 됨


14. 캐시 메모리, 메모리 계층성

  • 캐시 메모리
    • CPU 내부에 존재하는 매우 작고 빠른 메모리
    • CPU가 자주 사용하는 데이터나 명령어 등을 임시로 저장하여 주 메모리까지 접근하지 않고 CPU 단에서 데이터에 접근할 수 있게 함
    • 캐시가 꽉 찼다면 LRU, FIFO, 랜덤 등의 알고리즘을 사용하여 데이터를 내보냄
    • 아키텍처 레벨에서는 반대로 느린 디스크보다 상대적으로 빠르다는 측면에서 메모리에 저장되어 있는 데이터들을 캐시 메모리로 지칭하기도 함
  • 메모리 계층성
    • 메모리는 속도, 용량, 비용 간의 trade-off가 존재하여 메모리가 계층적으로 구성되게 됨
    • 가장 빠른 속도를 가진 메모리만 쓰면 좋겠지만 용량이 작고 비용이 크기 때문에 많이 쓸 수가 없기 때문
    • 레지스터 -> 캐시 메모리 -> 주 메모리 -> 보조 기억장치로 구성

종류

  • L1 캐시
    • 가장 빠르고 가장 작은 캐시
    • CPU 코어 내부에 직접 내장
    • 데이터 캐시와 명령어 캐시로 나뉨
  • L2 캐시
    • L1보다 크고 약간 느림
    • CPU 코어 내부에 있거나 코어 별로 별도로 존재
  • L3 캐시
    • L1, L2보다 훨씬 크고 느림
    • CPU 내 여러 코어가 공유

메모리 간 동기화 기법

  • Write-Through
    • 캐시에 쓰면서 동시에 메모리도 바로 갱신
    • 일관성 유지가 쉽지만 느림
  • Write-Back
    • 캐시 내 데이터만 수정 후 해당 블록이 교체될 때 메모리에 반영
    • 빠르지만 일관성 관리가 필요

매핑 방식

  • 직접 매핑
    • 각 메모리 블록이 캐시 내 한 위치에만 대응
    • 단순하지만 충돌 가능성 존재
  • 연관 매핑
    • 캐시 내 모든 위치에 저장할 수 있도록 함
    • 캐시 내 위치를 찾아야 하므로 검색 비용이 발생
  • 셋-연관 매핑
    • 직접 매핑과 연관 매핑의 절충안
    • 캐시를 여러 개의 셋으로 나누고 각 셋에서 연관 매핑 수행
    • 충돌 확률을 낮추고 성능 균형을 맞춤

지역성

  • 시간 지역성
    • 한 번 접근한 데이터나 명령어를 가까운 미래에 다시 접근할 가능성이 높음
    • 따라서 캐시는 한 번 올라오면 당분간은 저장해둠
  • 공간 지역성
    • 한 데이터에 접근하면 그 주변에 있는 데이터에도 곧 접근할 가능성이 높음
    • 따라서 캐시는 데이터를 블록 단위로 가져와 한꺼번에 저장 -> 주변에 있는 데이터까지 한 번에 들고오게 됨

지역성의 원칙에 따라 캐시를 저장하면 캐시 히트율을 높일 수 있고 전체적인 성능 향상으로 이어짐

만일 2차원 배열을 접근한다고 하자. 메모리 구조에서 2차원 배열은 행 단위로 연속해서 저장하게 된다. arr[0][0]에 접근하게 되면 캐시는 arr[0]에 해당하는 데이터 블록을 전체로 들고와서 저장하게 되고 따라서 2차원 배열을 가로 우선으로 접근하게 되면 캐시 히트율이 높아 성능이 좋다.


15. 메모리의 연속할당

프로세스가 메모리에 올라갈 때 메모리 공간의 연속된 영역을 할당하는 기법. 이 방식의 할당 전략은 크게 3가지가 존재

  • First-Fit(최초 적합)
    • 메모리의 앞쪽부터 검색하여 프로세스가 적재될 수 있는 최초의 빈 공간에 프로세스를 할당
    • 탐색 속도가 빠르나 외부 단편화가 생기기 쉬움
  • Best-Fit(최적 적합)
    • 메모리 전체를 검색하여 프로세스 크기에 가장 딱 맞는 공간을 찾아 할당
    • 외부 단편화를 최소화하는 전략
    • 메모리 전체를 탐색해야 해서 탐색 시간이 오래 걸림
    • 결과적으로 메모리 단편이 흩어져 메모리 관리가 오히려 어려워질 수도 있음
  • Worst-Fit(최악 적합)
    • 메모리 전체를 검색하여 가장 큰 빈 공간을 찾아 프로세스를 할당
    • 큰 공간에 할당하므로 남은 빈 공간도 비교적 큰 조각으로 남아 큰 프로세스가 들어올 가능성읖 높임
    • 메모리 전체를 탐색해야 해서 탐색 시간이 오래 걸림
    • 작은 프로세스들이 들어오는 경우 큰 공간이 잘게 쪼개지므로 단편화 유발 가능

일반적으로 속도가 가장 중요하면 First-Fit, 메모리 활용률이 중요하면 Best-Fit을 사용한다. Worst-Fit은 속도도 느리고 외부 단편화가 더 심해지기도 하여 잘 사용하지 않음


16. Thrashing

시스템의 페이지 교체 작업이 과도하게 발생해 실제 작업을 거의 하지 못하고 대부분의 작업을 페이지 교체에만 소모하는 현상을 의미

프로세스가 필요로 하는 페이지보다 실제 메모리에 할당된 페이지가 부족할 때 발생한다. 페이지 부재율이 급격히 상승하고 CPU 사용률이 급격히 저하한다.

발생 시나리오

  • 프로세스 A는 자신의 작업을 위한 페이지를 OS에 요청함
  • 해당 페이지 부재로 페이지를 가져오지만 메모리에 공간이 부족, 프로세스 B의 페이지를 없애버리고 가져옴
  • 방금 없애버린 프로세스 B의 페이지가 필요해져서 다시 한 번 해당 페이지를 가져옴
  • 위와 같은 현상이 반복되며 Thrashing 발생

해결 방법

Thrashing은 일반적으로 메모리의 크기에 비해 동시에 실행하는 프로세스의 수가 너무 많을 때 발생함.

  • 작업 집합 모델
    • thrashing이 일어나지 않도록 프로세스가 필요로 하는 만큼의 프레임을 할당
    • 한 프로세스가 필요로 하는 프레임의 개수는 공간 지역성에 기반하며 이와 유사한 크기의 작업 집합을 사용
    • 한 번 모든 작업 집합이 메모리에 올라오면 해당 지역을 벗어나기 전까지 페이지 부재율이 크게 떨어짐
  • 페이지 부재 빈도
    • 페이지 부재율을 관찰하고 이에 따라 프로세스의 프레임 수를 조절
    • 바람직한 페이지 부재율의 범위를 설정
    • 실제 부재율이 이보다 크면 프레임을 추가 할당하고 적다면 프레임을 회수

17. 가상 메모리

실제 물리적 메모리의 용량을 넘어서는 메모리 공간을 제공하는 방식.

OS는 보조 기억장치의 일부 공간을 메모리처럼(스왑 공간) 사용하고 프로세스는 메모리의 실제 주소가 아닌 가상 주소를 사용하도록 함. 현재 필요한 데이터나 코드만 메모리에 올려두고 나머지는 보조 기억장치에 두었다가 필요할 때 불러옴(요구 페이징)

가능한 이유

  • MMU
    • 현대 CPU에 탑재되어 있는 메모리 관리 장치
    • 프로세스가 사용하는 가상 주소를 실제 물리 주소로 매핑
    • MMU는 실제 메모리 주소를 얻기 위해 TLB를 참조하고 TLB에 없을 경우 페이지 테이블에서 변환
  • OS
    • 프로세스마다 페이지 테이블을 관리하며 가상 주소와 물리 주소와의 매핑, 디스크에 저장되어 있는 페이지의 위치를 추적

하드웨어가 가상 주소 -> 물리 주소의 변환을 지원하며 OS이가 이 정보를 관리하고 있으므로 가상 메모리 기법이 구현 가능

페이지 부재 시퀀스

  • 페이지 참조
  • 페이지 부재 발생
  • 페이지 적재
    • 만일 페이지 테이블에 빈 곳이 없다면 페이지를 교체
    • 희생자 페이지를 선택하고 해당 페이지와 swap
  • 페이지 테이블 갱신
  • 명령어 재실행

페이지를 교체해야할 경우 알고리즘에 따라 희생자 페이지를 결정하게 된다.

  • FIFO 페이지 교체
    • 가장 먼저 적재된 페이지를 교체
    • 이해와 구현이 쉽지만 성능이 항상 좋지는 않음
  • 최적 페이지 교체
    • 앞으로 가장 오랫동안 사용되지 않을 페이지를 교체
    • 가장 낮은 페이지 부재율을 보장
    • 미래를 알 수 없으므로 실질적으로 사용은 불가
  • LRU 페이지 교체
    • 가장 오래 전에 사용된 페이지를 교체
    • 최근에 사용된 페이지는 앞으로도 사용될 가능성이 높다는 특성을 이용한 알고리즘
    • 각 페이지마다 마지막 사용 시각 유지 필요
    • 여러 OS에서 사용되며 실질적으로 가장 좋은 알고리즘으로 간주

페이지 크기

  • 작은 페이지 크기
    • 내부 단편화 감소
    • 작은 단위로 메모리를 교체하므로 실제 필요한 데이터만 메모리에 올릴 수 있음
    • 페이지 테이블의 크기는 커짐
  • 큰 페이지 크기
    • 페이지 테이블 작아짐
    • 디스크 IO가 효율적
    • 내부 단편화 증가
    • 캐시 효율성 저하
    • 공간 지역성이 높은 프로세스의 경우 페이지 부재율을 줄일 수 있음

18. 페이징과 세그멘테이션

운영체제의 메모리 관리 기법엔 총 3가지가 있다. 첫 번째는 연속 메모리 할당 기법이고(First-Fit, Best-Fit 등의 기법을 사용하는 기법) 그 다음이 페이징과 세그멘테이션이다.

연속 메모리 할당 기법은 한 프로세스 전체를 연속적인 주소 공간에 적재하는 방법이지만 페이징과 세그멘테이션은 프로세스를 분할 후 적재한다.

  • 페이징
    • 하나의 프로세스를 물리적인 단위인 페이지로 분할하고 각 페이지를 임의 위치에 적재
    • 먼저 물리 메모리를 동일한 크기로 분할한다. 이 단위를 프레임이라고 한다.
    • 이후 프로세스를 프레임과 동일한 크기로 분할하는데, 이 프로세스의 조각을 페이지라고 한다.
    • 각 프로세스의 페이지 적재 정보는 페이지 테이블에 기록된다.
  • 세그멘테이션
    • 하나의 프로세스를 논리적 단위인 세그먼트로 분할하고 각 세그먼트를 임의 위치에 적재
    • 페이지의 경우 크기가 동일하지만 세그먼트는 프로그램의 논리적 구조에 따라 나누기 때문에 크기가 다를 수 있음
    • 가변 크기의 블록을 사용하므로 외부 단편화 발생 가능

실제 주소 가져오기

  1. 가상 주소의 상위 비트를 잘라서 페이지 번호 추출하기
  2. 페이지 번호를 바탕으로 페이지 테이블에서 프레임 번호를 추출
  3. 프레임 번호와 페이지 오프셋을 이용하여 물리 주소를 구함

페이지 보호

페이지 테이블에는 각 프레임마다 보호 비트가 존재하여 read-write, read-only를 표시함. 이를 이용하여 수정 가능한 프레임인지 아닌지를 판별

또한 유효 비트가 존재하여 page의 합법성 여부를 표시 만일 유효하지 않다면 페이지 부재가 발생한 것


19. TLB

TLB는 CPU 내부에서 사용하는 캐시 하드웨어. 가상 주소 -> 물리 주소 변환을 빠르게 하기 위해 사용됨

페이징 기법에서는 가상 주소를 기반으로 페이지 테이블까지 확인한 후 물리 주소를 얻을 수 있음. 페이지 테이블로의 접근은 CPU 입장에서는 느린 작업임. 따라서 CPU 내부에서 이 정보만을 저장하는 캐시 하드웨어를 두어 주소 변환을 빠르게 하였는데 이 장치가 TLB임

CPU가 물리 주소가 필요할 경우 먼저 TLB를 살펴보고 TLB에 없을 경우 메모리에 존재하는 페이지 테이블에 탐색하여 해당 정보를 TLB에 적재함.

TLB 동기화

TLB는 CPU의 각 코어마다 별도로 존재함. 현대 CPU에는 여러 개의 코어가 존재하는데 만일 페이지 테이블이 갱신 될 경우 해당 페이지에 대한 정보를 담고 있는 TLB들이 모두 수정되어야 할 필요가 있음.

따라서 페이지 테이블에 수정이 발생하면 OS 커널은 인터럽트를 일으켜 TLB에서 해당 정보들을 삭제해버리는 과정을 거침. 이를 TLB shootdown이라고 한다.

TLB shootdown은 프로세스가 사용하는 메모리 영역의 주소가 바뀌거나, 페이지 테이블이 수정될 경우 발생함

TLB와 컨텍스트 스위칭

각 프로세스는 고유의 페이지 테이블을 가짐. 이때 컨텍스트 스위칭이 일어났다는 소리는 다른 프로세스가 실행된다는 의미이므로 현재 저장되어 있는 TLB의 정보는 무의미해짐.

따라서 컨텍스트 스위칭이 발생할 때 TLB의 내용은 모두 비워지게 됨


22. File Descriptor, File System

  • File Descriptor
    • 운영체제에서 프로세스가 파일을 식별하기 위해 사용하는 정수 번호
    • 프로세스가 입출력 자원을 열면 OS가 프로세스의 파일 디스크립터 번호 중 사용하지 않는 가장 작은 값을 할당
    • 이후 프로세스는 해당 번호를 통해 파일에 읽기/쓰기 작업을 수행
  • File System
    • 파일과 디렉터리를 저장하고 관리하는 데이터 구조와 규칙의 집합
    • 파일의 저장 위치 관리, 파일 정보 관리, 저장 공간을 효율적으로 할당 및 해제

INode

유닉스 계통 OS의 파일 시스템에서 사용하는 자료구조. 파일 시스템에 대한 정보를 가지고 있으며 파일도 한 개의 아이노드를 지님

아이노드는 소유자 그룹, 접근 모드, 파일 형태, 생성 시간, 수정 시간 등 파일에 대한 정보를 가지고 있음

FileReader

자바의 파일 입출력 객체들은 OS의 파일 디스크립터를 사용해 파일을 염.

새로운 FileReader 객체가 생성되면 시스템 콜을 호출하여 파일 디스크립터 혹은 파일 핸들을 얻음.

이후 FileReader의 메소드 호출 시 다시 한 번 시스템 콜을 호출하여 파일 데이터를 읽어오게 됨


23. 동기, 비동기, 블로킹, 논블로킹

  • 동기
    • 작업을 요청한 쪽이 요청한 작업의 완료를 대기
    • 작업의 수행 순서가 보장
  • 비동기
    • 작업을 요청한 쪽이 요청한 작업의 완료를 대기하지 않음
    • 작업의 수행 순서가 보장되지 않음
  • 블로킹
    • 작업을 요청한 쪽이 자신의 작업을 수행하지 않음
  • 논블로킹
    • 작업을 요청한 쪽이 자신의 작업을 수행함

동기 + 논블로킹, 비동기 + 블로킹은 논리적으로 모순이기 때문에 일반적으로는 사용되지 않는다.


0개의 댓글