키워드 정리
커널
- 운영체제의 핵심 기능을 담당하는 부분
- 프로세스 관리, CPU/메모리 등 자원 접근 및 할당, 파일 시스템 관리...ㄴ
- 운영체제 서비스 중 커널에 포함되지 않는 서비스도 있음
사용자 / 커널 모드
- 사용자 프로그램이 하드웨어 자원에 접근하기 위해선, 운영체제를 통해서 접근해야 함
- CPU, 메모리, 하드 디스크 등 하드웨어 자원의 보호 목적
- 운영체제 코드를 실행하여 접근이 이루어짐
- CPU는 명령어를 사용자 모드 OR 커널 모드로 실행 가능
- 플래그 레지스터 내 슈퍼바이저 플래그의 값에 따라 모드 결정
- 사용자 모드: 운영체제 서비스를 제공받을 수 없음
- 일반적인 사용자 프로그램이 실행되는 모드
- 커널 영역의 코드를 실행할 수 없음
- 입출력 명령어 등, 하드웨어 자원에 접근하는 명령어를 실행할 수 없음
- 커널 모드: 운영체제 서비스를 제공받을 수 있음
- 운영체제 코드가 실행되는 모드
- 커널 영역의 코드를 실행할 수 있음
시스템 콜
- 운영체제 서비스를 제공받기 위해, 사용자 -> 커널 모드로 전환하는 요청
- 사용자 모드로 실행되는 프로그램은, 시스템 콜을 커널 모드로 전환하여 운영체제 서비스를 제공받음
- 일종의 인터럽트: 하드웨어 인터럽트(타이머 인터럽트 등)와 비교해서 소프트웨어 인터럽트로도 불림
- e.g., 하드 디스크에 데이터를 저장하는 시스템 콜
- 사용자 모드로 실행 시, 응용 프로그램은 하드 디스크에 직접 접근 불가
- (1) 하드 디스크에 데이터를 저장하는 시스템 콜 요청 -> 커널모드로 전환
- (2) 하드 디스크에 데이터를 저장하는 운영체제 코드를 실행해, 하드 디스크에 접근
- (3) 접근이 끝나면 사용자 모드로 복귀해 프로그램 실행 재개
시스템 콜의 종류
| 호출 | 기능 |
|---|
fork() | 새 자식 프로세스 생성 |
execve() | 프로세스 실행 (현재 프로세스의 메모리 공간을 새로운 프로그램의 내용으로 덮어씌움) |
exit() | 프로세스 종료 |
wait() | 자식 프로세스의 종료까지 대기 |
open() | 파일 열기 |
close() | 파일 닫기 |
read() | 파일 읽기 |
write() | 파일 쓰기 |
인터럽트 / 예외
| 예외 | 원인 | 동기 / 비동기 | 핸들러 반환 후 |
|---|
하드웨어 인터럽트 (Interrupt) | 외부 장치의 신호(타이머, 입출력장치) | 비동기 | 다음 명령어로 진행 |
소프트웨어 인터럽트 (trap) | 시스템 콜 등 의도된 예외 | 동기 | 다음 명령어로 진행 |
폴트 (fault) | 복구 가능한 오류(페이지 폴트) | 동기 | 오류 복구 시, 현재 명령어 유지 복구 실패 시, 프로그램 종료 |
중단 (abort) | 복구 불가능한 치명적 오류 | 동기 | 프로그램 종료 |
- 비동기: 예외의 원인이 명령어 실행이 아님. 통칭 인터럽트
- 동기: 예외의 원인이 명령어 실행임. 통칭 예외
- 예외 발생 시 운영체제의 인터럽트 핸들러가 실행됨
- 특정 예외가 발생했을 때, 이에 처리 및 대응하는 코드
- 운영체제 코드를 실행해야 하므로, 사용자 모드 -> 커널 모드로 전환이 이루어짐
- 인터럽트 발생 및 처리 과정
- (1) CPU가 예외를 감지 후 예외 번호를 계산
- (2) Interrupt Descriptor Table에서 예외 번호에 해당하는 예외 핸들러 주소를 확인해 진입
- (3) 커널 모드로 전환 -> 커널 스택에 리턴 주소, 현재 레지스터 값 등 푸시
- (4) 예외 핸들러 코드 실행, 예외 상황을 처리
- (5) 커널 스택에 저장된 값을 팝해 복원한 뒤 리턴 주소 사용해 사용자 모드로 복귀
- 하드웨어 인터럽트의 경우, 인터럽트 플래그가 활성화되어 있을 때만 인터럽트 요청 수용
- 단, 정전이나 하드웨어 고장으로 인한 인터럽트는 막을 수 없음 - 무조건 수용(non-maskable interrupt)
세그멘테이션 / 페이지 폴트
- General Protection Fault (예외번호 13)
- 흔히 말하는 Segmentation Fault는 General Protection Fault에 포함됨
- 정의되지 않은 영역 (e.g.,
NULL 포인터) / 읽기 전용 코드 영역 / 사용자 영역에서 커널 메모리 접근 등, 잘못된 메모리 접근 시 발생
- 일반적으로 복구하려고 시도하지 않음, 프로그램 종료 선택
- Page Fault (예외 번호 14)
- CPU가 가상 주소에 접근할 때, 해당 주소가 현재 메모리에 매핑되어 있지 않을 때 발생
- 리눅스 기준, MMU가 가상주소를 번역하는 과정에서 Page Fault 발생 시
- (1) 가상주소가 유효한 메모리 영역에 위치해 있는지 판단
- 메모리 영역은 heap, stack, text, mmap을 뜻함 -> 해당 주소가 어떤 영역에 속하는지 먼저 확인
- 유효하지 않은 경우, Segmentation Fault(GPF의 일종) 발생
- NULL 포인터 참조 시 무조건 발생, 미할당 포인터 참조 시에도 유효한 영역 외일 수 있으므로 발생 가능
- (2) 해당 영역 내 접근 권한이 있는지 판단
- 접근 권한이 없으면 General Protection Fault로 프로세스 중단
- e.g., 코드 영역은 쓰기 연산 금지 -> 해당 영역에 쓰기 시도 시
- e.g., 사용자 프로세스는 커널 영역의 데이터를 읽지 못함 -> 해당 영역에 접근 시도 시
- (3) 위 조건 모두 충족 시, 정상적인 Page Fault 루틴이 처리
- 새로운 물리 프레임으로 스와핑하고, 페이지 테이블을 갱신
사용자 스택, 레지스터
- 사용자 스택
- 함수 호출 시 필요한 정보를 저장하는 메모리 내 공간
- 스택 프레임: 각 함수가 사용하는 스택 내 저장 공간
- e.g., 인수의 경우 최대 6개까지 레지스터로 전달 -> 나머지는 스택으로 푸시
- 단, 시스템 콜의 경우 사용자 스택이 아닌 커널 스택에 값들이 푸시됨에 유의
- rax 레지스터: 리턴 값을 저장하는 용도의 범용 레지스터
기타 키워드
- file descriptor: 운영체제가 파일 등 입출력 자원을 식별하기 위해 사용하는 정수 값
open() 시스템 콜로 할당되며, 이 값을 통해 read(), write() 등 시스템 콜로 자원에 접근
- atomic operation: 도중에 중단되거나 다른 작업과 섞이지 않고, 한 번에 완전히 수행되는 연산
write(), read() 등 시스템 콜은 atomic하게 동작하도록 구현됨 -> race condition 없이 사용 가능
- 핀토스에서 global lock을 걸어주고 시스템 콜을 처리했던 이유는, 이를 구현하기 위해서
- 32bit OS vs 64bit OS
- 32비트, 64비트 -> 가상 주소값을 몇 비트로 나타내는지
- 32비트 OS에선 최대 (2^32)개 주소 -> 4GB 메모리 주소 가능
- 64비트 OS에선 최대 (2^64)개 주소 -> 수십~수백 TB까지 가능
- 더 많은 데이터 사용과 빠른 데이터 사용에 유리
퀴즈 정리
커널 모드에서 실행될 수 있는 작업 및 사용자 모드에서 실행되지 않는 이유
- 커널 모드에서는 파일 입출력, 프로세스 스케줄링, 메모리 매핑 등 작업이 실행된다. 사용자 프로그램이 직접적으로 운영체제의 하드웨어 자원에 접근하는 것을 막기 위해, 보안과 안정성을 위해 사용자 모드에서는 실행되지 않는다.
- e.g., 다른 프로세스를 임의로 조작하거나, 할당되지 않은 메모리 공간에 접근하는 행위를 방지할 수 있다.
MLFQS를 사용하는 이유
- 프로세스의 우선순위가 고정되는 기존 스케줄러에서는, 우선순위가 높은 프로세스가 계속 CPU를 점유하면 낮은 프로세스가 전혀 실행되지 않는 기아 현상이 발생할 수 있다. MLFQS는 프로세스를 우선순위에 따라 여러 큐에 배치하는데, 일정 시간마다 우선순위를 재계산해 큐를 재조정하기 때문에 기아 현상을 방지할 수 있다.
fork vs exec
fork는 부모 프로세스를 복제하여 독립적인 자식 프로세스를 생성한다. exec는 현재 프로세스의 코드, 데이터 등 메모리 공간을 새로 실행할 사용자 프로그램의 내용으로 덮어씌우며, 새로운 프로세스를 생성하지 않는다.
유저 프로그램에 스택이 높은 -> 낮은 주소, 힙이 낮은 -> 높은 주소로 성장하도록 설계된 이유
- 메모리상 힙 영역과 스택 영역 사이 미할당 영역을 유동적으로 사용할 수 있게 하기 위해서이다. 예를 들어, 힙이 너무 커져도 스택이 아직 작으면, 중간 공간이 남아 있는 한 둘 다 확장 가능하다.
#ifdef 함수의 장단점
#if defined(매크로 상수명) 대신 #ifdef(매크로 상수명)이라고 줄여 사용할 수 있어 코드가 간결해진다. 하지만 #if 를 사용할 때와 달리, &&나 || 등 조건식을 사용하여 다른 조건문과 함께 사용하는 것이 불가능해진다.