| 2.1 컴퓨터 구조 |
| 2.1.1 컴퓨터 아키텍쳐 |
| 2.1.2 컴퓨터 시스템 / 프로그램 작동 원리 |
| 2.1.3 풀링과 인터럽트, DMA |
| 2.1.4 병렬처리 |
이번 포스트에서는 운영체제를 다루는데 꼭 필요한 컴퓨터 구조에 대해서 가볍게 훑고 지나갈 것이다. 하드웨어 구조에 대한 큰 흐름을 이해하는 것을 목표로 하자.
2.1.1 컴퓨터 아키텍쳐
우리는 카카오톡, 크롬 등의 기타 응용프로그램을 통해 컴퓨터라는 기계를 다룬다. 이러한 응용 프로그램은 지난 시간에 언급했듯이 하드웨어에 대한 직접적 접근이 허락되지 않는다. 운영체제라는 소프트웨어가 모든 자원을 독점적으로 관리하고 있기 때문이다. 즉, 운영체제는 하드웨어를 홀로 지배하고있다. 컴퓨터 하드웨어 위 운영체제, 그리고 그 위 응용프로그램의 순서로 이해하면 된다.
컴퓨터라는 기계, 즉 하드웨어는 어떤 구조로 이루어져 있을까? 소프트웨어는 결국 하드웨어 기반으로 작동하는 프로그램이다. 따라서 우리는 이 기계를 다뤄야 할 필요가 있다.
1. 컴퓨터 하드웨어 구성
컴퓨터 부품은 많지만 가장 단순하게 CPU와 메모리 이 두 가지만 있다면 이를 컴퓨터라고 부를 수 있다. 이는 폰노이만 구조라 한다.
CPU : 명령어를 해석하여 실행하는 장치 Patch-Decode-Execute메모리 : 명령어들을 저장하고, 필요한 데이터들을 저장하는 곳.기타 : 입력 장치, 출력 장치, 저장 장치 등
컴퓨터에 연결되는 모든 장치들은 최종적으로 메인 보드와 연결된다. 본체의 메인보드가 나갔다, 라는 표현의 그 메인보드가 이 메인보드이다.
그리고 메인보드에 연결된 각 장치들은 메인 보드 내 버스를 통해 통신된다. 택시가 아니고 버스인 이유가 있다. 하나의 선을 공유하기 때문이다. 물론 정말 하나만 있는 것은 아니고, CPU 내부 버스 / 시스템 버스 / I.O 버스 / 데이터 버스 / 주소 버스 / 제어 버스 등이 있다.
위 내용들을 머리에 담고 컴퓨터의 동작을 하나씩 살펴보도록 하자.
2. 폰 노이만 구조
컴퓨터의 작동 방식은 단순하다. 명령을 읽고, 실행하는 것이다. 물론 이 과정을 수행하기 위해서 몇 가지 과정을 거쳐야한다. 디스크에 저장되어있는 프로그램을 메인 메모리에 적재하고, 메모리에 적재되어잇는 프로그램이 CPU까지 이동해야한다. 도식으로 나타내면 다음과 같다.
디스크 → 메모리 → CPU
화살표는 버스이다.
여기서 주목해야할 점은 CPU가 연산을 진행하기 위해서는 반드시 메인 메모리에 적재되어야한다는 점이다. 메모리 관리가 중요한 이유이다. 램은 클수록 좋다는 말이 괜히 들리는 것이 아니다. 이러한 구조로 인해 폰 노이만 구조는 필연적인 단점이 생긴다.
답을 알기 위해서는 클럭 Clock과 버스 BUS의 동작 원리를 알아야한다.
먼저 클럭이다. 컴퓨터의 장치들은 클럭 단위로 작업을 한다. 오케스트라의 악기가 시간의 차이 없이 정확하게 연주되는 것처럼 클럭을 통해 컴퓨터의 장치들이 동기화되는 것이다. CPU는 1클럭에 하나의 명령을 처리하는 것을 목표로 한다. 빠른 클럭은 대부분의 경우 높은 성능을 의미한다.
다음은 버스이다. 버스는 컴퓨터의 각종 신호가 오고가는 경로이다. 버스에는 크게 제어 버스, 주소 버스, 데이터 버스가 있는데 제어버스로 'Read / Write'를 결정하고, 주소 버스로 읽거나 쓰고자하는 메모리 위치의 주소를 적어둔다. 마지막으로 데이터 버스로 데이터가 이동하는 방식이다. 첫 번째 클럭에 제어 버스와 주소 버스가 정보를 전달하면, 그 다음 클럭에 해당 데이터가 입력 / 출력 된다.
모든 동작이 한 클럭에 하나씩 딱딱 맞춰 일어난다면 문제가 없겠지만 그렇게 쉬운 일이 아니다. 읽어야 할 데이터가 버스의 크기보다 크다면, 즉 경로가 더 좁다면 당연히 여러 클럭에 걸쳐 나눠 읽을 수 밖에 없다. 또한 갑자기 한꺼번에 많은 입출력을 요청하게 된다면 대기가 생길 수 밖에 없을 것이다. 설상가상으로 메모리는 태생적으로 CPU보다 느리다. 병목 현상이라고 하는 문제를 피할 수 없는 것이다. 컴퓨터가 느리다고 느끼는 대부분의 이유는 바로 이 병목 현상 때문이다.
물론 문제가 생긴다면 항상 해결 방안을 고안해내기 마련이다. 이런 병목 현상의 해결책으로는 두 가지가 있다.
첫 번째는 물리적으로 해결하는 방법이다. 메모리가 CPU보다 느리다면 메모리가 클럭을 낭비하지 않도록 하자, 라는 취지로 나온 것이 DDR, Double Data Rate 방식이다. 또 병렬화를 한 듀얼 채널 방식이 있다. 아주 단순하게는 메모리를 메인보드 상에서 최대한 CPU와 가까운 위치에 놓는 것이다. 전기 신호가 물리적으로 이동하는 거리를 짧게하여 속도를 높인 것이다.
두 번째는 구조적 해결법이다. 버퍼 Buffer를 도입함으로써 CPU와 메모리 간의 속도 차이를 완화하는 방식이다. 버퍼는 데이터 전달 시 임시 저장 공간을 의미하는데 일정량의 데이터를 모아 옮김으로써 속도를 완화한다. 말은 이렇게 하지만 정확히 어떻게 작동하는 것일까? 지금부터 버퍼에 대해 자세히 알아보겠다.
3. 메모리 계층 구조
버퍼에 대한 이야기를 한다면서 갑자기 메모리로 넘어간 이유가 있다. 앞서 버퍼는 데이터 전달 시 임시 저장하는 공간이라고 말했다. 저장, 즉 버퍼 또한 메모리인 것이다. 그런데 이 버퍼가 메모리보다 느리면 당연히 사용할 필요가 없지 않을까? 컴퓨터의 버퍼는 메인 메모리보다 훨씬 빠른 속도를 가진 캐시로 이루어져있다. 캐시는 메모리와 CPU간의 속도 차이를 완화하기 위해 메모리에서 앞으로 사용될 것으로 예상되는 데이터를 미리 가져와 임시 저장해두는 장소인 것이다.
따라서 CPU는 메모리에 접근해야 할 때 굳이 느린 메인 메모리까지 가지 않는다. 먼저 가장 가까이 있는 캐시에 우선 방문해 필요로 하는 데이터가 있는지 확인한다. 만약 찾았을 경우 Cache hit, 찾지 못하였을 경우 Cache miss라고 한다.
캐시와 메인 메모리인 RAM을 제외한 다른 메모리의 경우 운영 체제가 아닌 컴퓨터 구조에서 더 자세히 다루도록 하겠다.
2.1.2 컴퓨터 시스템 / 프로그램의 작동 원리
전반적인 컴퓨터 구조를 다뤄보았으므로 이제 시스템에 대해 알아보겠다. 여기서 우리가 집중할 주요 타겟은 CPU이다. 물론 메모리도 살짝 얹어질 것이지만, 일단은 CPU가 주요 쟁점이다. 그렇다면 CPU의 구성부터 살펴보자.
1. CPU의 구성 요소 - ALU, Register, Control unit
CPU는 명령어를 처리해주는 컴퓨터의 뇌이다. 따라서 당연히 연산하는 장치가 필요하다. 또한 명령어를 어디까지 진행하였고 앞으로 어떤 명령어를 실행해야하는지 순서를 까먹는다면 곤란할 것이다. 따라서 제어 유닛도 필요하다. 마지막으로 연산 도중 데이터를 임시 저장할 공간이 필요하기 때문에 레지스터 또한 CPU의 필수 구성요소이다.
산술논리 연산장치 ALU : 데이터의 덧셈, 뺄셈, 곱셈, 나눗셈과 같은 산술 연산과, AND, OR 같은 논리 연산을 수행 제어 장치 : CPU에서 명령어에 따른 작업을 지시 레지스터 : CPU 내에 데이터를 임시로 보관
2. 명령어
CPU만큼 프로그램의 작동에 중요한 것은 바로 명령어이다. 아무리 CPU가 좋아도 명령어가 없거나, 엉터리라면 프로그램은 당연히 작동될 수 없다.
명령어란 CPU가 해석할 수 있고 실행할 수 있는 기계 명령이다. 사람이 코딩한 고급 언어가 아닌 기계가 이해할 수 있도록 컴파일 된 코드라는 이야기이다.
또한 CPU의 종류마다 명령의 이름, 기계어 코드, 크기, 개수 등이 다르다. 예를 들어, 인텔 CPU와 AMD CPU의 명령어는 서로 다르다.
이러한 기계어 코드들이 메모리에 적재가 되어야하며 CPU는 순차적으로 이 기계어 명령어들을 읽어내려가며 지정된 동작을 수행한다.
3. CPU의 명령어 처리 과정, Instruction cycle
Fetch, Decode, Execute!
가져와서(fetch) 해석한 뒤 (decode) 실행한다! (execute) 이 세가지로 Instruction cycle을 설명할 수 있다.
풀어서 설명하자면 기계어 코드를 프로그램 카운터(레지스터의 한 종류)를 통해 순차적으로 읽는다. 그 후 현재 실행해야 할 명령어가 무엇인지 해석하고, 명령어를 수행한다.
예시를 들어보자. 여기 명령어 다섯 가지가 있다.
001 00001010 0001
011 00000010 0001
011 00001100 0001
100 0001 00000011
010 0001 00000000
각각의 명령어는 15 bit로 이루어졌으며, 앞의 3bit는 CPU에게 무슨 행동을 해야할 지 알려주는 instruct, 그 후는 Argument이다. 당연히 이것만 있다면 해석할 수 없다. 컴퓨터도 마찬가지이다. 따라서 이제부터 CPU의 설명서인 ISA를 일부 가져오겠다.
| Instruction | Code |
| Store A into R | 001 A[--------] R[----] |
| Read a value from R | 010 R[----] 00000000 |
| add A to R | 011 A[--------] R[----] |
| Divide a value in R by a | 100 R[----] A[--------] |
위 ISA를 이용하면 위의 기계어 코드가 어떤 의미를 뜻하는지 해석할 수 있다.
001 00001010 0001
001 : Store
00001010 : 10(10) into
0001 : Register 0001
위의 기계어 명령어는 레지스터 1에 10을 저장하라, 라는 뜻이 된다.
같은 방법으로 나머지 명령어를 차례대로 해석한다면,
레지스터 1에 10을 저장하라.
레지스터 1에 2를 더하라.
레지스터 1에 12를 더하라.
레지스터 1의 값을 3으로 나누어라.
레지스터 1의 값을 읽어라.
레지스터 1에 든 값은 순서대로 10, 12, 24, 8로 바뀌므로 최종적으로 읽은 값은 8이 될 것이다. 위와 같은 방법으로 CPU는 기계어 코드를 처리하고 결과를 사용자에게 알려준다.
4. 메모리 보호
지금까지 기본적인 CPU의 작동과정을 알아보았다. 하지만 현대의 운영체제를 공부하기 위해 반드시 알아야 할 개념이 있다. 바로 메모리 보호이다.
우선 메모리를 보호한다는 개념이 무엇인지부터 파악할 필요가 있다. 먼저 짚고 넘어가자면 악성 코드나 해킹같은 외부로부터의 위협보다도 컴퓨터 자체의 보호이다. 마우스 피스라고 생각하면 편하겠다. 물론 남이 와서 때리는 걸 막아주기도 하지만 우선 내가 혀 씹는 걸 막아주는 것이라고 이해하면 된다.
메모리 보호의 필요성은 우리가 프로그램을 하나만 사용하지 않는 것에서 기인한다. 현대의 운영체제는 시분할 기법을 사용하여 여러 프로그램을 동시에 실행하고 있다. 사용자 영역이 카카오톡, 크롬, 게임 등등으로 나누어져 있다는 뜻이다. 이 모든 프로그램은 당연하게도 메모리를 사용하고 있다. 그런데 만약 카카오톡이 크롬의 메모리 공간을 침범한다면 어떻게 될까? 침범받은 크롬은 물론 카카오톡도 정상적으로 작동하기 힘들 수 있다. 여기서 우리가 다시 떠올려야 할 사실이 있다. 운영체제도 소프트웨어이다. 응용프로그램이 운영체제의 메모리 영역을 침범한다면? 그다지 상상하고 싶지는 않다.
따라서 메모리 보호 기법이 나오게 되었다. 분할, Segmentation이라고 불리는 메모리 보호기법은 크게 4단계로 나눌 수 있다.
1. 작업의 메모리 시작 주소를 경계 레지스터에 저장 후 작업
2. 작업이 차지하고 있는 메모리의 크기, 즉 마지막 주소까지의 차이를 한게 레지스터에 저장
3. 사용자의 작업이 진행되는 동안 두 레지스터의 주소 범위를 넘어가는지 하드웨어적으로 점검
4. 두 레지스터 값을 벗어나면 메모리 오류와 관련된 인터럽트가 발생,
운영체제가 즉시 프로그램 강제 종료 (Segmentation fault)
예를 들어보자. 우리가 카카오톡을 실행한다고 하자. 카카오톡은 메모리 100번지에서 시작한다. 따라서 경계 레지스터에 100을 저장한다. 그리고 카카오톡은 총 40의 메모리 크기를 가지고 있다. 한계 레지스터에는 40이 저장 될 것이다. 즉 카카오톡이 사용할 수 있는 메모리는 100부터 140번지까지이다. 그런데 만약 카카오톡이 150번지에 접근하려고 한다면 무슨 일이 일어날까? 100과 140 이외의 값에 접근하는 것을 확인한 운영체제가 카카오톡을 강제로 종료시킨다. 이것이 Segmentation fault이다.
5. 부팅
마지막으로 알아볼 것은 바로 부팅이다. 부팅이라는 단어는 일상 생활에서도 많이 사용할 만큼 익숙한 단어일 것이다. 컴퓨터가 켜지는 것을 우리는 흔히 부팅이라고 표현한다. 그런데 정말 정확한 부팅 Booting은 무엇일까?
부팅이란 컴퓨터를 켰을 때 운영체제를 메모리에 올리는 과정이다.
메모리는 휘발성이라는 점을 생각하자. 휘발성이란 컴퓨터를 껐을 때, 정보가 모두 지워진다는 뜻이다. 운영체제 또한 소프트웨어이므로 컴퓨터를 끄면 당연히 메모리에서 지워진다. 컴퓨터를 킬 때, 디스크에 잠들어있는 운영체제를 컴퓨터의 메모리에 자동으로 올려주는 무언가가 필요하다는 이야기이다. 운영체제를 메모리에 올리는 과정, 이것을 부팅이라고 한다.
먼저 ROM에 들어있는 BIOS가 로드된다.
이 BIOS는 운영체제를 실행시켜주기 위한 프로그램으로 하드웨어 점검을 한 뒤 미리 '약속된' 저장장치의 '약속된' 위치의 프로그램을 실행시킨다. MBR, 디스크 (약속된 저장 장치) 의 첫 번째 섹터에 있는 부팅 정보(약속된 위치의 프로그램)를 메모리에 읽어오는 것이다. 이를 부트 로더; 부트 스트랩이라 한다.
부트로더는 디스크에 있는 운영체제 코드를 메모리로 읽어오고, CPU는 이 운영체제 프로세스를 실행함으로써 부팅이 완료된다.
2.1.3 풀링과 인터럽트, DMA
이제 기본적인 컴퓨터의 작동 과정까지 알아보았다. 이제는 컴퓨터가 작동하며 생길 수 있는 상황과 그 해결 방안에 대해 알아볼 것이다.
우리가 위에서 배운대로 CPU가 명령어의 순서에 따라 아주 원활하게 작업을 처리하고 있다. 그런데 그 순간, 키보드로부터 입력이 들어온다. CPU는 하던 일을 멈추고 외부에서 요청한 새로운 작업을 시작해야한다. 이 때 CPU가 이 상황을 처리하는 방법은 두 가지가 있다. 바로 풀링과 인터럽트이다.
1. 풀링 Polling
풀링은 한 마디로 CPU가 직접 왔다갔다하는 수동 시스템이다. CPU는 주기적으로 외부 상태를 검사하여 요청이 있다면 처리한다. 메세지는 CPU가 poll을 할 때까지 기다려야하는것이다. 당연히 자주 오갈수록 메세지에 대한 응답 속도는 향상되지만 CPU 입장에서는 귀찮은 일이 된다. 전체적인 작업 속도가 떨어지는 것이다. 무음 상태인 핸드폰에 문자가 오는지 오지 않는지 5분 간격으로 확인하면 당연히 공부를 많이 할 수 없듯이.
2. 인터럽트 Interrupt
인터럽트는 풀링과는 반대로 자원들이 직접 CPU에게 자신의 상태를 알려준다. 알림 시스템이 있는 것이다. 당연히 일일히 검사하는 시간이 줄어드니 CPU의 작업 처리 속도는 풀링보다 유리하다.
그렇다면 외부 장치들은 어떤 방식으로 CPU에게 알림을 보낼까? 외부장치가 CPU를 사용해야 할 때, IQU Interrupt ReQuest를 전송한다. 이 때, 각 IRQ에는 Interrupt number가 있다. 누가, 어떤 인터럽트를 발생시킨 것인지 구분하기 위한 고유의 값인 것이다. 이러한 인터럽트가 발생하면 CPU는 하던 일을 멈추고 인터럽트를 처리할 준비를 한다. 간단하게 표현하면, ① 현재 CPU의 각종 레지스터와 상태를 저장하고, ② 인터럽트 핸들링을 위해 ISR(Interrupt Service Routine)을 수행한 뒤, ③ 다시 원래 수행하던 프로그램으로 돌아와 이어서 처리한다. 간단히 말해, 하던 일 저장하고 다른 일 했다가 다시 원래대로 돌아오는 것이다. 인터럽트 과정을 자세히 표현하면 물론 이것보다 훨씬 복잡하지만 그것은 컴퓨터 구조에서 다루도록 하겠다.
이러한 인터럽트는 분명 풀링보다 CPU의 활용률을 높일 수 있다. 하지만 역시나 너무나 많은 인터럽트는 오히려 풀링보다 못한 결과가 나올 것이다. 또한 인터럽트가 동시에 여럿 발생했을 경우 어떻게 처리해야할지도 문제가 될 수 있다.
인터럽트의 종류
: 인터럽트는 하드웨어 인터럽트와 소프트웨어 인터럽트로 분류할 수 있다.
하드웨어 인터럽트 : CPU 외부의 디스크 컨트롤러나 주변 장치로부터의 요구
- 기계검사 인터럽트 : 프로그램을 실행하는 컴퓨터 자체 내에서 기계적인 문제가 발생한 경우
- 외부 인터럽트 : 오퍼레이터나 타이머에 의해 의도적으로 프로그램이 중단된 경우
- 입출력 인터럽트 : 입출력의 종료나 입출력의 오류에 의해 CPU의 기능이 요청되는 경우
- 프로그램 검사 인터럽트 : 프로그램 실행 중 보호된 기억공간 내에 접근하거나 불법적인 명령 수행과 같은 프로그램의 문제가 발생한 경우
소프트웨어 인터럽트 : CPU 내부에서 자신이 실행한 명령이나 CPU의 명령 실행에 관련된 모듈이 변화하는 경우
- 프로그램 실행 중 프로그램 상의 처리 불가능한 오류나 이벤트를 알리기 위한 경우 발생
- 트랩 또는 예외라고 부름. e.g., 허용되지 않은 주소 참조, 0으로 나누기
그렇다면 풀링과 인터럽트 중에서 무조건 인터럽트가 더 효율적일까? 답은 아니다, 이다. 단순한 장치를 모니터링 하는 것에는 풀링의 방식이 더 효율적일 수 있다.
3. DMA
풀링을 선택하든, 인터럽트를 선택하든 우리가 마주해야 하는 필연적인 문제 하나가 있다. 바로 CPU와의 근본적인 속도차이이다. 어쨌든 풀링이든 인터럽트든 모두 CPU가 관여되기 때문이다. 여기서 등장하는 것이 DMA이다.
DMA란 Direct Memory Access의 약자로 CPU의 개입 없이 메모리와 하드웨어 장치 간 데이터를 전송해주는 제어기이다.
CPU가 일일히 인터럽트 장치에서 데이터를 옮기고 있는 것은 시간 낭비이니 대신 데이터를 메모리로 올려주고 CPU는 그 시간동안 다른 일을 처리하다가 곱게 정리된 데이터만 쏙 가져가도록 도와주는 장치인 것이다. 일종의 비서라고 볼 수 있겠다.
작동 원리를 가볍게 살펴보자. 먼저 CPU가 DMA에게 데이터의 메모리, 주소, 크기, 대상 장치들을 DMA 컨트롤러에 지시한다. CPU의 할 일은 여기서 끝난다. 그럼 DMA는 지시받은 사항대로 데이터를 메모리에 올려놓는 것이다. 작업이 끝나면 DMA 컨트롤러는 CPU에게 작업이 완료되었으니 데이터를 가져가라고 인터럽트를 발생시켜 작업의 완료를 알린다.
여기까지가 기본적인 작동 내용이지만, 한 가지 사실을 상기하면 재미있는 일이 발생한다. 위에서 말했듯 BUS는 하나다. 만약 CPU와 DMA가 동시에 버스를 써야한다면 어떤 일이 일어날까? 우선순위를 어떻게 가지냐에 따라, 세 가지 모드가 가능하다.
1. 사이클 스틸링 Cycle Stealing 모드 | 경쟁적
속도가 빠른 CPU가 속도가 느린 DMA에게 버스 우선 순위를 주어 빠른 입출력이 가능하게 하는 방법.
한 번의 DMA 동작 중 한 Word 정도의 데이터를 전송 시 사용한다.
2. 버스트 Burst 모드 | 배타적
한 번의 DMA 동작 중 Block 단위의 데이터 전송 시 사용, 데이터 전송 완료 시까지 DMA의 버스 독점
여러 개의 메모리 워드로 구성된 블록이 지속적으로 전송
고속의 입출력 장치를 대상으로 한다.
3. Demand tranfer mode
DMA가 CPU에게 요구한 카운트 만큼 버스의 제어권을 가지는 모드.
카운트가 다 되거나, 중간에 DMA가 제어권을 반환하면 CPU가 다시 제어권을 가져감.
2.1.4 병렬처리
말 그대로 동시에 여러개의 명령을 처리하여 작업을 효율적으로 하는 방식이다. 컴퓨터가 동시에 여러 개의 명령을 처리하는 방법을 파이프라이닝 Pipelining이라고 한다.
'CS > 운영 체제' 카테고리의 다른 글
| 3.1 프로세스의 개념 (4) | 2024.04.17 |
|---|---|
| 3.0 프로세스 (1) | 2024.04.17 |
| 1.2 운영체제의 구조 (0) | 2024.04.11 |
| 1.1 운영 체제란? (2) | 2024.04.09 |
| 0. 운영 체제 (1) | 2024.04.09 |