티스토리 뷰
* CHAPETER 2 - 어셈블러 학습과 Makefile 입문
#1 레지스터 내용 정리
*16비트 레지스터 (= 기억회로)
CPU에는 레지스터라는 기억회로가 있는데, 이것은 기계어의 변수이다. 변수라는 건 데이터를 담는 공간, 그릇이라는 의미로 이해하면 된다. 대표적인 레지스터로는 다음의 8개가 있는데, 이들은 고유의 이름과 기능을 가지고 있다.
- AX - 어큐물레이터(accumulator: 누적 연산기라는 의미) // X의 의미는 확장(extend)의 의미다.
- CX - 카운터(counter: 수를 세는 기계라는 의미)
- DX - 데이터(data: 데이터라는 의미)
- BX - 베이스(base: 기초, 기점이라는 의미)
- SP - 스택 포인터(stack pointer: 스택용 포인터)
- BP - 베이스 포인터(base pointer: 베이스용 포인터)
- SI - 소스 인덱스(source index: 읽기 인덱스)
- DI - 데스티네이션 인덱스(destination index: 쓰기 인덱스)
/* 레지스터의 이름이 알파벳 순이 아닌 이유는 기계어에 따라 레지스터 번호순으로 나열했기 때문이다. */
이것들은 모두 16비트(= 8바이트) 레지스터로 16자리의 2진수를 기억할 수 있다. 읽을 때는 '에이엑스 레지스터' 등과 같이 알파벳 그대로 읽는다. 고유의 기능을 가지고 있다고 위에서 서술했는데, 예를 들면 이렇다. AX는 연산을 할 때 사용되는데 각종 연산에 AX 레지스터를 사용하면 프로그램이 한 층 더 간결해진다. 아래 예시를 보자.
ADD CX, 0x1234 (16진수 1234, 10진수로는 4660)는 기계어로 81 C1 34 12 라는 4바이트의 명령이지만,
ADD AX, 0x1234는 기계어로 05 34 12 라는 3바이트의 명령이 된다. (기계어가 한 바이트 더 줄었다.)
/* ADD CX, 0x1234는 CX 레지스터에 0x1234 를 더해라 라는 의미다. 즉, CX += 0x1234 로 해석할 수 있다. */
이 예에서 보다시피, 프로그램이 간결해진다는 건 여기서 기계어가 좀 더 짧아진다는 의미다. 어셈블러 명령어의 단어 수에는 전혀 차이가 없다. 이처럼 각각의 레지스터에는 고유의 기능이 있는데, CX의 경우 횟수를 세는 데 사용하면 편리하게 되어 있고 BX는 메모리의 번지 계산의 기준 주소로 사용하면 편리하게 되어 있다.
레지스터 이름 뒤에 보면, X와 P가 붙어 있는데 X의 경우는 확장(extend)의 의미로 쓰이고 있다. 예전에는 CPU의 레지스터가 8비트 였는데, 그 때 16비트가 등장하면서 기존의 레지스터 보다 확장됐다는 의미로 저렇게 이름이 붙여졌다. P의 경우는pointer (주소를 가리킨다)를 의미한다.
한편, CPU에는 8비트의 레지스터도 8개가 존재한다. 이름이 위의 16비트 레지스터와 닮아보이는 건 그만한 이유가 있다.
- AL - 어큐물레이터 로우 (low: 낮다의 의미)
- CL - 카운터 로우
- DL - 데이터 로우
- BL - 베이스 로우
- AH - 어큐물레이터 하이 (high: 높다의 의미)
- CH - 카운터 하이
- DH - 데이터 하이
- BH - 베이스 하이
AX 레지스터의 16비트 중 아래쪽 0 ~ 7비트 (0부터 센다)의 8개비트를 AL라고 한다. 또한 8 ~ 15비트 까지는 AH라고 한다. 단지 AX, CX ... 를 두 개의 이름으로 나누어 둔 것이므로 추가적인 저장공간을 확보했다고 해석하면 안된다. 여전히 16바이트밖에 기억할 수 없다.
그렇다면, 32비트씩 처리되는 레지스터는 어떻게 표현이 될까? 매우 간단한데, 저 위의 레지스터들의 이름 앞에다가 'E' 만 붙여주면 된다. E의 의미는 여전히 확장(extend) 이다. 16비트 시대 입장에서 32비트는 또 다른 확장이니까.
구조를 보자면 EAX는 32비트(=4바이트) 레지스터지만, AX와 일부 공통으로 되어 있고, 32비트 중 하위 16비트 (0~15비트)가 AX 그 자체다. 상위 16비트 (16~31비트) 는 이름도 레지스터 번호도 없다. 즉, EAX를 16비트 2개로 나누어 사용하는 것은 가능하지만, 간단하게 사용할 수 있는 것은 하위 비트(0~15, AX) 뿐이고, 상위 비트를 꼭 사용하고 싶다면 16비트만큼 밀어내는 명령을 사용하여 상위 16비트를 하위로 내려야만 한다. 마찬가지로 32비트의 CPU는 32바이트만 기억할 수 있다. (4바이트(32비트) * 8개 = 32바이트)
32비트 레지스터를 설명할 때, 세그먼트 레지스터 또한 설명이 필요하다. 위의 16비트의 경우 8비트의 레지스터가 존재했듯이 32비트의 경우 16비트 레지스터 또한 존재한다.
- ES - 엑스트라 세그먼트 (extra segment: 덤 세그먼트)
- CS - 코드 세그먼트 (code segment)
- SS - 스택 세그먼트 (stack segment)
- DS - 데이터 세그먼트
- FS - 명칭 없음 (덤 세그먼트 2번째)
- GS - 명칭 없음 (덤 세금너트 3번째)
#2 어셈블러 해석, CPU와 메모리 내용 정리
MOV SI, msg 의 의미를 살펴 보면 MOV는 대입의 의미이므로 SI = msg; 라는 의미이다. 여기서 msg는 레이블이라 부르는데 그 정체는 주소다.
JMP entry라는 명령 (entry로 점프하라, entry로 가라)을 보며 의미를 더 명확히 해보자. entry는 프로그램이 시작되는 부분을 의미하는 레이블로, 그 값을 0x7c50 이라고 가정해 보자. 그럴 때 JMP entry 라는 문장을 JMP 0x7c50 라고 해석해도 전혀 문제 될 게 없다. 어셈블러에서 레이블은 단지 주소를 의미하는 숫자에 불과하다. 어떤 숫자가 되는지는 ORG 명령에 의해 번지 값을 계산한 후, 그 값을 레이블 값으로 정해주는 것이다.
/* ORG는 프로그램이 실행 시에 메모리 내 몇 번지에 로딩되는지를 알려주는 명령이다. origin의 의미다. */
그래서 만약 MOV AX, entry 라고 쓰면 AX에는 0x7c50이 대입되는 것이다. entry 이후에 프로그램의 모든 실행 소스코드가 AX에 저장된다고 해석하는 건 곤란하다. 단지 프로그램의 시작 주소값을 저장하는 것에 불과하다.
다음은 MOV AL, [SI]을 보자. [ ]의 의미는 '메모리'를 의미한다. 메모리란 기억 소자로 만들어진 대규모 아파트 단지 같은 것인데, 꽤나 규칙적으로 기억 소자들이 나열되어 있다. 위에서 레지스터를 설명하면서 CPU가 얼마나 변변 찮은 기억공간을 가지고 있는지를 설명했는데, 이를 위해 메모리를 따로 준비해 줄 필요가 있는 것이다.
MOV 명령은 전송이 도착하는 쪽이나 전송을 주는 쪽에 레지스터나 정수 뿐만 아니라 메모리를 지정하는 것도 가능하다. 그 때 [ ] 기호를 사용한다.
메모리는 CPU 외부에 존재한다. 즉, 외부기억 장치라고 할 수 있다. CPU는 자신의 단자 일부를 이용해 메모리에게 '메모리야, 5678 번지에 있는 데이터를 나에게 보내주라.' 라는 전기 신호를 보낸다. (정확히는 칩셋이라는 제어 소자가 들어간다.) CPU가 메모리의 데이터(내용)를 읽거나 쓸 때에는 이렇게 주고받는 동작을 반복하는 것이다.
메모리와 교신하는 것은 데이터를 주고받을 때뿐만이 아니다. 프로그램 자체도 메모리의 어딘가에 반드시 들어가 있어야 한다. 일반적으로 프로그램이라 하는 것은 상당히 커서, 전부 합쳐봐야 44바이트밖에 안 되는 레지스터에 들어갈 수 없다. 따라서 여기에는 반드시 메모리에 둔다는 규칙이 존재한다. 이는 CPU가 기계어 (프로그램을 구성하고 있는)를 실행할 때는 반드시 메모리로부터 프로그램을 1명령씩 읽어 들여 차례로 실행하기 때문이다.
메모리는 CPU와 꽤 떨어진 곳에 위치하고 있다. CPU의 반도체 안 쪽과 비교한다면 멀다 (10cm라고 가정)고 표현할 수 있다. 그래서 데이터를 주고 받을 때 응답시간이 꽤 걸릴 수밖에 없다. (CPU는 매우 고속으로 동작하므로, 10cm라는 짧은 거리의 전기 신호가 전해지는 속도조차 문제가 된다.) 그리고 메모리는 레지스터보다 엄청나게 많은 것을 기억할 수 있지만 메모리를 이용하면 레지스터보다 몇 배나 느리다.
위에서 설명한 MOV의 사용법에 추가로 BYTE, WORD, DWORD 라는 영어도 사용한다. 예를 들면 아래와 같다.
MOV BYTE [678], 123 (여기서 숫자는 컴퓨터 입장에서 2진수(010101 ... 등)로 받아들여 진다. ON과 OFF의 나열이다.)
해석을 하자면 이렇다. 일단 전기 신호가 들어오면 잠깐 회로의 어딘가(678번지)에 8개의 기억 소자 (소자 당 1bit)가 반응할 수 있는 회로(678번지)에 123을 ON과 OFF로 표시한 전기 신호를 기억하는 것이다. 왜 8개의 기억소자냐 하면, BYTE로 지정했기 때문이다.
MOV WORD [678], 123 (WORD는 2개의 번지를 이용할 수 있다. (번지 당 1바이트 = 8bit))
"데이터의 크기[번지]"
PC에는 BIOS 라는 프로그래밍이 있는데, PC의 기판 상에 있는 ROM (read only memory, 전원을 차단해도 내용이 없어지지 않는 읽기 전용 메모리) 이라는 소자에 들어 있다. BIOS는 OS 제작자가 자주 사용할 것 같은 프로그램을 PC 제조사가 미리 준비해 둔 매우 고마운 존재다.
/************************************************* ** helloos.nas source code (교재의 소스 코드 일부) *************************************************/ entry: MOV AX, 0 ; initialize register MOV SS,AX ; SS (stack segment) = AX (accumulator) MOV SP,0x7c00 ; SP (stack pointer) = 0x7c00 MOV DS,AX ; DS (data segment) = AX (accumulator) MOV ES,AX ; ES (extra segment) = AX (accmulator) MOV SI,msg ; msg means label putloop: MOV AL,[SI] ADD SI, 1 ; SI + 1 CMP AL,0 ; AL = character code JE fin ; fin label means final MOV AH, 0x0e ; one char expression possible MOV BX, 15 ; color code INT 0x10 ; call video BIOS JMP putloop fin: HLT ; stop cpu JMP fin ; Endless Loop /************************************************* ** End Line *************************************************/
- AH = 0x0e;
- AL = 캐릭터 코드;
- BH = 컬러 코드;
- 리턴 값: 없음
- 주: 힙, 백스페이스, CR (carriage retturn, 개행에 사용된다.), LF (line feed, 개행에 사용된다.)는 제어코드로 인식된다.
/* 관련 설명은 현재 OS 교재 55p에 있는 helloos.nas 파일의 코드를 바탕으로 하고 있습니다. (위의 코드 첨부) */
/************************************************* ** helloos.nas source code (교재의 소스 코드 일부) *************************************************/ entry: AX = 0; SS = AX; SP = 0x7c00; DS = AX; ES = AX; SI = msg; putloop: AL = BYTE [SI]; SI = SI + 1; if (AL == 0) { goto fin; } AH = 0x0e; BX = 15; INT 0x10; goto putloop; fin: HLT goto fin; /************************************************* ** End Line *************************************************/
- 0x00000000 ~ 0x0009FFFF : RAM
- 0x000A0000 ~ 0x000BFFFF : 비디오 카드 접근 영역
- 0x000C0000 ~ 0x000C7FFF : 비디오 BIOS
- 0x000C8000 ~ 0x000DFFFF : 각종 카드의 ROM 영역
/* 0X000D0000 ~ 0x000DFFFF 영역은 대부분 비어 있다. */ - 0x000E0000 ~ 0x000FFFFF : 확장 BIOS
- 0x000F0000 ~ 0x000FFFFF : BIOS
- 0x00100000 ~ 0x00EFFFFF : RAM
- 0x00F00000 ~ 0x00FFFFFF : RAM 혹은 홀 (BIOS의 설정에 의해서 변경될 수 있다.)
/* 286의 경우는 0x00FFFFF0 로부터의 16바이트에 리셋트 점프 명령이 있을 수도 있다. */ - 0x01000000 ~ 메모리의 끝 : RAM
- 메모리의 끝 ~ 0xFFFFFFEF : PCI 장치 등의 메모릴 맵핑 I/O에 이용 가능한 영역
- 0xFFFFFFF0 ~ 0xFFFFFFFF : 386이후에서는 여기에 리셋트 점프 명령이 있다.
- 0x00000000 ~ 0x000003FF : 리얼모드용 인터럽트 벡터
/* 물론 IDT를 변경하면 변경할 수 있지만, 기본적으로는 이 주소가 사용된다. */ - 0x00000300 ~ 0x000003FF : BIOS용 스택
- 0x00000400 ~ 0x000004FF : BIOS가 사용하는 영역
- 0x00007C00 ~ 0x00007DFF : 부트섹터가 로딩되는 주소
- 0x0009FC00 ~ 0x0009FFFF : ACPI 영역
#5 실습 시 문제가 발생할 수 있는 부분 (69p 부트섹터 ~ 72p Makefile)
실습을 하다 보면, 뭔가 꼬일 때가 있다. 아마도 책이 그렇게 완벽하지 않다는 것의 반증일 것이다. 이번 장에서의 실습을 성공적으로 하기 위해서는 69p의 3번 <부트섹터만 정리> 파트를 진행하지 않고 4번 <이후를 위한 Makefile 도입> 부분부터 실행해야 한다.
69p 하단부 부터 Makefile 설명이 시작된다. Makefile은 SciTE 편집기를 통해 정확하게 작성해야하는데, 이 부분이 상당히 헷갈린다. 아래의 내용과 같이 작성해 보면, 다들 문제 없이 실행이 가능할 것이다. 소스 코드이며 아래 사진까지 첨부해놨다.
/************************************************* ** Makefile 내용 *************************************************/ // 여기서 중간 중간 공백은 space 키를 누름으로써 작성하시면 됩니다. /* 엔터키로 개행을 하신 후에 탭 키를 누르셔서 저렇게 줄 간격을 맞춰줍니다. */ ipl.bin : ipl.nas Makefile ../z_tools/nask.exe ipl.nas ipl.bin ipl.lst helloos.img : ipl.bin Makefile ..\z_tools\edimg.exe imgin:../z_tools/fdimg0at.tek \ wbinimg src:ipl.bin len:512 from:0 to:0 imgout:helloos.img /************************************************* ** End Line *************************************************/
이후의 실습 또한 문제 없이 진행할 수 있을 것이다. 파일의 내용을 조금 다르게하는 것으로, 공백이나 개행을 정확하게 하지 않았을 경우 매우 곤란해질 수 있다는 걸 깨달은 오늘이다.
'Develop Story > OS' 카테고리의 다른 글
<OS 구조와 원리>: OS개발 30일 프로젝트 #5 (0) | 2017.08.09 |
---|---|
<OS 구조와 원리>: OS개발 30일 프로젝트 #4 (0) | 2017.07.21 |
<OS 구조와 원리> : OS개발 30일 프로젝트 #3 (0) | 2017.07.02 |
<OS 구조와 원리> : OS개발 30일 프로젝트 #1 (0) | 2017.06.29 |
<OS 구조와 원리> : OS개발 30일 프로젝트 #0 (0) | 2017.06.29 |
- Total
- Today
- Yesterday