입출력 시스템
입출력 시스템¶
개요¶
입출력(I/O) 시스템은 CPU와 외부 장치(키보드, 디스크, 네트워크 등) 간의 데이터 전송을 담당합니다. I/O 시스템의 설계는 시스템 전체 성능에 큰 영향을 미치며, 폴링, 인터럽트, DMA 등 다양한 방식으로 구현됩니다. 이 레슨에서는 I/O 시스템의 구조와 동작 원리를 학습합니다.
난이도: ⭐⭐⭐
선수 지식: CPU 구조, 메모리 시스템
목차¶
1. I/O 시스템 개요¶
1.1 I/O 장치의 다양성¶
┌─────────────────────────────────────────────────────────────┐
│ I/O 장치 분류 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 입력 장치 출력 장치 저장 장치 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 키보드 │ │ 모니터 │ │ HDD/SSD │ │
│ │ 마우스 │ │ 프린터 │ │ USB │ │
│ │ 스캐너 │ │ 스피커 │ │ SD카드 │ │
│ │ 마이크 │ │ LED │ │ 광학드라이브│ │
│ │ 터치스크린│ │ │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 통신 장치 │
│ ┌────────────────────────────────────────────────────────┐│
│ │ 네트워크 카드 (NIC), WiFi, Bluetooth, USB 허브 ││
│ └────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────┘
I/O 장치 특성:
┌─────────────────┬──────────────┬───────────────────────────┐
│ 장치 │ 데이터율 │ 특성 │
├─────────────────┼──────────────┼───────────────────────────┤
│ 키보드 │ ~100 B/s │ 저속, 비동기, 문자 단위 │
├─────────────────┼──────────────┼───────────────────────────┤
│ 마우스 │ ~1 KB/s │ 저속, 동기, 이벤트 │
├─────────────────┼──────────────┼───────────────────────────┤
│ 기가비트 이더넷 │ 125 MB/s │ 고속, 패킷 단위 │
├─────────────────┼──────────────┼───────────────────────────┤
│ SATA SSD │ ~600 MB/s │ 고속, 블록 단위 │
├─────────────────┼──────────────┼───────────────────────────┤
│ NVMe SSD │ ~7 GB/s │ 초고속, 병렬 처리 │
├─────────────────┼──────────────┼───────────────────────────┤
│ 4K 디스플레이 │ ~20 GB/s │ 초고속, 연속 데이터 │
└─────────────────┴──────────────┴───────────────────────────┘
1.2 I/O 시스템 구조¶
┌─────────────────────────────────────────────────────────────┐
│ I/O 시스템 계층 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 응용 프로그램 │ │
│ │ read(), write(), printf() │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 운영체제 I/O 서브시스템 │ │
│ │ 버퍼링, 캐싱, 스풀링, 스케줄링 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 디바이스 드라이버 │ │
│ │ 장치별 제어 코드, 인터럽트 처리 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 하드웨어 컨트롤러 │ │
│ │ I/O 포트, 레지스터, 버스 인터페이스 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ I/O 장치 │ │
│ │ 물리적 장치 (디스크, 키보드 등) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
1.3 I/O 제어 방식 비교¶
┌───────────────────┬────────────────────┬────────────────────┐
│ │ 프로그램 I/O │ 인터럽트 I/O │
│ 특성 │ (폴링) │ │
├───────────────────┼────────────────────┼────────────────────┤
│ CPU 개입 │ 높음 │ 중간 │
├───────────────────┼────────────────────┼────────────────────┤
│ CPU 효율 │ 낮음 │ 높음 │
├───────────────────┼────────────────────┼────────────────────┤
│ 구현 복잡도 │ 낮음 │ 중간 │
├───────────────────┼────────────────────┼────────────────────┤
│ 적합한 장치 │ 고속, 예측 가능 │ 저속, 비동기 │
├───────────────────┼────────────────────┼────────────────────┤
│ 데이터 전송 │ CPU 경유 │ CPU 경유 │
└───────────────────┴────────────────────┴────────────────────┘
┌───────────────────┬────────────────────┐
│ │ DMA │
│ 특성 │ │
├───────────────────┼────────────────────┤
│ CPU 개입 │ 낮음 │
├───────────────────┼────────────────────┤
│ CPU 효율 │ 매우 높음 │
├───────────────────┼────────────────────┤
│ 구현 복잡도 │ 높음 │
├───────────────────┼────────────────────┤
│ 적합한 장치 │ 대용량 전송 │
├───────────────────┼────────────────────┤
│ 데이터 전송 │ 메모리 직접 │
└───────────────────┴────────────────────┘
2. 프로그램 I/O (폴링)¶
2.1 폴링의 개념¶
정의: CPU가 I/O 장치의 상태를 주기적으로 확인하며 데이터 전송
┌─────────────────────────────────────────────────────────────┐
│ 폴링 동작 과정 │
├─────────────────────────────────────────────────────────────┤
│ │
│ CPU I/O 장치 │
│ │ │ │
│ │ 1. 상태 확인 (읽기 준비?) │ │
│ │──────────────────────────────▶│ │
│ │ │ │
│ │ 2. "아직 안 됨" │ │
│ │◀──────────────────────────────│ │
│ │ │ │
│ │ 3. 상태 확인 (다시) │ │
│ │──────────────────────────────▶│ │
│ │ │ │
│ │ 4. "아직 안 됨" │ │
│ │◀──────────────────────────────│ │
│ │ ...반복... │ │
│ │ N. 상태 확인 │ │
│ │──────────────────────────────▶│ │
│ │ │ │
│ │ N+1. "준비 완료" │ │
│ │◀──────────────────────────────│ │
│ │ │ │
│ │ N+2. 데이터 읽기 │ │
│ │◀──────────────────────────────│ │
│ │ │ │
│ │
│ 문제: CPU가 대기하며 아무 일도 못함 (Busy Waiting) │
│ │
└─────────────────────────────────────────────────────────────┘
2.2 I/O 포트와 레지스터¶
I/O 장치 컨트롤러의 레지스터:
┌─────────────────────────────────────────────────────────────┐
│ I/O Controller Registers │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Status Register (상태 레지스터) │ │
│ │ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │ │
│ │ │Busy │Ready│Error│ IRQ │ ... │ ... │ ... │ ... │ │ │
│ │ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ │ │
│ │ - 장치 상태 표시 (읽기 전용) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Control Register (제어 레지스터) │ │
│ │ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │ │
│ │ │Start│ IE │Mode │ Dir │ ... │ ... │ ... │ ... │ │ │
│ │ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ │ │
│ │ - 장치 제어 명령 (쓰기) │ │
│ │ - IE: Interrupt Enable │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Data Register (데이터 레지스터) │ │
│ │ ┌───────────────────────────────────────────────┐ │ │
│ │ │ Data (8/16/32 bits) │ │ │
│ │ └───────────────────────────────────────────────┘ │ │
│ │ - 실제 전송 데이터 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
2.3 폴링 프로그래밍 예시¶
// 간단한 폴링 기반 I/O 코드
#define STATUS_REG 0x3F8 // 상태 레지스터 주소
#define DATA_REG 0x3F9 // 데이터 레지스터 주소
#define READY_BIT 0x01 // Ready 비트 마스크
// 문자 출력 (폴링)
void putchar_polling(char c) {
// 장치가 준비될 때까지 대기 (Busy Wait)
while ((inb(STATUS_REG) & READY_BIT) == 0) {
// CPU가 계속 루프를 돌며 확인
// 아무 일도 하지 못함
}
// 데이터 전송
outb(DATA_REG, c);
}
// 문자열 출력
void print_string_polling(const char* str) {
while (*str) {
putchar_polling(*str++);
}
}
// 문자 입력 (폴링)
char getchar_polling(void) {
// 입력 데이터가 있을 때까지 대기
while ((inb(STATUS_REG) & READY_BIT) == 0) {
// Busy Wait
}
return inb(DATA_REG);
}
2.4 폴링의 장단점¶
장점:
┌─────────────────────────────────────────────────────────────┐
│ - 구현이 간단함 │
│ - 하드웨어 요구사항 최소 │
│ - 예측 가능한 타이밍 │
│ - 빠른 장치에서는 효율적 (데이터가 즉시 준비되는 경우) │
│ - 실시간 시스템에서 지터(jitter) 최소화 │
└─────────────────────────────────────────────────────────────┘
단점:
┌─────────────────────────────────────────────────────────────┐
│ - CPU 시간 낭비 (Busy Waiting) │
│ - 느린 장치에서 매우 비효율적 │
│ - 여러 장치 처리 어려움 │
│ - 전력 소비 증가 │
└─────────────────────────────────────────────────────────────┘
CPU 시간 낭비 계산:
- 시리얼 포트: 115200 bps = 11520 문자/초
- 한 문자당 약 87us
- 3GHz CPU: 87us = 261,000 사이클
- 한 문자 전송에 CPU가 26만 사이클 대기!
3. 인터럽트 기반 I/O¶
3.1 인터럽트의 개념¶
정의: I/O 장치가 CPU에 비동기적으로 신호를 보내 처리를 요청
┌─────────────────────────────────────────────────────────────┐
│ 인터럽트 동작 과정 │
├─────────────────────────────────────────────────────────────┤
│ │
│ CPU (작업 수행 중) I/O 장치 │
│ │ │ │
│ │ 1. I/O 명령 발행 │ │
│ │─────────────────────────────────────▶│ │
│ │ │ │
│ │ 2. 다른 작업 수행 │ │
│ │ (I/O 완료 기다리지 않음) │ │
│ │ │ │
│ │ ...시간 경과... │ 3. I/O 처리 │
│ │ │ │
│ │ 4. 인터럽트 신호 (IRQ) │ │
│ │◀═════════════════════════════════════│ │
│ │ │ │
│ │ 5. 현재 상태 저장 │ │
│ │ 6. 인터럽트 핸들러 실행 │ │
│ │ 7. 데이터 전송 │ │
│ │ 8. 원래 작업 재개 │ │
│ │ │ │
│ │
│ 장점: CPU가 I/O 대기 중에도 다른 작업 수행 가능 │
│ │
└─────────────────────────────────────────────────────────────┘
3.2 인터럽트 처리 과정¶
┌─────────────────────────────────────────────────────────────┐
│ 인터럽트 처리 상세 단계 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 장치가 IRQ 라인 활성화 │
│ └── 인터럽트 컨트롤러 (PIC/APIC)에 신호 전달 │
│ │
│ 2. 인터럽트 컨트롤러가 CPU에 인터럽트 요청 │
│ └── 인터럽트 우선순위 확인 │
│ └── 마스크되지 않은 경우 CPU에 전달 │
│ │
│ 3. CPU가 현재 명령어 완료 후 인터럽트 확인 │
│ └── 인터럽트 플래그 확인 (IF) │
│ └── 인터럽트 가능 상태면 처리 시작 │
│ │
│ 4. CPU 상태 저장 │
│ └── 플래그 레지스터 → 스택 │
│ └── CS:IP (또는 RIP) → 스택 │
│ └── 인터럽트 비활성화 (중첩 방지) │
│ │
│ 5. 인터럽트 벡터 테이블 참조 │
│ └── 인터럽트 번호로 핸들러 주소 조회 │
│ │
│ 6. 인터럽트 서비스 루틴 (ISR) 실행 │
│ └── 장치별 처리 코드 실행 │
│ └── 인터럽트 확인 (Acknowledge) 신호 전송 │
│ │
│ 7. EOI (End of Interrupt) 전송 │
│ └── 인터럽트 컨트롤러에 처리 완료 알림 │
│ │
│ 8. IRET 명령으로 복귀 │
│ └── 저장된 상태 복원 │
│ └── 원래 코드로 복귀 │
│ │
└─────────────────────────────────────────────────────────────┘
3.3 인터럽트 벡터 테이블¶
x86 인터럽트 벡터 테이블 (IDT):
┌─────────────────────────────────────────────────────────────┐
│ Interrupt Descriptor Table (IDT) │
├──────┬──────────────────────────────────────────────────────┤
│ Vec │ 설명 │
├──────┼──────────────────────────────────────────────────────┤
│ 0 │ Divide Error (#DE) │
│ 1 │ Debug Exception (#DB) │
│ 2 │ NMI (Non-Maskable Interrupt) │
│ 3 │ Breakpoint (#BP) │
│ 6 │ Invalid Opcode (#UD) │
│ 8 │ Double Fault (#DF) │
│ 13 │ General Protection Fault (#GP) │
│ 14 │ Page Fault (#PF) │
│ ... │ ... │
│ 32 │ IRQ 0: Timer (PIT) │
│ 33 │ IRQ 1: Keyboard │
│ 34 │ IRQ 2: Cascade (PIC2 연결) │
│ 35 │ IRQ 3: COM2/COM4 │
│ 36 │ IRQ 4: COM1/COM3 │
│ ... │ ... │
│ 46 │ IRQ 14: Primary IDE │
│ 47 │ IRQ 15: Secondary IDE │
│ ... │ ... │
│ 128 │ System Call (Linux: int 0x80) │
│ ... │ ... │
│ 255 │ Reserved │
└──────┴──────────────────────────────────────────────────────┘
IDT Entry 구조 (64비트):
┌─────────────────────────────────────────────────────────────┐
│ 63 48 47 46 44 43 40 39 35 34 32 │
│ ├───────────┼──┼──────┼────────┼─────┼─────┤ │
│ │ Offset │P │ DPL │ Type │ IST │ 0 │ │
│ │ [63:48] │ │ │ │ │ │ │
│ └───────────┴──┴──────┴────────┴─────┴─────┘ │
│ 31 16 15 0 │
│ ├─────────────────┼─────────────────┤ │
│ │ Segment Sel. │ Offset [15:0] │ │
│ └─────────────────┴─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
3.4 인터럽트 기반 I/O 프로그래밍¶
// 인터럽트 기반 키보드 드라이버 예시
#define KEYBOARD_IRQ 1
#define KEYBOARD_PORT 0x60
// 키보드 버퍼
volatile char keyboard_buffer[256];
volatile int buffer_head = 0;
volatile int buffer_tail = 0;
// 인터럽트 핸들러 (ISR)
void keyboard_handler(void) {
// 1. 스캔코드 읽기
unsigned char scancode = inb(KEYBOARD_PORT);
// 2. 버퍼에 저장
keyboard_buffer[buffer_head] = scancode;
buffer_head = (buffer_head + 1) % 256;
// 3. EOI 전송 (인터럽트 완료 알림)
outb(0x20, 0x20); // PIC에 EOI
}
// 문자 읽기 (블로킹)
char getchar_interrupt(void) {
// 버퍼에 데이터가 있을 때까지 대기
// (실제로는 sleep/wakeup 사용)
while (buffer_tail == buffer_head) {
// CPU 절전 또는 다른 프로세스 실행
asm("hlt"); // Halt until interrupt
}
char c = keyboard_buffer[buffer_tail];
buffer_tail = (buffer_tail + 1) % 256;
return c;
}
// 인터럽트 핸들러 등록
void init_keyboard(void) {
// IDT에 핸들러 등록
set_interrupt_handler(32 + KEYBOARD_IRQ, keyboard_handler);
// 인터럽트 활성화
enable_irq(KEYBOARD_IRQ);
}
3.5 인터럽트의 장단점¶
장점:
┌─────────────────────────────────────────────────────────────┐
│ - CPU 효율성 향상 (대기 시간에 다른 작업 수행) │
│ - 비동기 이벤트 처리에 적합 │
│ - 다중 장치 처리 용이 │
│ - 전력 효율적 (CPU가 대기 상태로 전환 가능) │
└─────────────────────────────────────────────────────────────┘
단점:
┌─────────────────────────────────────────────────────────────┐
│ - 구현 복잡성 증가 │
│ - 인터럽트 오버헤드 (컨텍스트 저장/복원) │
│ - 인터럽트 지연 시간 (Latency) 존재 │
│ - 고빈도 인터럽트 시 오버헤드 증가 (Interrupt Storm) │
└─────────────────────────────────────────────────────────────┘
인터럽트 오버헤드:
- 상태 저장: ~100 사이클
- 핸들러 진입: ~50 사이클
- 캐시/TLB 영향: ~100+ 사이클
- 총: ~500-1000 사이클/인터럽트
초당 10만 인터럽트 @ 3GHz:
오버헤드 = 100,000 × 500 / 3,000,000,000 ≈ 1.7% CPU
4. DMA (Direct Memory Access)¶
4.1 DMA의 개념¶
정의: CPU를 거치지 않고 I/O 장치와 메모리 간 직접 데이터 전송
┌─────────────────────────────────────────────────────────────┐
│ DMA vs CPU 기반 전송 비교 │
├─────────────────────────────────────────────────────────────┤
│ │
│ CPU 기반 전송 (Programmed I/O): │
│ │
│ Memory ←──── CPU ────→ I/O Device │
│ read write │
│ │
│ - 매 바이트마다 CPU 개입 │
│ - CPU가 데이터 이동 담당 │
│ - CPU 시간 소모 큼 │
│ │
│ DMA 전송: │
│ │
│ Memory ◀══════════════▶ I/O Device │
│ │ │
│ │ DMA Controller │
│ └─────────┐ │
│ │ │
│ CPU ─────────┘ (설정만) │
│ │
│ - CPU는 전송 설정만 담당 │
│ - DMA 컨트롤러가 전송 수행 │
│ - 전송 완료 시 인터럽트로 알림 │
│ - CPU는 다른 작업 가능 │
│ │
└─────────────────────────────────────────────────────────────┘
4.2 DMA 동작 과정¶
┌─────────────────────────────────────────────────────────────┐
│ DMA 전송 과정 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. CPU가 DMA 컨트롤러 설정 │
│ ┌─────────────────────────────────────────────────────┐│
│ │ - 소스 주소 (메모리 또는 I/O) ││
│ │ - 목적지 주소 ││
│ │ - 전송 크기 (바이트 수) ││
│ │ - 전송 방향 (읽기/쓰기) ││
│ │ - 전송 모드 (블록/사이클 스틸링) ││
│ └─────────────────────────────────────────────────────┘│
│ │
│ 2. CPU가 DMA 전송 시작 명령 │
│ │
│ 3. DMA 컨트롤러가 버스 요청 (Bus Request) │
│ - CPU에 HOLD 신호 전송 │
│ │
│ 4. CPU가 버스 승인 (Bus Grant) │
│ - HLDA 신호로 버스 제어권 양도 │
│ - CPU는 버스 사용하지 않는 작업 수행 │
│ │
│ 5. DMA 컨트롤러가 데이터 전송 │
│ ┌────────┐ ┌────────┐ │
│ │ Memory │◀═══════▶│ I/O │ │
│ └────────┘ DMA └────────┘ │
│ Bus │
│ │
│ 6. 전송 완료 시 │
│ - 버스 반환 │
│ - CPU에 인터럽트 발생 │
│ │
│ 7. CPU가 완료 처리 │
│ - 상태 확인 │
│ - 필요시 다음 전송 설정 │
│ │
└─────────────────────────────────────────────────────────────┘
4.3 DMA 컨트롤러 구조¶
┌─────────────────────────────────────────────────────────────┐
│ DMA Controller │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Control Registers │ │
│ ├──────────────────────────────────────────────────────┤ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Command Register │ │ │
│ │ │ - DMA 동작 모드 설정 │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Mode Register │ │ │
│ │ │ - 전송 방향, 모드 (단일/블록/수요) │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Status Register │ │ │
│ │ │ - 완료 상태, 요청 상태 │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Channel 0 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Address Reg │ │ Count Reg │ │ Page Reg │ │ │
│ │ │ 0x0000 │ │ 1024 │ │ 0x00 │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Channel 1 │ │
│ │ ... (동일 구조) │ │
│ └──────────────────────────────────────────────────────┘ │
│ ... Channel 2, 3, ... │
│ │
└─────────────────────────────────────────────────────────────┘
4.4 DMA 전송 모드¶
1. 블록 전송 (Block/Burst Mode):
┌─────────────────────────────────────────────────────────────┐
│ │
│ ──────┬────────────────────────────────┬────── │
│ CPU │ DMA │ CPU │
│ 사용 │ 버스 독점 사용 │사용 │
│ ──────┴────────────────────────────────┴────── │
│ │
│ - 전체 블록을 한 번에 전송 │
│ - 가장 빠른 전송 │
│ - CPU가 오래 대기할 수 있음 │
│ - 대용량 데이터에 적합 │
│ │
└─────────────────────────────────────────────────────────────┘
2. 사이클 스틸링 (Cycle Stealing):
┌─────────────────────────────────────────────────────────────┐
│ │
│ ──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬── │
│ C │D │C │D │C │D │C │D │C │D │C │
│ P │M │P │M │P │M │P │M │P │M │P │
│ U │A │U │A │U │A │U │A │U │A │U │
│ ──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴── │
│ │
│ - 한 번에 한 워드/바이트씩 전송 │
│ - CPU와 DMA가 번갈아 버스 사용 │
│ - CPU 영향 최소화 │
│ - 전송 속도는 느림 │
│ │
└─────────────────────────────────────────────────────────────┘
3. 수요 전송 (Demand Mode):
┌─────────────────────────────────────────────────────────────┐
│ │
│ 장치가 준비될 때마다 전송 (DREQ 신호 기반) │
│ 장치 속도에 맞춤 │
│ │
└─────────────────────────────────────────────────────────────┘
4.5 DMA 프로그래밍 예시¶
// DMA 디스크 읽기 예시 (간략화)
#define DMA_CHANNEL 2
#define DMA_ADDR_REG 0x04 // 채널 2 주소
#define DMA_COUNT_REG 0x05 // 채널 2 카운트
#define DMA_PAGE_REG 0x81 // 채널 2 페이지
#define DMA_MODE_REG 0x0B
#define DMA_MASK_REG 0x0A
// DMA 전송 설정
void setup_dma_read(void* buffer, size_t count) {
uint32_t addr = (uint32_t)buffer;
// 1. DMA 채널 마스크 (비활성화)
outb(DMA_MASK_REG, DMA_CHANNEL | 0x04);
// 2. 플립플롭 리셋
outb(0x0C, 0);
// 3. 모드 설정 (읽기, 채널 2, 단일 모드)
outb(DMA_MODE_REG, 0x46);
// 4. 주소 설정 (하위 16비트)
outb(DMA_ADDR_REG, addr & 0xFF);
outb(DMA_ADDR_REG, (addr >> 8) & 0xFF);
// 5. 페이지 설정 (상위 비트)
outb(DMA_PAGE_REG, (addr >> 16) & 0xFF);
// 6. 카운트 설정 (count - 1)
outb(DMA_COUNT_REG, (count - 1) & 0xFF);
outb(DMA_COUNT_REG, ((count - 1) >> 8) & 0xFF);
// 7. DMA 채널 활성화
outb(DMA_MASK_REG, DMA_CHANNEL);
}
// 디스크 읽기 명령 + DMA
void read_disk_dma(void* buffer, uint32_t sector, uint16_t count) {
// DMA 설정
setup_dma_read(buffer, count * 512);
// 디스크 컨트롤러에 읽기 명령 발행
issue_disk_read_command(sector, count);
// CPU는 다른 작업 수행 가능
// 전송 완료 시 인터럽트 발생
}
// DMA 완료 인터럽트 핸들러
void dma_complete_handler(void) {
// 전송 완료 처리
// 상태 확인, 버퍼 사용 가능
signal_dma_complete();
}
5. 버스 구조¶
5.1 버스의 종류¶
┌─────────────────────────────────────────────────────────────┐
│ 시스템 버스 구조 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CPU │ │
│ └─────────────────────────┬───────────────────────────┘ │
│ │ │
│ Front-Side Bus │
│ (또는 QPI/UPI) │
│ │ │
│ ┌─────────────────────────┴───────────────────────────┐ │
│ │ Memory Controller Hub │ │
│ │ (Northbridge) │ │
│ └───────────┬─────────────┬─────────────┬─────────────┘ │
│ │ │ │ │
│ Memory PCIe DMI │
│ Bus x16 │
│ │ │ │ │
│ ┌───────────┴───┐ ┌────┴────┐ │ │
│ │ DRAM │ │ GPU │ │ │
│ └───────────────┘ └─────────┘ │ │
│ │ │
│ ┌──────────────────────────────────────┴──────────────┐ │
│ │ I/O Controller Hub │ │
│ │ (Southbridge) │ │
│ └───────┬──────────┬──────────┬──────────┬───────────┘ │
│ │ │ │ │ │
│ SATA USB PCIe Audio │
│ │ │ x1 │ │
│ ┌─────┴─────┐ ┌──┴──┐ ┌──┴──┐ ┌───┴───┐ │
│ │ HDD/SSD │ │ USB │ │ NIC │ │Codec │ │
│ └──────────┘ │Devs │ └─────┘ └───────┘ │
│ └─────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
5.2 버스 특성¶
버스 구성 요소:
┌─────────────────────────────────────────────────────────────┐
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Data Bus │ │
│ │ - 데이터 전송 라인 │ │
│ │ - 폭: 8, 16, 32, 64비트 │ │
│ │ - 양방향 │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Address Bus │ │
│ │ - 메모리/I/O 주소 지정 │ │
│ │ - 폭: 20, 32, 36, 40+비트 │ │
│ │ - 단방향 (CPU → 장치) │ │
│ └───────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Control Bus │ │
│ │ - 제어 신호 전송 │ │
│ │ - Read, Write, IRQ, DMA 요청 등 │ │
│ │ - 양방향 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
주요 버스 표준:
┌────────────────┬──────────────┬───────────────┬─────────────┐
│ 버스 │ 대역폭 │ 용도 │ 특징 │
├────────────────┼──────────────┼───────────────┼─────────────┤
│ PCIe 4.0 x16 │ ~64 GB/s │ GPU, 고속 장치 │ 직렬, 레인 │
├────────────────┼──────────────┼───────────────┼─────────────┤
│ PCIe 5.0 x16 │ ~128 GB/s │ 차세대 GPU │ 최신 표준 │
├────────────────┼──────────────┼───────────────┼─────────────┤
│ SATA III │ ~600 MB/s │ 저장 장치 │ 직렬 │
├────────────────┼──────────────┼───────────────┼─────────────┤
│ NVMe (PCIe 4) │ ~7 GB/s │ 고속 SSD │ 저지연 │
├────────────────┼──────────────┼───────────────┼─────────────┤
│ USB 3.2 Gen2 │ ~1.25 GB/s │ 주변 장치 │ 범용 │
├────────────────┼──────────────┼───────────────┼─────────────┤
│ Thunderbolt 4 │ ~5 GB/s │ 고속 주변장치 │ 데이지체인 │
└────────────────┴──────────────┴───────────────┴─────────────┘
5.3 버스 중재¶
여러 장치가 버스를 공유할 때 충돌 방지:
1. 중앙 집중식 중재:
┌─────────────────────────────────────────────────────────────┐
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │Device 0│ │Device 1│ │Device 2│ │Device 3│ │
│ └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ │
│ │ REQ │ REQ │ REQ │ REQ │
│ │ │ │ │ │
│ └─────┬─────┴─────┬─────┴─────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Bus Arbiter │ │
│ │ (중앙 중재기) │ │
│ │ - 요청 수신 │ │
│ │ - 우선순위에 따라 GRANT 신호 발행 │ │
│ └──────────────────────────────────────────────┘ │
│ │ │ │ │
│ GRANT │ GRANT │ GRANT │ │
│ ▼ ▼ ▼ │
│ │
└─────────────────────────────────────────────────────────────┘
2. 우선순위 방식:
- 고정 우선순위: 장치마다 고정된 우선순위
- 라운드 로빈: 순환하며 공평하게 할당
- 동적 우선순위: 사용 패턴에 따라 조정
6. I/O 인터페이스¶
6.1 I/O 주소 지정¶
1. 분리 I/O (Isolated I/O / Port-Mapped I/O):
┌─────────────────────────────────────────────────────────────┐
│ │
│ 메모리 주소 공간 I/O 주소 공간 │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ 0x0000_0000 │ │ 0x0000 │ │
│ │ │ │ │ │
│ │ Memory │ │ I/O Ports │ │
│ │ │ │ │ │
│ │ 0xFFFF_FFFF │ │ 0xFFFF │ │
│ └────────────────────┘ └────────────────────┘ │
│ │
│ - 별도의 주소 공간 사용 │
│ - IN, OUT 명령어 사용 │
│ - x86 아키텍처에서 사용 │
│ │
│ 예시: │
│ outb(0x3F8, data); // 포트 0x3F8에 쓰기 │
│ data = inb(0x3F8); // 포트 0x3F8에서 읽기 │
│ │
└─────────────────────────────────────────────────────────────┘
2. 메모리 맵 I/O (Memory-Mapped I/O):
┌─────────────────────────────────────────────────────────────┐
│ │
│ 통합 주소 공간 │
│ ┌────────────────────────────────────────────┐ │
│ │ 0x0000_0000 │ │
│ │ │ │
│ │ System Memory (RAM) │ │
│ │ │ │
│ │ 0x7FFF_FFFF │ │
│ ├────────────────────────────────────────────┤ │
│ │ 0x8000_0000 │ │
│ │ │ │
│ │ I/O Device Registers │ │
│ │ (메모리처럼 접근) │ │
│ │ │ │
│ │ 0xFFFF_FFFF │ │
│ └────────────────────────────────────────────┘ │
│ │
│ - 일반 메모리 명령어로 I/O 접근 │
│ - ARM, RISC-V 등에서 주로 사용 │
│ - 현대 PC에서도 대부분의 장치가 MMIO 사용 │
│ │
│ 예시: │
│ volatile uint32_t* reg = (uint32_t*)0xFE200000; │
│ *reg = value; // I/O 레지스터에 쓰기 │
│ value = *reg; // I/O 레지스터에서 읽기 │
│ │
└─────────────────────────────────────────────────────────────┘
6.2 장치 드라이버 구조¶
┌─────────────────────────────────────────────────────────────┐
│ 디바이스 드라이버 구조 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Driver Entry Points │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ init() - 드라이버 초기화 │ │
│ │ open() - 장치 열기 │ │
│ │ close() - 장치 닫기 │ │
│ │ read() - 데이터 읽기 │ │
│ │ write() - 데이터 쓰기 │ │
│ │ ioctl() - 제어 명령 │ │
│ │ interrupt_handler() - 인터럽트 처리 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Driver Internal Data │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ - 장치 상태 │ │
│ │ - 버퍼 │ │
│ │ - 대기 큐 │ │
│ │ - 설정 정보 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
리눅스 디바이스 드라이버 예시:
static struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_close,
.read = my_read,
.write = my_write,
.unlocked_ioctl = my_ioctl,
};
7. 현대 I/O 시스템¶
7.1 NVMe (Non-Volatile Memory Express)¶
┌─────────────────────────────────────────────────────────────┐
│ NVMe 아키텍처 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 기존 SATA/AHCI vs NVMe: │
│ │
│ SATA/AHCI: │
│ - 단일 명령 큐 (깊이 32) │
│ - HDD 시대에 설계 │
│ - 높은 지연시간 │
│ │
│ NVMe: │
│ - 64K 큐, 각 큐당 64K 명령 │
│ - SSD에 최적화 │
│ - 낮은 지연시간 │
│ - PCIe 직접 연결 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CPU / Driver │ │
│ └─────────────────────────┬───────────────────────────┘ │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Submit Q 0 │ │ Submit Q 1 │ │ Submit Q N │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ NVMe Controller │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │Complete Q 0 │ │Complete Q 1 │ │Complete Q N │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
7.2 USB 시스템¶
┌─────────────────────────────────────────────────────────────┐
│ USB 아키텍처 │
├─────────────────────────────────────────────────────────────┤
│ │
│ USB 세대별 속도: │
│ ┌──────────────┬──────────────────────────────────────┐ │
│ │ USB 2.0 │ 480 Mbps (High Speed) │ │
│ │ USB 3.0 │ 5 Gbps (SuperSpeed) │ │
│ │ USB 3.1 │ 10 Gbps (SuperSpeed+) │ │
│ │ USB 3.2 │ 20 Gbps (SuperSpeed USB 20Gbps) │ │
│ │ USB4 │ 40 Gbps │ │
│ └──────────────┴──────────────────────────────────────┘ │
│ │
│ USB 전송 타입: │
│ ┌──────────────┬──────────────────────────────────────┐ │
│ │ Control │ 설정, 제어 (작은 데이터) │ │
│ │ Bulk │ 대용량 데이터 (저장 장치) │ │
│ │ Interrupt │ 소량, 주기적 (키보드, 마우스) │ │
│ │ Isochronous │ 실시간, 주기적 (오디오, 비디오) │ │
│ └──────────────┴──────────────────────────────────────┘ │
│ │
│ USB 토폴로지: │
│ │
│ Host Controller │
│ │ │
│ ▼ │
│ Root Hub │
│ / | \ │
│ / | \ │
│ Hub Device Device │
│ / \ │
│ / \ │
│ Dev Dev │
│ │
└─────────────────────────────────────────────────────────────┘
7.3 가상화 I/O¶
┌─────────────────────────────────────────────────────────────┐
│ I/O 가상화 기법 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 에뮬레이션: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Guest OS → Hypervisor → Physical Device │ │
│ │ (I/O 트랩 및 에뮬레이션) │ │
│ └─────────────────────────────────────────────────────┘ │
│ - 느림, 호환성 높음 │
│ │
│ 2. Para-virtualization (virtio): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Guest OS (virtio 드라이버) → Hypervisor → Device │ │
│ └─────────────────────────────────────────────────────┘ │
│ - 최적화된 가상 인터페이스 │
│ - 게스트 OS 수정 필요 │
│ │
│ 3. Direct Device Assignment (VFIO): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Guest OS → Physical Device (직접 접근) │ │
│ │ (IOMMU로 메모리 격리) │ │
│ └─────────────────────────────────────────────────────┘ │
│ - 네이티브 성능 │
│ - 장치 공유 불가 │
│ │
│ 4. SR-IOV (Single Root I/O Virtualization): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 하나의 물리 장치를 여러 가상 함수(VF)로 분할 │ │
│ │ 각 VM이 독립적인 VF에 직접 접근 │ │
│ └─────────────────────────────────────────────────────┘ │
│ - 네이티브 성능 + 공유 가능 │
│ │
└─────────────────────────────────────────────────────────────┘
8. 연습 문제¶
기초 문제¶
-
폴링, 인터럽트, DMA 방식의 차이점을 설명하시오.
-
인터럽트 벡터 테이블의 역할은?
-
DMA 컨트롤러에 설정해야 하는 정보 3가지는?
중급 문제¶
- 다음 상황에서 적합한 I/O 방식을 선택하시오:
- (a) 키보드 입력 처리
- (b) 10MB 파일 디스크에서 읽기
-
(c) 고속 네트워크 패킷 처리 (10Gbps)
-
인터럽트 기반 I/O에서 발생할 수 있는 "Interrupt Storm"이란?
-
메모리 맵 I/O와 포트 맵 I/O의 장단점을 비교하시오.
심화 문제¶
- 다음 조건에서 CPU 효율을 계산하시오:
- CPU 클럭: 3GHz
- 디스크 전송률: 500MB/s
- DMA 블록 크기: 4KB
-
DMA 완료 인터럽트 처리 시간: 1000 사이클
-
USB의 4가지 전송 타입을 설명하고 각각에 적합한 장치 예를 들시오.
-
SR-IOV가 가상화 환경에서 I/O 성능을 향상시키는 원리를 설명하시오.
정답
1. I/O 방식 비교: - 폴링: CPU가 계속 상태 확인, 간단하지만 CPU 낭비 - 인터럽트: 장치가 완료 알림, CPU 효율적이지만 오버헤드 - DMA: 메모리 직접 전송, 대용량에 효율적이지만 복잡 2. 인터럽트 벡터 테이블: - 인터럽트 번호를 핸들러 주소로 매핑 - 인터럽트 발생 시 해당 핸들러로 점프 3. DMA 설정 정보: - 소스/목적지 주소 - 전송 크기 (바이트 수) - 전송 방향 (읽기/쓰기) 4. 적합한 I/O 방식: - (a) 인터럽트 (저속, 비동기) - (b) DMA (대용량 블록 전송) - (c) DMA + 폴링 또는 NAPI (고속, 많은 패킷) 5. Interrupt Storm: - 너무 많은 인터럽트가 발생하여 CPU가 핸들러 처리만 하는 상황 - 정상 작업 수행 불가 - 해결: 인터럽트 병합, 폴링 전환 (NAPI) 6. I/O 주소 지정 비교: - 포트 맵: 별도 주소 공간, 별도 명령어 필요, 주소 공간 절약 - 메모리 맵: 통합 주소 공간, 일반 명령어 사용, 캐시 주의 필요 7. CPU 효율 계산: - 전송률 500MB/s, 블록 4KB → 초당 125,000 전송 - 각 전송마다 1000 사이클 인터럽트 처리 - 총 인터럽트 사이클: 125,000,000 - CPU 효율: 1 - (125M / 3G) = 1 - 0.042 = 95.8% 8. USB 전송 타입: - Control: 장치 설정, 상태 확인 (모든 USB 장치) - Bulk: 대용량 데이터 (USB 드라이브, 프린터) - Interrupt: 소량 주기적 (키보드, 마우스) - Isochronous: 실시간 (웹캠, USB 오디오) 9. SR-IOV 원리: - 하나의 물리 장치에 여러 가상 함수(VF) 생성 - 각 VM이 전용 VF에 직접 접근 - 하이퍼바이저 개입 없이 네이티브 성능 - IOMMU로 메모리 격리 보장다음 단계¶
- 18_Parallel_Processing_Multicore.md - 멀티코어 아키텍처와 병렬 프로그래밍
참고 자료¶
- Computer Organization and Design (Patterson & Hennessy)
- Operating System Concepts (Silberschatz et al.)
- NVMe Specification
- USB Specification
- Linux Device Drivers (Corbet, Rubini, Kroah-Hartman)