개발자 노트

OS 개발 코드 분석 본문

▶ INBOX ◀

OS 개발 코드 분석

heeyam 2016. 9. 27. 10:44
반응형




DB 명령은 'data byte'의 약어로, 파일의 내용을 1바이트만 직접 쓰는 명령이다.


DW 명령은 'data word'의 약어로, 파일의 내용을 16비트 즉 2바이트만 직접 쓰는 명령이다.


DD 명령은 'data double-word'의 약어로, 파일의 내용을 32비트 즉 4바이트만 직접 쓰는 명령이다.


RESB 명령은 'reserve byte'의 약어로, 예약의 의미로 지정한 바이트 만큼을 비어둘 때 사용하는 명령이다.

nask라는 어셈블러에서는 지정한 바이트 만큼을 비어두는 것에 그치지 않고 0x00 으로 채워진다.

예) RESB 16 은 16바이트의 공간을 예약해둔다는 의미이다. nask에서는 16바이트의 공간을 0x00으로 채운다.


; 명령은 코멘트 명령이다.



< mov 명령과 레지스터 >

MOV SS, AX 는 SS = AX; 라는 대입문이다. 해당 명령을 실행해도 AX 는 비워지지 않는다. 즉, SS에 AX의 값을 복사하는 것과 같다.


CPU에는 레지스터라는 기억 회로가 있다. 이 것은 기계어의 변수와도 같다. 

대표적인 레지스터는 다음의 8개이다.


AX - accumulator: 누적 연산기라는 의미

CX - counter: 수를 세는 기계라는 의미

DX - data: 데이터라는 의미

BX - base: 기초, 기점이라는 의미

SP - stack pointer: 스택용 포인터

BP - base pointer: 베이스용 포인터

SI - source index: 읽기 인덱스

DI - destination index: 쓰기 인덱스

IP - instruction pointer: 명령 포인터


이것들은 모두 16비트 레지스터로 16자리의 2진수를 기억할 수 있다.

8개의 레지스터 중 어느 것을 사용해도 대략 같은 계산이 가능하지만, 각종 연산에 AX를 사용하면 프로그램을 '기계어로 쓸 때' 간결하게 쓸 수 있다.

예를 들어

ADD CX, 0x1234 는 81 c1 34 12 라는 4바이트의 명령이지만,

ADD AX, 0x1234 는 05 34 12 라는 3바이트의 명령이 된다.


AX, CX, DX, BX 라는 이름에서 X는 확장(extend)를 의미한다. 이는 8비트 레지스터에서 16비트 레지스터로 확장됐음을 말한다.


IP는 다음에 실행할 명령이 메모리의 어느 번지인지를 CPU가 기억해 두기 위한 레지스터이다. 이 레지스터가 없으면 CPU는 어느 프로그램을 실행하고 있는지 모르게 되어 프로그램을 계속해서 실행할 수 없다. 명령을 실행할 때마다 이 IP 레지스터는 자동적으로 더해져서, 다음에 실행할 명령의 번지를 항상 기억하고 있다. CS 세그먼트 레지스터와 한 쌍이 되어 실행 번지가 만들어진다.


8비트 레지스터도 8개 있다.

AL - accumulator low

CL - counter low

DL - data low

BL - base low

AH - accumulator high

CH - counter high

DH - data high

BH - base high


AX 레지스터의 16비트 중 아래쪽 비트 0부터 7의 8비트 부분을 AL이라고 하며, 위쪽의 비트 8부터 15비트까지의 남은 8비트 부분을 AH라고 한다.

예) AX 레지스터에 0xa1d3 을 넣을 때 AH와 AL의 값은 다음과 같이 된다.

(그림) AH: 0xa1 AL: 0xd3


BP, SP, SI, DI 는 위 처럼 L과 H로 나눌 수 없다. 나눈 결과가 필요할 때는 예로 MOV AX, SI 등을 한 후에 AL과 AH를 사용하는 방식을 이용해야한다.


32비트 레지스터

EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI, EIP

이것은 16비트 레지스터에서 32비트 레지스터로 확장됐음을 의미하며 16비트 레지스터명 앞에 E를 붙였다. 이 E역시도 확장(expend)를 의미한다.

EAX를 예로 설명하자면 AX와 공통으로 되어 있고, 32비트 중 하위 16비트가 AX 그 자체이다. 상위 16비트는 이름이 없고, 레지스터 번호도 없다. 즉, EAX를 16비트 2개로 나누어 사용하는 것은 가능하지만, 간단하게 사용할 수 있는 것은 하위 비트뿐이고 상위 비트를 사용하고 싶으면 16비트 만큼 밀어내는 명령을 사용하여 상위 16비트를 하위로 내려서 사용해야한다.


<세그먼트 레지스터>


세그먼트 레지스터는 16비트의 크기를 가진다.

ES - extra segment: 덤 세그먼트

CS - code segment

SS - stack segment

DS - data segment

FS - 명칭 없음(덤 세그먼트 2번째)

GS - 명칭 없음(덤 세그먼트 3번째)


<EFLAGS 레지스터>

EFLAGS는 FLAGS라는 16비트의 레지스터가 확장된 32비트 레지스터이다. FLAGS는 캐리 플래그나 인터럽트 플래그 등으로 구성된 레지스터이다. 캐리 플래그는 JC라든가 JNC 라는 점프 명령으로 0인지, 1인지 알아볼 수 있지만, 인터럽트 플래그는 JI라든가 JNI 같은 명령이 없고, 알아보는 방법은 EFLAGS를 읽어 들여 9번째 비트가 0인지, 1인지를 체크해야한다.


(그림)

공백 비트는 특별히 의미 없음


< MOV AX, SI 와 MOV AL, [SI]의 차이 >

MOV AL, [SI]에서 [ ]  기호는 '메모리'를 의미한다. 메모리란 기억장치를 뜻한다.

우리가 원하는 작업을 컴퓨터에서 처리하기 위해서는, 처리대상의 내용이나 또는 처리 결과를 잠시 기억하고 있어야하는데, CPU는 레지스터만으로는 정보를 처리하는데 필요한 데이터를 충분히 기억할 수 없다.

그래서 필요한 것이 메모리이다. 메모리는 CPU의 외부에 존재한다. 

CPU가 메모리와 교신하는 것은 데이터를 주고받을 때뿐만이 아니다. 원래 프로그램 자체도 메모리의 어딘가에 반드시 들어가 있어야 한다. 일반적으로 프로그램이라는 것은 상당히 커서, 레지스터에 들어갈 수 없는데도 불구하고, 반드시 메모리에 둔다는 규칙이 정해져 있다. 이는 CPU가 기계어를 실행할 때에는 반드시 메모리로부터 프로그램을 1명령씩 읽어 들여 차례로 실행하기 때문이다.


메모리는 CPU의 반도체 안쪽보다 멀리 떨어져 있다. 때문에, CPU가 메모리에게 데이터를 알려달라든가, 이 데이터를 내 대신 기억해달라고 말하고 나서 메모리가 무사히 그 요구에 응답할 때까지 꽤 시간이 걸린다. 그래서 메모리는 레지스터보다 엄청나게 많은 것을 기억할 수 있지만 레지스터를 사용할 때보다 몇 배나 느리다. 


다시 어셈블리로 돌아가서, MOV 명령은 전송이 도착하는 쪽이나 전송을 주는 쪽에 레지스터나 정수뿐만 아니라 메모리를 지정하는 것도 가능하다. 그리고 메모리를 지정할 때 [ ] 라는 기호를 사용한다. 또한 BYTE, WORD, DWORD 라는 영어도 사용한다.

예) MOV BYTE [678], 123

위의 예는 메모리 단지의 678번지에 123을 기억시키는 명령이다. 이 때 BYTE 로 지정했기 때문에 678번지에 있는 8비트의 기억소자가 반응한다.

예) MOV WORD [678], 123

위의 예는 메모리 단지의 678번지와 그 옆의 679번지의 기억 소자 총 16비트가 반응한다. 이 경우 123이라는 것은 16비트의 수치로 해석되어, 즉 00000000 01111011 라고 해석되어, 먼저 아래의 01111011이 678번지에, 그리고 위의 00000000이 옆의 679번지에 보내진다.

(그림)


이처럼 어셈블러에서 메모리를 지정할 때에는,

"데이터의 크기[번지]"

로 써야한다.


메모리 번지를 지정할 때에는 위와 같이 정수를 지정하는 방법 외에도 레지스터를 사용하는 방법도 가능하다.

예) BYTE[SI] 또는 WORD[BX] 등.

이 때 주의 할 것은, 번지 지정에 사용될 수 있는 레지스터는 BX, BP, SI, DI 뿐이다. AX, CX, DX, SP 를 사용할 수 없는 이유는 그런 처리를 나타내는 기계어가 없기 때문이다.

그래서 DX번지의 메모리 내용을 AL에 대입하기 위해서 다음과 같은 방법을 쓸 수 있다.

MOV BX, DX

MOV AL, BYTE [BX]


여기까지 설명이 되었다면 '메모리의 SI번지의 1바이트의 내용을 AL로 읽어라'는 명령이 다음과 같음을 알 수 있다.

MOV AL, BYTE [SI]


그러나 MOV 명령에서는 비트 수가 같은 것끼리만 대입할 수 있는 규칙이 있어서, AL에 대입하는 이상 BYTE 외에는 있을 수 없으므로 이것을 생략하여 다음과 같이 쓸 수도 있다.

MOV AL, [SI]


* MOV 명령에서는 비트 수가 같은 것끼리만 대입할 수 있는 규칙 때문에 예를 들어 MOV AX, CL 이라고 쓰면 어셈블러는 대응하는 기계어를 찾을 수 없어서 번역 에러가 난다.




ORG 0x7C00

ORG 명령: 해당 프로그램이 실행될 때 메모리 내 어느 번지에 로딩되는지를 nask에 가르쳐 주기 위한 명령이다.

왜 0x7C00 인가?: 우리는 메모리 영역 전체를 마음대로 쓸 수 없다. 예를 들면 메모리의 0번지, 즉 제일 처음 부분은 BIOS가 여러 가지 용도로 사용하고 있어서 이를 마음대로 사용하면 BIOS는 오동작을 일으키게 되고, 우리가 만든 프로그램 또한 오동작을 하게 된다. 메모리의 0xf0000 번지 부근에는 BIOS 자체가 들어 있어서 이곳도 사용할 수 없다. 그 외에도 사용하면 안 되는 메모리 영역이 여기저기 있기 때문에, 이러한 것을 신경 써줘야한다.


0x7C00을 쓰는 이유는 IBM 사가  메모리의 0x00007c00 ~ 0x00007dff 번지를 부트섹터가 읽혀지는 어드레스로 지정했기 때문이다. 따라서 부트섹터를 만들 때는 ORG 0x7C00 을 해줘야한다.


여기서 잠깐!

ORG는 기계어에 대응되는 명령이 아니다.



< 부트로더 >

부트로더란 

부트로더에 대한 개념정리

– 구글 위키에서 부트로더의 정의

부트로더(boot loader, 문화어: 초기적재프로그램)란 운영 체제가 시동되기 이전에 미리 실행되면서 커널이 올바르게 시동되기 위해 필요한 모든 관련 작업을 마무리하고 최종적으로 운영 체제를 시동시키기 위한 목적을 가진 프로그램을 말한다.

 

‘부트로더’ 라는 단어의 사전적 정의입니다. 위 정의만 가지고는 잘 이해가 되지 않으시는 분들도 계실 텐데, 간단하게 설명하면 이렇습니다.

가장 먼저 컴퓨터가 켜지면 롬에 들어있는 BIOS가 로드됩니다. BIOS는 컴퓨터에 연결된 저장 매체에서 설정된 부팅 순서대로 부트로더들을 불러오게 됩니다. 이 때, 하드 디스크가 첫 번째 부팅 장치로 설정되어 있으면, BIOS는 하드 디스크의 부트로더를 로드하게 됩니다.

운영체제는 크게 세 부분으로 나뉩니다. 실제 명령어들이 구동되는 커널, 프로그램에 포함된 명령어들을 커널에 전해주는 프레임워크, 그리고 커널과 프레임워크 위에서 구동되는 UI가 있습니다.

일반적으로 ‘부팅’이라하면 커널을 로드하고, 프레임워크를 실행시킨뒤, UI를 실행하는, 운영체제를 시동하는 행위를 일컫는데요, 위의 정의에서 나타나는 것 같이 부트로더는 커널을 시동시키기 전 하드웨어들을 준비하는 역할을 합니다.

그렇다면 부트로더는 어디에 있을까요?

하드디스크의 파티션 정보는 MBR이라는 하드 디스크의 첫 섹터 속에 존재합니다. 여기서 BIOS는 파티션 테이블을 읽은 후 활성화 되어있는 파티션의 PBR을 읽어 그 데이터로 부팅하게 됩니다. 여기서 이 MBR과 PBR이 바로 1차 부트로더입니다.

여기서 잠깐, 제가 왜 ‘1차’ 부트로더라는 말을 썼을까요? 그렇다면 ‘2차’ 부트로더도 있다는 말인가요?

네, 있습니다. 바로 이렁게 생겼는데요, 여기서 GRLDR은 GRUB4DOS의 2차 부트로더입니다. 윈도우 비스타/7/8의 2차 부트로더는 BOOTMGR이고요, XP는 NTLDR입니다. 2차 부트로더는 파일의 형식으로 부팅 파티션의 루트경로에 존재합니다.

왜 2차 부트로더라는 것이 존재할까요? MBR/PBR의 최대 용량은 512바이트밖에 되지 않기 때문에 도스 같은 운영체제 정도에는 적합했지만 윈도우 같은 복잡한 운영체제의 커널을 로드할 준비를 하기에는 턱없이 모자랐습니다. 그래서 2차 부트로더를 1차 부트로더를 통해 커널을 로드하듯이 로드한뒤, 2차 부트로더를 통해 커널을 로드하는 방식을 택하게 된 것입니다.

따라서 현재 일반적으로 운영체제를 부팅시키는 대략적인 과정은 다음과 그림과 같습니다. 그렇다면 멀티부팅을 하게 되면 어떻게 될까요?

어떤 부트로더인지에 따라 조금씩 달라질 수도 있지만 우리가 사용하게될 GRUB4DOS의 멀티부팅 과정은 이렇습니다. 2차 부트로더가 로드된 후 어떤 운영체제를 부팅할 지 선택하는 화면을 띄워준 뒤, 해당 운영체제의 2차 부트로더를 로드하게 됩니다. 이러한 과정을 체인 로딩 (Chainloading) 이라고 합니다.

 

즉, 부트로더의 역할은 !

– 임베디드 보드의 전원을 켰을 때, 플래시 메모리(ROM)에서 시작되는 시스템 소프트웨어로서 하드웨어를 초기화 시켜 주고, 커널을 메모리(RAM)에 적재(복사)시켜서 사용자 명령 처리를 준비하는 역할을 한다.

즉, 부트로더에 의해서 임베디드 보드가 기동(부팅)된다.  부트로더는 직렬통신, 인터럽트, 타이머, 콘솔, 메모리, 각종 입출력장치 등을 점검하여 실행 가능 상태로 만들어 준다. 임베디드 보드의 하드웨어 구성과 부트로더는 서로 밀접한 관계이며, 임베디드 보드 제작사에 의해서 부트로더가 보통 제공이 된다.
일반적으로 임베디드 보드 제작사에서 제공하는 부트로더를 플래시 메모리에 그대로 적재시켜 사용해도 되지만, 임베디드 보드의 하드웨어 구성과 내부 동작방식을 이해하고 응용하고자 할 때는 부트로더 소스코드를 분석해야 한다. 특히, 새롭게 임베디드 보드를 제작하고자 한다면 부트로더 이해와 코딩은 필수적이다.

 

출처 – http://diginuri.tistory.com/7

http://www.embeddedworld.co.kr/atl/view.asp?a_id=4912

#참고

https://jinheeahn.wordpress.com/2015/02/13/%EB%B6%80%ED%8A%B8%EB%A1%9C%EB%8D%94%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B0%9C%EB%85%90%EC%A0%95%EB%A6%AC/


http://blog.naver.com/PostView.nhn?blogId=bitnang&logNo=70183582975

반응형
Comments