컴퓨터 구조에 대한 두번째 이야기
여기서의 컴퓨터 구조는 CPU에 국한되어 있다.
컴퓨터 구조의 접근 방법
컴퓨터를 디자인 하자
CPU를 공부하는데에 있어서 직접 디자인 하는 방식으로 공부를 해보자
프로그래머 관점
컴퓨터 구조를 잘아는 프로그래머도 컴퓨터 디자인에 참여한다. 컴퓨터 디자인은 레지스터와 명령어를 디자인한다.
- 레지스터와 명령어 디자인은 초기 단계부터 같이 병행되어서 전개가 되야 한다.
- 왜냐면 명령어가 어떻게 구성이 되어 있냐에 따라서 레지스터 셋이 결정이 되고 레지스터 셋이 결정이 되야만 하드웨어 전체적인 구성이 나올 수 있다.
- 따라서 프로그램 측면에서 레지스터와 명령어를 디자인 해볼꺼다.
레지스터 디자인의 핵심
레지스터는 몇 비트로 구성할 것인가?
- 8,16,32,64(이 책에서는 16bit)
- 보편적으로 레지스터는 비트 수와 일치한다 예를들어 n bit 시스템이 있다고 하면 레지스터도 n bit 이다.
- n bit 시스템에서는 명령어의 길이가 n bit 이다.
- 명령어의 길이가 n bit이기 때문에 레지스터도 n bit여야지만 하나의 명령어를 저장할 수 있다.
- 그렇기때문에 32bit인 경우 데이터의 이동의 최소단위가 32bit이다.
몇 개 정도로 레지스터를 구성할 것인가?
- 레지스터 수는 많을수록 좋다 하지만 무작정 늘릴 수는 없다.
- 이 책에서는 레지스터의 개수를 8개로 구성했다.
레지스터 각각을 무슨 용도로 사용할 것인가?
- 레지스터는 특별한 목적을 가지고 있는 메모리 장치다
- RAM이나 H/D 같은 경우 무엇이든지 저장할 수 있는 저장 장치다.
- RAM이나 H/D에 뭔가를 저장하는데 제한이 있는게 아닌 범용적인 저장 장치다.
- 하지만 레지스터는 특별한 목적을 가지고 있는 저장 장치다.
- 그래서 디자인할때 용도를 정해야 한다. 그리고 용도를 정하면 명령어가 단순해지고 속도가 빨라지는 장점이 있다.
- 속도가 빨라지는 이유는 특별한 목적이 정해져 있어서 고민할 필요 없이 원하는 것을 저장하고 가져갈 수 있기 때문이다.
- r0 ~ r3은 범용 레지스터다.(연산을 위한 레지스터다.)
- r4 ~ r7은 특별한 용도가 있는 레지스터다.
명령어 구조 및 명령어 디자인
명령어의 기본 모델
- CPU가 16 bit라면 명령어도 16bit가 되어야 한다.
- 명령어의 길이가 16bit 여야지만 CPU로 명령어가 fetch 될때 하나의 명령어가 하나의 레지스터에 저장이 가능하다.
- 16bit 시스템에서는 레지스터의 크기가 16bit이고 명령어도 16bit로 구성되어 있다.
- CPU 구성 형태에 따라서 명령어 구조가 달라진다. 그래서 CPU가 달라지면 이에 따른 명령어 구조가 달라지기 때문에, 어셈블리 언어로 구현된 프로그램은 구조가 다른 CPU로 이식이 불가능하다.
명령어가 디자인 안되어 있으면 ALU도 디자인 할 수 없다. 명령어 안에 연산자, 저장소 등 이런 정보들이 어떻게 나눠져있는지 알아야지 ALU가 명령어를 해석할 수 있다. 그렇기 때문에 명령어 디자인이 CPU 디자인과 동시에 진행되어야 한다. CPU 디자인은 하드웨어 전문가 뿐만 아니라 소프트웨어 전문가의 일이기도 하다.
명령어는 복잡하게 구성되어 있다. 명령어에는 일을 시키기 위한 기본적인 정보가 다 담겨있다. 예를들어 16bit 명령어 안에 어떠한 연산을 할것이고 그 연산 결과는 어디에 저장 할것이며 연산의 피연산자는 무엇인지에 대한 정보들이 하나의 명령어로 묶여있다.
명령어 안에는 피연산자 정보들도 들어가 있다. 명령어에 따라서 저장소, 피연산1, 피연산자2 이러한 조합들이 달라진다. 위 그림은 사칙연산 명령어 구조이다.
저장소(3bit)
- ADD 연산자 뒤에 세 개의 비트에는 연산 결과를 저장할 저장소 정보를 둔다.
- 이곳에는 항상 연산 결과를 저장할 레지스터 정보만 올 수 있게 제한한다.
연산자
- 연산에 대한 정보는 세 개의 비트만 할당했기 때문에 우리가 만들어 낼 수 있는 연산자는 최대 8개다
피연산자 1,2(4bit)
- 레지스터
- 숫자
- 맨 왼쪽 bit가 0이면 뒤에 3개 bit는 숫자를 나타낸다.
- 1이면 레지스터가 오는거다 라는 약속을 해야 뒤에 3개의 bit가 레지스터인지 숫자인지 구분할 수가 있다.
- 명령어를 디자인하는데에 있어서 허용하는 범위가 넓으면 넓을수록 손해보는 bit가 늘어나서 제한을 둬야한다.
- 4개의 bit를 다 써도 숫자를 표현하는데 있어 부족한데 맨 왼쪽 bit를 레지스터인지 숫자인지 구분하는 용도로 쓰는 바람에 숫자를 표현하는게 더 좁아지게 되버린다.
- 이러한 문제를 해결 하는 방법이 있다.
RISC VS CISC
CISC
- CISC 는 "Complex Instruction Set Computer" 의 약자로 복잡한 명령어 체계를 가지는 컴퓨터를 의미한다.
- CISC는 명령어 수가 많고 그 크기가 일정치 않기 때문에 CPU 가 복잡해질 수 밖에 없다. 이러한 복잡한 구조는 성능 향상에 제한이 따른다.
- 보다 높은 성능의 CPU를 디자인하기 위해서 보다 단순한 CPU 구조가 필요하다.
RISC
- CISC에서 복잡한 명령어를 간소화 해서 명령어를 단순화 해서 만든 CPU 구조
- 제한사항이 증가, 조합할 요소가 별로 없어서 프로그래밍 하기 힘들다는 단점이 있다.
명령어 구조는 단순해야 한다.
오늘날의 대부분은 RISC 구조다
CPU의 성능을 향상시키는데 있어서 CISC는 제한적이다 반면 RISC는 고성능의 CPU에 적합하다.
명령어 하나가 처리되는 과정은 아래와 같다.
- Fetch
- Decode
- Execution
각각의 단계에서 한클럭씩 소모된다고 해보자.
그러면 하나의 명령어를 실행하는데에 있어서 필요한 클럭은 총 3클럭이다.
그러면 명령어 5개를 실행한다 가정해보자 그러면 총 15클럭이 필요하다.
RISC는 명령어 구조가 단순해서 위 3단계를 거치면서 모든 명령어를 동시에 처리 할 수 있게 디자인 되어 있다.(처리하는데 7클럭 필요)
RISC 구조의 장점
- Decode 할때 다른 명령어를 Fetch 할 수 있고 Execution 할때 다른 명령어를 Decode를 할 수 있다.
- 그래서 n개의 명령어를 처리하는데에 있어 필요한 클럭수는 n+2다.
- 7클럭을 가지고 5개의 명령어를 처리할 수 있다.
- CISC보다 적은 클럭을 가지고 똑같은 일을 할 수 있어 고성능 컴퓨터에서는 RISC 구조로 간다.
- 클럭이 높다고 좋은게 아니다. 한 클럭당 얼마나 효율적으로 일을 처리하는지도 중요하다.
이제 연산자에 대해 정의 해보자 우선 덧셈 , 뺄셈 , 곱셈 , 그리고 나눗셈을 먼저 정의하자.
더불어 어셈블리 프로그래밍을 할 때 사용되는 심볼(Symbol) 도 결정을 하자.
연산의 의미 | 심볼 | 2진 코드 |
덧셈 | ADD | 001 |
뺄셈 | SUB | 010 |
곱셈 | MUL | 011 |
나눗셈 | DIV | 100 |
각각의 레지스터를 나타내는 2진 코드는 다음과 같이 구성해보자
레지스터 심볼 | 2진 코드 |
r0 | 000 |
r1 | 010 |
r2 | 010 |
r3 | 011 |
r4 , ir | 100 |
r5 , sp | 101 |
r6 , lr | 110 |
r7 ,pc | 111 |
r2 는 연산 결과를 저장한다. 따라서
- r2 = r1 + r3 레지스터 r1 과 r3에 있는 값을 더해서 r2에 저장하라
- r2 = r1 + 4 레지스터 r1 과 숫자 4를 더해서 r2에 저장하라
그런데 저장된 데이터가 숫자를 표현하는 것인지, 레지스터 정보를 표현하는 것인지 구분할 수 있어야 한다.
예를 들어, 0001 이 들어 있다면 이것이 레지스터 r1을 의미하는지, 숫자 1을 의미하는지 알수가 없다.
이 문제를 해결하기 위해 또 하나의 비트를 희생해야 한다.
네 개의 비트 중 첫 번째 비트가 1이면 레지스터 정보를 담고 있는 것이고 , 0이면 숫자 정보를 담은 것으로 약속하자
LOAD & STORE 명령어 디자인
연산 결과를 메모리에 넣으면 좋을 텐대 왜 안될까?
- 모든 CPU의 데이터는 레지스터를 통해서 빠져나가기 때문에 일단은 레지스터는 임시 저장소다.
- 데이터가 CPU로 들어오거나 나가거나 임시로 레지스터에 저장한다.
굳이 레지스터를 명시해야 하나? 어차피 레지스터를 통해서 나갈꺼면은 그냥 레지스터를 통해서 나간다고 치고 명령어에는 실제 최종 목적지를 담으면 더 좋지 않겠냐?
- CISC일 경우는 가능하다
- 하지만 CISC일 경우 명령어가 많아지고 그만큼 조합이 다양해지고 복잡해지게 되서 성능 향상에 있어서 제한적이다.
피연산자에는 레지스터와 숫자 정보 밖에 올 수 없다
- 피연산자1은 메모리 0x20에 피연산자2는 메모리 0x30에 그리고 각 메모리에 있는 데이터를 저장해서 저장소(r2)에 저장한다 해보자
- 그러면 "0x10번지(a)에 저장된 값과 , 0x20 번지 (b)에 저장된 값을 더해서 0x30 번지(c)에 저장해라
- 하지만 메인 메모리 주소 정보를 사칙 연산의 피연산자로 올 수 있도록 명령어 구조를 설계하지 않았다. (RISC (Reduced Instruction Set Computer) 방식 )
- 왜냐면 메모리 주소가 오게 할 수 있지만 명령어 구조가 복잡해지고 이에 따라 하드웨어 구성도 복잡해지기 때문에 성능이 저하 될 수 있다.
- CISC (Complex Instriction Set Computer) 방식이면 가능하다.
- 따라서 메인 메모리에 저장된 데이터를 레지스터로 일단 옮겨다 놓은 다음에 덧셈을 진행해야 한다.
- 레지스터와 메인 메모리 사이에서 데이터를 전송할 수 있는 명령어가 필요하다.
LOAD & STORE 명령어의 필요성
명령어의 제한
명령어를 디자인 하는 과정에서 사칙 연산 결과를 레지스터에만 저장할 수 있게 제약 사항을 뒀다.
즉, 피연산자로 올 수 있는 것은 숫자와 레지스터이다.
모든 연산은 레지스터를 통해서 진행하도록 명령어를 디자인해서 CPU는 레지스터를 통해서 연산을 처리해야 한다.
CPU가 레지스터를 통해서 연산이 처리 가능하기에 메인 메모리에 저장된 데이터를 레지스터로 이동시키기 위한 LOAD 명령어와, 레지스터에 저장된 데이터를 메인 메모리로 이동시키기 위한 위한 STORE 명령어가 필요하다.
LOAD와 STORE
LOAD & STORE 명령어의 디자인
메인 메모리에 존재하는 데이터를 레지스터로 이동시키는 명령어에 대해 LOAD라는 심볼을 붙여주고, 다음고 같이 디자인하자.
LOAD r1 , 0x10 // 0x10 번지에 존재하는 데이터를 레지스터 r1에 저장하라
LOAD r2 , 0x20 //0x20 번지에 존재하는 데이터를 레지스터 r2에 저장하라
ADD r3 , r1 ,r2 // r1, r2에 저장된 값을 더해서 r3에 결과를 저장해라
STORE r3, 0x30 // r3에 저장된 값을 0x30번지에 저장해라
덧셈 연산의 피연산자는 숫자이거나 레지스터가 되어야 한다고 정의했기 때문에 우리가 지금 골치를 썩고 있는 것이다.
그렇다면 무엇이 해결책이겠는가?
피연산자가 레지스터가 되게끔 하기 위해서 메모리에 저장된 값을 레지스터에 옮겨다 놓은 후 덧셈 연산을 하면 된다.
Direct 모드와 Indirect 모드
명령어를 디자인할때 피연산자를 표현할 수 있는 비트 수를 제한을 뒀다.
피연산자의 크기로는 4비트가 올 수 있다. 심지어 한 비트는 다른 용도로 사용중이다.
그래서 우리가 표현하고자 하는것을 충분히 표현을 못한다.
그래서 표현하는것의 문제점을 해결하기 위해 Direct 모드와 Indirect 모드를 제공한다.
어드레싱
- 특정 메모리를 참조한다는 뜻이다.
- 메모리를 참조하는데에 있어서 Direct 모드가 있고 Indirect 모드로 참조할 수 있다.
Direct 모드의 문제점과 Indirect 모드의 제안
하나의 명령어에 여러 정보를 담다 보니 표현하는 데이터 크기에 제한이 따른다는 문제점이 등장하였다. 지금까지 우리가 언급한 메모리 접근방법을 가리켜 Direct 모드라 하는데, 이 방법을 통해서는 메모리의 모든 영역에 대한 접근이 불가능하다. 왜냐하면 Direct 모드로 메모리를 접근할 경우, 할당 된 비트 수 안에서 표현 가능한 범위의 메모리 영역만 접근이 가능하기 때문이다.
주소값을 명령어에 직접 표현하기 때문에 Direct 모드라 한다. 여기서 우리는 이러한 Direct 모드가 지니고 있는 단점을 해결하기 위해서 Indirect 모드라는 것을 등장시키기로 결정하겠다.
Direct 모드의 문제점
Indirect 모드의 이해
앞에서의 LOAD 명령어에서는 레지스터에 저장할 데이터가 존재하는 주소를 직접적으로 표현하였는데, 반대로 Indirect 모드라 한다면 명령어에서 지정하는 번지에 저장된 값을 주소값으로 참조하게 된다.
Direct 모드
- LOAD, r1, 0x10
- 메모리의 주소값을 직접 지정해서 값을 가져오는 모드
Indirect 모드
- LOAD r1, [0x10]
- 0x10 번지에 있는 값을 가져와서 그 값을 메모리로 참조하라는 의미
- 즉 0x10 번지에 갔는데 16진수로 0x30이 있다 이것을 메모리로 참조해서 0x30번지로 다시 가서 거기에 있는 값을 LOAD 한다.
- 한다리 건너가는 방식이다.
LOAD r1, [0x10]
- 0x10 번지로 감
- 0x10번지에 있는 값이 0x30이면 0x30번지로 가기
- 0x30 번지에 있는 값을 r1으로 LOAD하기
- r1에 10 값을 넣음
- 4와4를 곱해서 r0넣는다 r2도 마찬가지
- r0과 r2에 있는 값을 곱해서 r3에 넣는다. 곱한값은 0x100이다
- r3에 있는 값(0x0100)을 0x0030에 STORE한다.
- 그리고 [0x0030] 이여서 우선 0x0030으로 간다.
- 0x0030에는 0x0100이 들어 있다.
- 0x0100으로 간다.
- 거기에 있는 값을 r2로 LOAD한다.
- r1, r2에 있는 값을 더해서 r3에 저장한다.
'운영체제 > 뇌를 자극하는 윈도우즈 시스템 프로그래밍' 카테고리의 다른 글
뇌를 자극하는 윈도우즈 시스템 프로그래밍 6장 (0) | 2024.09.05 |
---|---|
뇌를 자극하는 윈도우즈 시스템 프로그래밍 5장 (0) | 2024.05.04 |
뇌를 자극하는 윈도우즈 시스템 프로그래밍 3장 (0) | 2024.05.03 |
뇌를 자극하는 윈도우즈 시스템 프로그래밍 2장 (0) | 2024.05.03 |
뇌를 자극하는 윈도우즈 시스템 프로그래밍 1장 (0) | 2024.05.02 |