운영체제란?
컴퓨터 사용자와 컴퓨터 하드웨어 사이에서 중개자 역할을 하는 프로그램이다.
운영체제의 목표
- 사용자 프로그램 실행 및 사용자 문제 해결을 쉽게 함
- 컴퓨터 시스템 사용을 편리하게 함
- 컴퓨터 하드웨어를 효율적으로 사용함
→ 운영체제는 사용자 프로그램과 하드웨어 사이에 위치한 모든 소프트웨어이다.
Hardware (e.g., mouse, keyboard) <-> Operating System <-> User Program
OS is a resource manager and allocator.
- 하드웨어 액세스에 대한 충돌하는 요청 사이에서 결정
- 성능을 효율적이고 공정(fair)하게 사용하려고 함(하드웨어가 한정적이기 때문)
OS is a control program.
- 사용자 프로그램의 실행을 제어
- 오류와 부적절한 사용을 방지
Architecture Review
North Bridge
---------------------------------
| CPU(s) |
---------------------------------
| | | | |
| L1 Cache L2 Cache L3 Cache
| |
| Graphics |
| / \\ |
| GPU Graphics Memory |
| |
---------------------------------
| Memory |
---------------------------------
|
|
South Bridge
---------------------------------
| I/Os |
| / |
| Storage |
---------------------------------
North Bridge:
- CPU(s): North Bridge는 CPU와 직접적으로 연결되어 있으며, (L1, L2, L3)도 North Bridge를 통해 메모리에 접근한다.
- Graphics: 그래픽 카드(그래픽 처리 장치)는 North Bridge를 통해 메인 메모리에 접근한다.
- Memory: North Bridge는 메인 메모리와 직접 연결되어 있으며, CPU 및 다른 장치들이 메모리에 접근할 수 있도록 관리한다.
South Bridge:
- I/Os: South Bridge는 입출력(I/O) 장치들과 연결되어 있다. 이들은 South Bridge를 통해 메인 메모리에 접근한다.
- Storage: 저장 장치들은 South Bridge를 통해 메인 메모리에 접근한다.
모든 장치들은 메모리에 액세스하기 위해 경쟁하며, North Bridge 및 South Bridge는 이들 간의 접근을 조정하여 충돌을 방지하고 효율적인 데이터 전송을 보장한다.
Typical Memory Layout
Example: 64KB 메모리
- 2의 16제곱 바이트로, 주소는 0~65535 사이
- 모든 메모리 공간을 자유롭게 사용할 수 있는게 아니다.
- e.g., 전원을 켰을 때 실행되는 코드, 주변장치의 I/O 메모리 맵, BIOS, etc.
- 이미 차지하고 있는 공간을 제외한 나머지 Free Mememory만 사용 가능하다.
Communicating with Devices
CPU와 장치(GPU, 모니터, 키보드, etc)들이 통신하는 방법에는 폴링(polling)과 인터럽트(interrupts) 방식이 있다. 이 두 가지 방식을 모두 사용하는 경우도 있다.
- 폴링(Polling): 옛날 방식으로 다시 두 가지로 나뉜다.
- I/O Ports: I/O만을 위한 메모리 주소를 정해두어 CPU와 장치들이 공유하는 방식
- Memory mapping: 주소 공간의 어떤 영역에 CPU와 장치 들의 주소 공간을 메모리와 매핑하는 방식
- 인터럽트(Interrupts): 이벤트가 발생할 때만 시그널(signal)을 사용하여 통신한다. 계속 확인을 해야하는 폴링 방식을 개선하여 비동기적으로 수행된다.
I/O Ports
장치와 통신하는 가장 오래된 방법으로 16-bit 메모리 공간을 사용한다.
각 장치에는 주소 공간의 일부가 할당된다. (e.g., 0xF000~0xF03F)
CPU와 장치들은 가상 메모리 공간을 읽고 쓰면서 통신한다.
단점
- CPU가 모든 데이터 전송에 이용된다.
- e.g., 디스크에서 메모리로 데이터를 이동할 때, CPU가 각 I/O 포트에서 메모리로 16-bit 값을 복사해야 한다.
- 모든 I/O 포트들은 동기적으로 수행된다.
- e.g, 디스크가 데이터를 전송하려 할 때 CPU가 I/O 포트를 읽지 않을 수 있다.
→ 속도가 느리다.
Direct Memory Access (DMA)
처음 사용할 때만 I/O 포트와 메모리가 직접 통신하고 이후 데이터를 옮길 때는 DMA 방식을 이용한다.
Interrupts
인터럽트는 실행 중인 코드에서 인터럽트 핸들러로 CPU 제어를 전달한다.
- 각 인터럽트는 번호로 할당되며, 이 번호는 인터럽트 벡터 테이블(Interrupt Vector Table)의 인덱스 역할을 한다.
- 인터럽트 벡터 테이블은 인터럽트 번호를 해당 인터럽트를 처리하는 핸들러로 매핑하는 역할을 한다.
인터럽트가 발생하면 문맥 교환(context switch)이 일어난다.
- 인터럽트 발생 직전 CPU의 상태(프로그램 카운터, 레지스터 값 등)를 저장한다.
- 인터럽트 핸들러가 실행을 완료하면 저장된 CPU의 상태를 복원하여 이전에 실행 중이던 코드로 복귀한다.
Today’s Servers/Desktops
- 균일성 및 호환성 향상
- 이전에는 다양한 제조사들이 각자의 하드웨어 표준을 따라 제품을 제작했지만, 현재는 표준화된 하드웨어 인터페이스를 따르는 추세이다.(Apple과 PC는 동일한 내부를 사용하여 상호 운용성이 높아짐)
- 강력한 산업 그룹들이 하드웨어 호환성을 위한 엄격한 표준을 제정했다. JEDEC(Joint Electron Device Engineering Council), PCI SIG(PCI Special Interest Group), USB-IF(USB Implementors Forum) 등
- IBM-PC 호환성의 소멸
- 이전에는 대부분의 개인 컴퓨터가 IBM-PC 호환성을 갖추고 있었으나, 현재의 서버 및 데스크톱 컴퓨터는 x86-64 아키텍처를 기반으로 한다.
- 이전에는 BIOS(Basic Input/Output System)를 사용했지만, 현재는 UEFI(Unified Extensible Firmware Interface)가 대체되어 사용된다.
Booting up
What Happens After You Push Power?
- BIOS 시작: 컴퓨터가 부팅되면 먼저 BIOS(Basic Input/Output System)가 시작된다. (BIOS는 컴퓨터의 기본 하드웨어 설정 및 초기화를 담당)
- CMOS 설정 로드: BIOS는 CMOS(Complementary Metal-Oxide-Semiconductor) 메모리에서 저장된 설정을 로드한다. (하드웨어 구성, 부팅 디바이스 순서, 시간 및 날짜 등)
- 장치 초기화: BIOS는 시스템에 연결된 모든 장치를 초기화하고 감지한다. (메모리, 그래픽 카드, 저장 장치, 네트워크 인터페이스 등)
- POST 실행: POST(Power-On Self Test)는 컴퓨터의 하드웨어 구성 요소가 정상적으로 작동하는지 테스트한다. (메모리, 그래픽 카드, CPU 등의 중요한 하드웨어를 점검하여 오류가 있는지 확인)
- 부트스트랩 시퀀스 시작: 부트스트랩 시퀀스는 운영 체제를 로드하기 위해 실행되는 프로세스이다. BIOS는 부팅 디스크(일반적으로 하드 디스크 또는 SSD)의 부트로더를 로드하고 실행한다. 부트로더는 운영 체제의 커널을 로드하고 실행하여 최종적으로 데스크톱 환경이나 로그인 화면을 표시한다.
Starting the BIOS
BIOS(Basic Input/Output System)는 일종의 미니 운영 체제로 간주될 수 있으며 ROM(Read-Only Memory) 칩에 기록된다. PC 전원을 켜자마자 BIOS는 다음 일들을 수행한다.
- BIOS 칩으로부터 코드 실행
- PC는 켜자마자 BIOS 칩에 저장된 코드가 실행된다.
- 컴퓨터의 하드웨어 초기화 및 부팅 프로세스를 관리한다.
- BIOS 코드 복사
- BIOS 칩에 있는 코드를 RAM의 낮은 주소(e.g. 0xFF)으로 복사한다.
- 부트로더 실행 지시
- 메모리의 특정 위치(보통 0xFFFF0)에 부트로더 실행을 지시하는 명령을 기록한다.
jmp 0xFF
(16-bit) 인스트럭션은 RAM의 0xFFFF0 (2^20 - 16)에 쓰여진다.
- x86 CPU들은 항상 부팅 시 EIP 레지스터에 0xFFFF0을 설정하고 실행한다.
BIOS는 시스템의 하드웨어가 정상적으로 작동하는지 확인하고, 간단한 저수준 장치의 드라이버를 설치한다. 또한 저장 장치를 스캔하여 Master Boot Record(MBR)를 찾는다. 그리고 MBR을 RAM으로 로드하고 실행하도록 CPU에 지시한다.
Loading Settings from CMOS
BIOS는 사용자가 구성할 수 있는 설정 옵션(e.g., 부팅 순서, CPU 속도 조절)을 포함하고 있는데, 이를 CMOS(Complementary Metal-Oxide-Semiconductor) 메모리에 저장한다. CMOS 메모리는 전원이 꺼져도 설정이 유지될 수 있도록 별도의 배터리를 통해 전원을 공급받는다. PC가 부팅될 때 BIOS는 CMOS 메모리에 저장된 설정을 로드하여 시스템을 초기화한다.
Initialize Devices
- 하드웨어들을 스캔하고 초기화 한다. (키보드, 마우스, 비디오, 부팅가능한 저장 장치)
- 메모리에 인터럽트 핸들러를 설치하고 인터럽트 벡터 테이블을 빌드한다.
- 확장 카드(e.g., 비디오 카드, SCSI 카드)에 자체 BIOS가 있는 경우, 실행하여 해당 장치를 초기화한다.
- POST(Post Power-On Self Test) 테스트를 실행한다. POST 테스트는 RAM을 점검하여 오류를 감지한다.
Bootstrapping
부트스트래핑(bootstrapping)은 실제 운영체제를 찾아서 로드하는 과정이다. 먼저 BIOS는 모든 부팅 가능한 장치를 찾는다. 그리고 각 장치에서 MBR을 찾으려고 시도한다. MBR에는 실제 OS를 로드할 수 있는 부트로더가 있다.
The Master Boot Record (MBR)
마스터 부트 레코드(MBR)은 저장 장치의 섹터1(주소 0)에 작성된 특별한 512바이트 파일이다.
- 446바이트의 부팅 가능한 코드가 있다.
- 각각 16바이트 크기의 최대 4개 파티션 정보를 갖는다.
- 마지막 2바이트는 MBR의 유효성을 확인하는 매직넘버(Magic Number)를 갖는다. 이 값은 0x55AA으로 설정되어 있다.
- MBR은 전체 운영체제를 포함하기에 너무 작아서 체인 로딩(chain-loading) 시퀀스로 시작된다. MBR이 부트로더를 실행하여 다른 파티션에 있는 부트로더를 로드하고, 그 부트로더가 실제 운영체제를 로드한다.
Address(Hex) | Address(Dec) | Description | Size (Bytes) |
---|
0x000 | 0 | Bootstrap code area | 446 |
0x1BE | 446 | Partition Entry #1 | 16 |
0x1CE | 462 | Partition Entry #2 | 16 |
0x1DE | 478 | Partition Entry #3 | 16 |
0x1EE | 494 | Partition Entry #4 | 16 |
0x1FE | 510 | Magic Number | 2 |
| | Total: | 512 |
Example Bootloader: GRUB
- Grand Unified Bootloader
- Used with Unix, Linux, Solaris, etc.
INTERFACE TO APPLICATIONS
System Call Interface
시스템 호출 테이블(System call table)
- 운영 체제의 API를 추상화하는 데 사용되는 중간 계층이다.
- 테이블은 항상 고정된 위치에 있다.
- 각 운영체제 API에는 테이블에서 고유한 인덱스가 할당된다.
- 프로그램은 함수 주소를 하드코딩하는 대신 이 테이블을 통해 API에 접근할 수 있다.
Traps: Software Interrupts
소프트웨어에서 생성되는 인터럽트 중 하나로, 주로 시스템 호출을 할 때 자주 사용된다.
Example:
mov eax, 1
: 시스템 호출 번호를 EAX 레지스터에 설정한다.
xor ebx, ebx
: EBX 레지스터를 0으로 설정한다.
int 0x80
: 소프트웨어 인터럽트를 발생시킨다.(0x80은 시스템 호출을 위한 인터럽트 번호)
(Simplified) System Call Example
- 소프트웨어가 int 0x80 명령을 실행하고 EIP를 push: 소프트웨어가 시스템 호출을 수행하기 위해
int 0x80
과 같은 인터럽트 명령을 실행한다. 이 때 CPU는 현재 실행 중인 명령의 주소를 스택에 푸시합니다.
- CPU가 IVT에서 핸들러를 찾아 전달: CPU는 인터럽트 벡터 테이블(IVT)에서 해당 인터럽트 번호에 대한 핸들러 주소를 찾는다. 이 핸들러는 운영 체제의 시스템 호출을 처리하는데 사용된다.
- CPU가 제어를 운영 체제 핸들러에 전달: CPU는 찾은 핸들러로 제어를 전달하여 운영 체제의 시스템 호출을 처리할 수 있도록 한다.
- 핸들러가 syscall 테이블에서 EAX를 찾음: 운영 체제의 핸들러는 EAX 레지스터의 값을 사용하여 시스템 호출을 식별한다. EAX에는 시스템 호출의 번호 또는 식별자가 들어 있다.
- API 코드로 점프: 핸들러는 시스템 호출 테이블에서 해당 번호에 대한 API 코드의 주소를 찾는다. 그런 다음 해당 API 코드로 점프하여 실제 시스템 호출을 수행한다.
CPU Support for System Calls
많은 CPU들은 시스템 호출을 위한 인스트럭션들을 제공한다. 예를 들어 x86 아키텍처에서는 시스템 호출을 인터럽트를 통해 시작할 수 있다. 리눅스의 경우, x86 아키텍처에서 int x86
이 시스템 호출 인터럽트이다. EAX 레지스터는 원하는 API의 테이블 인덱스를 설정한다.
EAX | Function |
---|
1 | sys_exit |
2 | sys_fork |
3 | sys_read |
4 | sys_write |
KERNEL
Towards a Kernel
커널(Kernel)은 컴퓨터에서 항상 실행되는 유일한 프로그램으로, 보통 부트로더에 의해 로드된다. 커널은 운영 체제의 핵심 부분으로, 하드웨어와 다른 소프트웨어 간의 인터페이스를 제공하고 시스템 자원을 관리한다.
Kernel Features
- 장치 관리
- 필수: CPU 및 메모리 관리
- 선택적: 디스크, 키보드, 마우스, 비디오 등의 장치 관리
- 시스템의 주요 자원을 관리하고, 하드웨어 장치와의 상호 작용을 관리한다.
- 프로그램 로딩 및 실행
- 프로그램을 메모리로 로드하고 실행하는 기능을 제공한다.
- 시스템 호출과 API
- 응용 프로그램이 운영 체제의 기능을 호출할 수 있도록 하는 시스템 호출 인터페이스를 제공한다.
- 보호 및 오류 허용성
- 프로그램 충돌이 컴퓨터 전체의 충돌로 이어지지 않도록 보호한다.
- 오류 처리 및 회복 기능을 제공하여 시스템의 안정성을 유지한다.
- 보안
- 인증된 사용자만 로그인할 수 있도록 보안 기능을 구현한다.
- 접근 권한 및 권한 관리를 통해 시스템의 안전성을 유지한다.
Architecting Kernels
- Monolithic kernels
- 모든 기능이 함께 컴파일된다.
- 모든 코드가 권한있는 커널 공간에서 실행된다.
- e.g. Linux
- Microkernels
- 필수 기능만 커널에 포함된다.
- 다른 모든 기능은 권한이 없는 사용자 공간에서 실행된다.
- e.g. MINIX
- Hybrid kernels
- 대부분의 기능이 커널에 포함된다.
- 일부 기능은 동적으로 로드된다.
- 일반적으로 모든 기능이 커널 공간에서 실행된다.
- e.g. Windows NT
Monolithic Kernels의 장단점
장점
- 단일 코드 베이스로 커널 개발이 용이하다.
- 응용 프로그램 개발자들을 위한 견고한 API를 제공한다.
- 별도의 장치 드라이버를 찾을 필요가 없다.
- 밀접한 결합(tight coupling)으로 인해 빠른 성능을 제공한다.
단점
- 대규모 코드 베이스로 인해 정확성을 확인하기 어렵다.
- 버그가 전체 커널을 충돌시킬 수 있으며, 따라서 전체 시스템이 다운될 수 있다.
Microkernels의 장단점
장점
-
작은 코드 베이스로 인해 정확성을 확인하기 쉽다.
→ 고도의 보안이 필요한 시스템에 적합하다.
-
극도로 모듈화되어 있고 구성이 가능하다.
→ 임베디드 시스템에서 필요한 기능만 선택하여 사용 가능
→ 새로운 기능을 추가하기가 쉬움(e.g. a new file system)
-
서비스가 충돌해도 시스템이 안정적으로 유지된다.
단점
- 많은 컨텍스트 스위치로 인해 성능이 떨어진다.
- 안정적인 API가 없어 응용 프로그램을 작성하기가 더 어렵다.