컴퓨터 시스템의 구조 전반이 어떻게 생겨 먹었는지, 그리고 어떻게 돌아가는지 살펴본다.
📂 목차
📚 본문
현대 컴퓨터 시스템은 하나 이상의 CPU와 여러 개의 장치 컨트롤러(Device Controller)로 구성되어 있고, 공통 버스(Common Bus, System Bus) 를 통해 연결되어 있다.
Common Bus는 각 구성 요소들과 공유 메모리(Shared Memory) 간의 접근 경로를 제공한다(공유 메모리가 있다면 각 장치들마다 해당 메모리를 읽어서 데이터를 공유할 수 있다). 일반적으로 운영체제는 각 Device Controller 마다 하나의 장치 드라이버(Device Driver) 를 가지게 된다. CPU 와 장치 컨트롤러는 병렬로 동작이 가능하기에 메모리 접근 주기(Memory Cycle) 도중 경쟁하게 된다.
이런 공유 메모리에 대한 접근을 질서있게 하기 위해서 메모리 컨트롤러(Memory Controller)가 존재하고 컨트롤러는 메모리 접근을 동기화(Synchronize) 한다.
위의 흐름을 가지고 다음을 읽자.
1.2.1.1 Interrupts
Interrupt는 CPU의 주의를 끌기 위해 사용되는 제어 신호로,
Common Bus의 Control Bus를 통해 전달된다. 주된 종류는 다음과 같다:
- I/O 작업 완료 알림: 디스크 읽기, 프린터 출력 등 작업 완료 시
- 입력 이벤트 알림: 키보드 입력, 마우스 클릭 등 사용자 인터랙션 발생 시
- 타이머 인터럽트 (Timer Interrupt): 일정 주기로 발생하여 OS가 CPU 제어권을 회수할 수 있게 함
- 소프트웨어 인터럽트 (System Call): 사용자 프로그램이 운영체제 기능을 요청할 때 발생
- 하드웨어 오류/예외 (Exception): 0으로 나누기, 잘못된 메모리 접근 등 오류 발생 시 자동으로 발생
I/O 작업 완료 알림
I/O 작업 완료 알림을 예로 들어보자.
- CPU가 I/O 장치 A에 작업을 요청
- 운영체제의 Device Driver가 CPU 명령을 해당 장치가 이해할 수 있도록 해석 및 변환
- 변환된 명령이 Device Controller 하드웨어로 전달됨
- Device Controller가 실제 장치에 명령을 내려 작업 수행
- 작업 완료 시, Device Controller가 Control Bus를 통해 CPU에 Interrupt를 전송
- CPU는 Interrupt를 수신하고, 적절한 Interrupt Handler (Interrupt Service Routine, ISR)를 호출하여 후속 작업을 처리
Interrupt Handler 를 보자.
⭐️ Interrupt Handler
운영체제 내부에서 Interrupt 가 발생했을 때, 실행되는 특수한 함수/코드 블록이다.
CPU가 Control Bus 에서 Interrupt 를 받았을 때, 현재 실행 중이던 작업을 잠시 멈추고,
즉시 고정된 위치(fixed location) 로 제어를 전환(Context Switching)한다.
고정된 위치는 일반적으로 해당 인터럽트를 처리하기 위한 서비스 루틴(Interrupt Service Routine, ISR) 의 시작 주소를 포함한다. ISR이 실행되고, 그 작업이 완료되면 CPU는 중단된 게산을 다시 이어서 수행하게 된다. 인터럽트를 처리하는데 있어서 책에서 제공하는 방법은 두 가지 이다.
- 인터럽트 정보를 확인하는 일반 루틴(Generic Routine)을 호출한 뒤, 해당 루틴이 인터럽트에 특화된 핸들러를 호출하는 방식이 일반적이고 단순한 방법이다.
- 인터럽트는 매우 자주 발생하기 때문에 빠르게 처리되어야 하므로 위 방법은 맞지 않고, 속도를 높이기 위해 인터럽트 루틴들의 주소를 저장한 포인터 테이블(Interrupt Vector Table)을 사용한다면 Generic Routine 을 거치지 않고도 테이블을 통해 직접 해당 ISR 으로 점프할 수 있다.
Interrupt Vector Table 을 이용한 방법을 보자.
Interrupt Vector Table(Interrupt Vector)을 사용한 방식
Interrupt Vector Table은 보통 메모리의 낮은 영역(처음 100여 개 위치)에 저장되며,
각 위치에는 인터럽트 요청 번호(인터럽트 벡터)에 해당하는 서비스 루틴(ISR)의 주소가 저장된다.
CPU는 인터럽트가 발생하면, 전달받은 고유 번호(벡터 번호)를 기반으로
Interrupt Vector Table을 인덱싱하여 해당 ISR로 직접 점프한다.
이 방식은 Windows, UNIX 등 대부분의 운영체제에서 사용된다.
이때, 인터럽트 처리를 위해서는 CPU의 상태 정보(예: 레지스터 값)를 반드시 저장해야 한다.
인터럽트 처리가 끝난 후에는 이 상태를 복원하여 중단된 작업을 재개할 수 있어야 한다.
✅ 따라서 인터럽트 처리를 위해 필요한 조건은 다음과 같다.
- ISR들의 주소가 저장된 Interrupt Vector Table이 필요
- 인터럽트 발생 시, CPU 상태 정보(Context)를 저장해야 함
- ISR 실행 완료 후, CPU는 인터럽트 복귀 명령(Return from Interrupt)을 실행하여 원래 작업으로 복귀
그럼 이러한 이론들을 기반으로 실제 구현 단계에서는 어떤 구조가 필요할지 살펴보자.
1.2.1.2 Implementation
구현에 있어서 크게 다음 기능들을 구현해야 한다.
- Interrupt Controller
- Interrupt Chaining
- Interrupt Priority Level
Interrupt Controller
앞에서 인터럽트의 전체적인 처리 과정을 이해했으니, 이제 CPU가 인터럽트 신호를 어떻게 해석하고 처리하는지를 살펴보자.
현대 컴퓨터에서는 여러 장치들이 서로 다른 방식으로 인터럽트를 발생시키기 때문에,
CPU가 직접 해석하기엔 일관성이 부족하다. 이를 해결하기 위해 Interrupt Controller라는 하드웨어가 사용된다.
Interrupt Controller는 장치들로부터 들어오는 Interrupt Request Line의 신호를 감지하고,
해당 신호들의 우선순위를 판별하여, 가장 먼저 처리해야 할 인터럽트 번호(Interrupt Vector Number)를 CPU에 전달한다.
CPU는 이 번호를 사용하여 Interrupt Vector Table을 인덱싱하고,
해당 위치에 등록된 ISR(Interrupt Service Routine)을 실행한다.
이를 위 요구사항과 더불어 추가하면:
- Interrupt Controller는 Interrupt Request Line을 통해 인터럽트 신호를 감지
- 신호에 따라 판별된 인터럽트 번호를 CPU에 전달
- CPU는 해당 번호로 Interrupt Vector Table을 인덱싱
- ISR들의 주소가 저장된 Interrupt Vector Table이 필요
- 인터럽트 발생 시, CPU 상태 정보(Context)를 저장해야 함
- ISR 실행 완료 후, CPU는 인터럽트 복귀 명령(Return from Interrupt)을 실행하여 원래 작업으로 복귀
이 과정을 통해 컴퓨터 시스템은 Interrupt Controller라는 하드웨어 자원을 활용하여 ISR을 안정적으로 실행할 수 있으며,
운영체제 입장에서는 이를 Interrupt Controller라는 추상적인 객체로 간주하여 관리하게 된다.
Interrupt Chaining
Interrupt Vector Table은 말 그대로 인터럽트 번호를 통해 ISR 주소를 빠르게 찾아갈 수 있도록 설계된 테이블이다.
그러나 장치 수가 많아질수록 각 장치에 대해 개별적인 각 장치마다 독립된 인터럽트 벡터 항목이 필요해지므로,
공간 복잡도가 급격히 증가하게 된다.
예시
- 32번(키보드): 0x1000
- 33번(키보드): 0x1100
- 34번(키보드): 0x1200
- 35번(마우스): 0x1300
- 36번(디스크): 0x1400
- 37번(디스크): 0x1500 …
장치가 추가될 때마다 번호를 계속 할당하면 테이블은 비대해져 공간 복잡도가 증가하고, 관리도 어렵다.
또 다른 문제는, 예를 들어 USB와 같은 포트 기반 장치는
여러 장치(USB 마우스, USB 키보드 등)가 공통된 인터럽트 번호(예: 64번)를 공유해야 하는 상황이 자주 발생한다.
예시
- 64번(USB): 0x8500 …
이 경우 하나의 ISR이 모든 요청을 처리하게 되므로 요청이 많을수록 조건문이 복잡해지고, 시간 복잡도가 증가하게 된다.
이러한 비효율을 줄이기 위해 Interrupt Chaining이라는 기법이 사용된다.
이 방식에서는 Interrupt Vector Table의 각 항목이 ISR 하나의 주소가 아니라, ISR들을 연결한 연결 리스트의 시작점을 가리킨다.
즉, 인터럽트 번호 40번에 대응되는 테이블 항목이 다음과 같은 구조를 갖는다:
Index 40 → [ ISR1 ] → [ ISR2 ] → [ ISR3 ] → null
이 구조는,
- 모든 장치에 대해 거대한 인터럽트 벡터 테이블을 만드는 공간 오버헤드와
- 하나의 ISR에서 모든 요청을 처리하는 시간 오버헤드 사이에서 절충된 설계를 하는 것이라고 볼 수 있다(널리 사용됨).
Interrupt Priority Levels
우선순위는 들어오는 Interrupt 에 대해 낮은 순위를 가지는 Interrupt 처리를 지연(defer)하게 하고
모든 인터럽트를 완전히 차단하지 않아도 되게 해준다.
따라서 더 높은 우선순위의 인터럽트가 실행 중인 낮은 우선순위의 인터럽트를 중단하고(preempt) 먼저 처리될 수 있도록 한다.
(예: 타이머 인터럽트가 키보드 입력보다 높은 우선순위로 처리됨)
이는 인터럽트가 운영체제 전반에서 비동기 이벤트 처리를 위해 또는 다양한 목적을 위해 사용될 수 있으며
시간에 대해 민감한 처리(time-sensitive processing)에 광범위하게 사용할 수 있으며,
인터럽트를 효율적으로 처리하는 것이 시스템 성능을 좌우하게 하는 매우 중요한 요소이다.
✒️ 용어
Device Controller
특정 유형의 장치를 제어하는 역할을 담당하는 하드웨어
- Device Controller 1:n Devices 로 매핑 가능
- 로컬 버퍼 (Local Buffer) 와 특수 목적 레지스터 (Special-purpose Registers) 를 내장
- 주변 장치와 이 로컬 버퍼 간에 데이터를 전송하는 역할을 수행
Common Bus(System Bus)
컴퓨터 시스템 내의 모든 주요 구성 요소들을 연결시키는 하나의 공통된 통신선을 Common Bus 라고 하며,
이를 통해 데이터를 주고 받는데, 이 통신선은 세 가지 유형의 신호선을 포함한다:
- 데이터 버스 (Data Bus): 데이터 전송
- 주소 버스 (Address Bus): 데이터의 위치 지정
- 제어 버스 (Control Bus): 동작 제어 및 동기화
Shared Memory
컴퓨터 시스템 내의 여러 구성 요소들이 공동으로 접근할 수 있는 메모리 공간(RAM의 일부)을 의미한다.
Device Driver
- Device Driver 1:1 Device Controller 로 매핑
- 컨트롤러의 세부 동작을 이해
- 일관된 인터페이스를 다른 장치들에게 뿌림
Memory Cycle
CPU 또는 장치 컨트롤러와 같은 구성 요소가 메모리에 데이터를 읽거나 쓰기 위해 소요되는 단일 동작 주기를 의미
하나의 메모리 사이클은
- 주소 지정 (Addressing) → CPU 또는 컨트롤러가 접근하고자 하는 메모리 주소를 지정
- 읽기 또는 쓰기 (R/W) → 지정된 주소로부터 데이터를 읽거나 데이터를 해당 위치에 기록
- 응답 대기 (Wait/Response) → 메모리가 요청을 처리하고 응답하는 데 걸리는 시간 포함
의 과정을 행하는 시간이다.
Memory Controller
여러 구성요소의 메모리 동시 접근에 대해 충돌을 방지하고 접근을 동기화 하는 역할을 수행하는 하드웨어
- 공유 메모리에 대한 읽기/쓰기 요청 순서를 조정
- 구성 요소 간의 병렬 접근 충돌 방지
- 현대 시스템에서는 메모리 계층 구조에 따라 다양한 컨트롤러가 분산되어 존재
추후에 나옴
Synchronize
여러 구성 요소가 동시에 자원에 접근하려고 할 때, 그 순서를 조정하여 충돌이나 오류가 없도록 처리를 제어하는 것을 의미
Interrupt Request Line(IRQ Line)
Interrupt Controller 와 직접적으로 연결되어 있는 2개의 하드웨어 선이며,
하나는 Non-Maskable Interrupt(NMI), 다른 하나는 Maskable Interrupt 이다.
- Non-Maskable Interrupt: 절대 무시할 수 없는 신호를 담당(반드시 처리), 메모리 오류, 하드웨어 고장, CPU 팬 멈춤, …
- Maskable Interrupt: 일반적인 인터럽트(보편적으로 인터럽트는 이걸 말함), 중요한 계산중이라면 이 Interrupt 로 들어온건 무시 가능
Context Switching
하나의 프로세스가 CPU를 사용 중인 상태에서 다른 프로세스가 CPU를 사용하도록 하기 위해, 이전의 프로세스의 상태(context)를 저장하고 새로운 프로세스의 상태를 적재(load)하는 작업