현대의 운영체제: 인터럽트 기반 시스템, 컴퓨터 버스
컴퓨터의 전원을 켜면 먼저, POST 과정이 시작되고 그 후에 부트 로더가 하드 디스크에 있는 운영체제 프로그램을 RAM으로 가져와 할당한다. 할당된 운영체제의 커널은 컴퓨터의 전원이 꺼질 때까지 메모리에 상주한다.
이제 부팅이 다 끝나고 우리는 운영체제(윈도우) 화면을 보게 되었다. 사용자가 운영체제에서 마우스를 움직인다고 가정하자. 이 동작을 컴퓨터는 어떻게 알고 실행할 수 있을까? 바로 인터럽트를 통해 알 수 있다.
이벤트와 인터럽트
사용자가 키보드를 입력하고 마우스를 클릭하는 등의 동작이나 사건을 이벤트라고 한다. 이벤트가 발생하면 CPU에게 이벤트 발생을 알리는데, 이벤트 발생을 알리는 것을 인터럽트(Interrupt)라고 부른다. 인터럽트는 '방해하다’라는 뜻인데, 컴퓨터 용어에서는 신호를 보내 이벤트 발생을 알리는 것을 의미한다.
- 우리가 마우스의 커서를 움직이는 경우
1) 마우스를 움직이는 순간 마우스에서 인터럽트 전기신호가 발생하여 이를 CPU에게 보낸다.
2) 전기신호를 받은 CPU는 운영체제 내부에 있는 인터럽트를 처리하는 코드(Interrupt service routine, ISR)로 이동한다. 이 코드에는 마우스 움직임이 발생할 때 해야할 작업 코드(모니터의 커서 위치를 옮기는 코드)가 작성되어 있다.
3) CPU는 ISR을 실행시키고, ISR은 커서를 이동시킨 좌표를 모니터상에 표현해주는 역할을 한다.
인터럽트의 종류와 동작방법
위의 예시처럼 입출력장치로부터 오는 인터럽트뿐 아니라 전원 이상이나 기계적인 오류 때문에 발생하는 인터럽트를 포함하여 하드웨어 인터럽트(Hardwore Interrupt)라고 한다. 인터럽트라고 하면 기본적으로 하드웨어 인터럽트를 말하지만, 소프트웨어에서도 인터럽트를 요청을 할 수 있다. 소프트웨어에 의해 발생하는 인터럽트를 트랩(Trap)이라 한다.
1) 하드웨어 인터럽트
- 우리가 마우스로 한글(HWP) 아이콘을 더블클릭 하는 경우
1) 마우스를 더블클릭하면 인터럽트 CPU에게 인터럽트(전기신호)를 보낸다.
2) 전기신호를 받은 CPU는 OS 내부에 있는 인터럽트를 처리하는 코드(Interrupt service routine, ISR)로 이동한다.
3) CPU는 ISR을 실행시키고, ISR은 커서가 더블클릭한 좌표에 있는 아이콘(프로그램)을 실행한다.
4) 동시에 운영체제의 커널은 하드디스크에서 한글 실행 파일을 찾아 Memory로 옮겨온다.
5) CPU는 메모리에 있는 프로그램 명령어들을 읽어와 프로그램을 실행을 한다.
2) 소프트웨어 인터럽트(Trap)
한글 프로그램을 실행 중에 사용자가 메모장을 실행시키고 싶은 경우, 한글 프로그램이 소프트웨어 인터럽트를 발생시킨다. 그럼 CPU는 하던 일을 멈추고, 운영체제 내부에 존재하는 ISR 중에 해당 인터럽트를 처리하는 코드(ISR)로 이동해 다. 그럼 운영체제는 하드디스크에서 메모장 파일을 찾아서 메모리에 적재하고, CPU는 메모리에 적재된 프로그램 명령어를 읽어와 프로그램을 실행한다.
3) 내부 인터럽트(Internal Interrupt)
마지막으로 내부 인터럽트(Internal Interrupt)가 있다. 내부 인터럽트는 프로그램을 수행하는 도중에 발생하는 예외 상황을 처리한다. 대표적인 예로 0으로 나누는 동작이다. 프로그램의 내부에 result = a / 0; 와 같은 코드가 있을 때, CPU는 내부 인터럽트를 발생시켜 운영체제 안에 있는 ISR로 이동한다. 이 경우에는 DividedByZero 라는 ISR로 이동한다. 여기서 잘못된 동작을 수행한 프로그램을 강제로 종료시킨다.
4) 시그널
내부 인터럽트는 사용자의 의지와 상관없이 발생하는 인터럽트이지만 사용자가 직접 발생시키는 인터럽트도 있다. 작동 중인 프로세스를 끝내려고 Ctrl + C 키를 누르거나 kill 명령어를 사용하는 것이 그 예이다. 이처럼 사용자의 의지로 발생시키는 자발적 인터럽트를 시그널이라고 부른다.
운영체제의 커널은 부팅에 의해 메인 메모리에 올라온 후 평소에는 대기 상태에 있으면서 인터럽트 신호를 받은 CPU가 ISR를 실행시키면 그때 작업을 수행한다.
🔎 아직 풀리지 않은 의문이 한 가지 더 남았다.
인터럽트를 통해 CPU가 ISR을 실행시키고, OS는 하드디스크에서 프로그램을 메모리로 적재해 CPU가 비로소 메모리에 있는 프로그램 명령어들을 읽어와 프로그램을 실행을 하는 것까지는 알았다. 즉, 프로그램을 메모리까지 무사히 적재해야 CPU가 프로그램을 실행시킬 수 있다.
그럼 메모리에 있는 프로그램과 데이터를 CPU로 어떻게 가져와 실행할 수 있는 걸까?
컴퓨터 버스
우리가 버스를 통해 다른 곳으로 이동을 할 수 있는 것처럼, 컴퓨터 버스(Bus)는 CPU와 주변 장치 사이에서 데이터 전송역할을 하는 통로(선로)이다. CPU가 한번에 처리할 수 있는 데이터 단위를 워드(word)라고 하는데, 워드 단위로 데이터를 전송하도록 설계되었다. 32비트의 OS는 4Byte, 64비트의 OS는 8Byte의 워드 크기를 가진다.
컴퓨터 내의 버스 종류는 내부 버스와 외부 버스로 나눌 수 있다.
내부 버스: 마을 버스와 같은 존재(BSB, back-side bus)
- CPU 회로 안에서 (ALU와 레지스터), (ALU와 제어장치), (제어장치와 레지스터) 간의 데이터 전송을 위한 버스
외부 버스: 광역 버스와 같은 존재(FSB, front-side bus)
- CPU와 주변 장치 사이의 데이터 전송을 위한 버스
- 구조 상 구분: 시스템 버스(데이터 버스, 주소 버스, 제어 버스), I/O 버스, 그래픽 버스 등
- 기능 측면으로 구분: 시스템 버스(데이터 버스, 주소 버스, 제어 버스)
하나의 버스 채널의 3가지 기능
1) 제어 버스(Control bus)
다음에 어떤 작업을 할지 지시하는 제어 신호가 오고 간다. 메모리에서 데이터를 가져올지, 처리한 데이터를 옮겨 놓을지에 대한 지시 정보가 오고 가는데 메모리에서 데이터를 가져올 때는 읽기(read) 신호를 보내고, 처리한 데이터를 메모리로 옮겨놓을 때는 쓰기(write) 신호를 보낸다. 제어 버스는 CPU가 시스템 내의 각종 요소들의 동작을 제어할 수도 있고, 주변 장치들 또한 CPU의 연산을 멈출 수도 있는 양방향 버스이다.
1) 주소 버스(Address bus)
메모리의 데이터를 읽거나 쓸 때 어느 위치에서 작업할 것인지를 알려주는 메모리의 위치 주소가 오고 간다. 주변 장치의 경우도 마찬가지로 하드디스크의 어느 위치에서 데이터를 읽어올지, 어느 위치에 저장할 지에 대한 위치 정보가 주소 버스를 통해 전달된다. 주소 버스는 메모리 주소 레지스터와 연결되어 있으며 단방향이다. CPU에서 메모리나 주변 장치로 나가는 주소 정보는 있지만 주소 버스를 통해 CPU로 전달되는 정보는 없다.
2) 데이터 버스(Data bus)
제어 버스가 다음에 어떤 작업을 할지 신호를 보내고 주소 버스가 위치 정보를 전달하면 데이터가 데이터 버스에 실려 목적지까지 이동한다. 데이터 버스는 메모리 버퍼 레지스터와 연결되어 있으며 양방향으로 데이터 전달이 가능하다. 버스의 대역폭은 한 번에 전달할 수 있는 데이터의 최대 크기를 말하는데, CPU가 한 번에 처리할 수 있는 데이터의 크기와 같다. 우리가 32bit CPU, 64bit CPU라고 하는 32bit, 64bit는 CPU가 한 번에 처리할 수 있는 데이터의 크기인 워드를 말한다.
채널 분리
초창기에는 System Bus 하나의 단일 구조였지만 같은 버스에 연결된 장치들 사이의 속도 차이로 인해 병목 현상이 발생했다. 느린 트랙터(주변장치) 때문에 뒤따르는 승용차(CPU와 메모리)가 느리게 갈 수 밖에 없는 현상과 같다. 주변 장치는 저마다 데이터 전송송도가 다르기 때문에 여러 개의 버스를 만들어 이를 해결하고자 했다. 이때 데이터가 지나다니는 하나의 통로를 채널이라고 하는데 4채널 버스는 4개의 주변장치가 동시에 데이터를 주고 받을 수 있는 4차선 도로와 같다. 초기에는 모든 장치가 하나의 버스로 연결되어 있어서 CPU가 작업을 하다가 입출력 명령을 만나면 직접 입출력장치에서 데이터를 가져오는 폴링 방식을 사용했다. 그러나 주변장치에서 데이터를 직접 가져오는 것은 매우 느리기 때문에 요리사가 직접 냉장고까지 가지 않도록 보조 주방을 두는 것처럼 메인 버스와 입출력 버스 구조를 만들었다.
이제 현대의 컴퓨터는 CPU와 메모리를 연결하는 메인버스, CPU와 그래픽카드를 연결하는 그래픽 버스, 고속 입출력 버스와 저속 입출력 버스를 사용한다.
CPU 내부 버스의 속도는 시스템 버스의 속도보다 빠르기 때문에 메모리를 비롯한 주변장치의 버스 속도가 내부 버스 속도를 따라가지 못한다. 이러한 장치 간의 속도 차이를 개선하기 위한 하나의 방법으로 캐시가 있었다.
캐시는 메모리와 CPU 간의 속도 차이를 완화하기 위해 메모리의 데이터를 미리 가져와 저장해두는 임시 장소다.
CPU가 사용할 것으로 예상되는 데이터를 미리 가져다놓는데, 이렇게 미리 가져오는 작업을 'prefetch'라고 한다. 캐시는 CPU 안에 있으며 CPU 내부 버스의 속도로 작동한다. 따라서 주 메모리(DRAM)에서 시스템 버스로 데이터를 가져오는 것보다 더 빠르게 데이터를 실행할 수 있다.
캐시는 메모리의 내용 중 일부를 미리 가져오고 CPU는 메모리에 접근해야 할 때 캐시를 먼저 방문하여 원하는 데이터를 있는지 찾아보게 된다.
컴퓨터 구성 요소간 통신
1. CPU와 메모리: 적재(Load)와 저장(Store) 명령으로 상호 작용이 일어난다.
(1) 적재(Load)
- 주소 버스: 메모리에서 불러올 데이터가 저장된 주소값이 전달 (CPU -> Memory)
- 제어 버스: Memory Read 신호 전달 (CPU -> Memory)
- 데이터 버스: 지정한 메모리에 저장된 값 전달 (Memory -> CPU)
(2) 저장(Store)
- 주소 버스: 메모리에서 해당 데이터를 저장할 주소값 전달 (CPU -> Memory)
- 제어 버스: Memory Write 신호 전달 (CPU -> Memory)
- 데이터 버스: 저장할 데이터 전달 (CPU -> Memory)
2. CPU와 I/O Unit: 입력(Input)과 출력(Output) 명령으로 상호 작용이 일어난다.
(1) 입력(Input)
- 주소 버스: 해당 입출력 장치의 포트 번호 전달 (CPU -> I/O Unit)
- 제어 버스: I/O Read(input) 신호 전달 (CPU -> I/O Unit)
- 데이터 버스: 해당 입출력 장치는 데이터 전달 (I/O Unit -> CPU)
(2) 출력(Output)
- 주소 버스 : 해당 입출력 장치의 포트 번호 전달(CPU -> I/O Unit)
- 제어 버스 : I/O Write(output) 신호 전달(CPU -> I/O Unit)
- 데이터 버스 : 입출력 장치에 데이터 전달(CPU -> I/O Unit)
(3) 인터럽트(Inputerrupt)
입출력장치는 제어신호를 통해 인터럽트 요청을 보내 CPU에 입출력 작업을 요청할 수 있다.
- 인터럽트 요청: 입출력 준비를 마친 I/O Unit이 CPU에 입출력 작업의 시작 요청
- 인터럽트 확인: CPU가 입추력 동작을 수행할 것을 입출력장치에 통보
직접 메모리 접근(DMA)
메모리는 CPU의 명령에 따라 작동하기 때문에 입출력 제어기는 CPU의 명령이 있어야 다양한 주변 장치에서 온 데이터를 메모리에 옮길 수 있다. 그러나 직접 메모리 접근 방식은 CPU의 도움 없이도 메모리에 접근할 수 있도록 입출력 제어기에 권한을 부여하였다.
3. 메모리와 I/O Unit: 메모리와 입출력장치의 통신은 CPU를 이용하지 않고 직접 메모리 액세스 방식(DMA, Direct Memory Access)으로 통신한다.
- DMA 제어기는 CPU를 대신해 주소 버스와 제어버스에 신호를 전달
- DMA 제어기는 통신 시작 전 CPU에 버스 사용에 대한 허가를 받아야 한다.
DMA 제어기를 통해 입출력 장치에서 가져온 데이터 혹은 입출력 장치로 가져갈 데이터가 저장된 메인 메모리는 CPU가 작업하는 공간과 겹치게 된다. 그래서 CPU가 작업하는 공간과 DMA 제어기가 데이터를 옮기는 공간을 분리하여 메인 메모리를 운영하는데 이를 메모리 맵 입출력이라고 부른다. 메모리 맵 입출력에서는 메인 메모리의 주소 공간 중 일부를 DMA 제어기에 할당하여 작업 공간이 겹치는 것을 막는다.