1. 출처

출처보기(http://neri.cafe24.com/menu/bbs/view.php?id=kb&page=1&sn1=&divpage=1&sn=off&ss=on&sc=on&keyword=x86&select_arrange=headnum&desc=asc&no=264)

2. 작성한 이유

OS를 만들어보고 싶은 마음에 무작정 뛰어 들었는데 대부분 환경은 리눅스상이
gcc였습니다. 하지만 저는 windows 환경하의 vc 개발을 주로 해왔으므로 무척
불편(?)했습니다. Djgpp 라는 dos용 gcc 포팅 버전과 윈도우용 cygwin 패키지를
사용하면 역시 동일하게 gcc를 윈도우에서 컴파일 할 수 있습니다 그래도 저는 vc의
에디팅 환경이 맘에 들어서 디버깅까지는 힘들겠지만 (어차피 커널 디버깅하려면
스스로 자신의 커널 디버거를 만들어야 하는 거 같습니다. ) 에디팅과 컴파일
그리고 링커를 vc6를 사용하고 싶었고 그래서 이를 사용할 수 있는 방법을 소개하려
합니다.

3. 참고하면 좋은 사이트들

어려운 영문 OS개발방법을 한글화 해주는 고마운 사이트
Chobits os의 소스를 얻을 수 있습니다.
무척 좋은 Protected mode에 대한 설명과 소스를 담고 있는 사이트
http://www.nondot.org/sabre/os/articles
-
OS에 대한 전반적인 자료가 있습니다.
http://www.osdever.net/
-
Bona Fide OS 개발 튜토리얼 사이트
http://mega-tokyo.com/osfaq2/
-
OS FAQ
http://www.osdev.org/
-
소스가 공개된 여러 os가 있습니다.
http://www.bellona2.com
-
혼자서 GUI OS를 만드시고 책도 출판하신 분의 사이트
http://ksyspro.org
-
한국 임베디드 개발자 모임

4. Protected i386 OS를 위한 잛은 설명

Intel은 다른 cpu 벤더보다 역사가 오래되어서 4bit microprocessor인 4004에서
출발해서 8bit cpu 8008, 8080, 16비트 8086, 80186, 80286, 32비트 80386, 80486,
80586 (또는 P5 그러나 숫자는 저작권 보호를 받지 못한다길래 이후에 Pentium으로
바뀌었습니다 ). 이렇게 긴 역사를 갖고 있는 보편적인 cpu 8080 또는 8086 통틀어
intel x86 cpu에서 돌던 프로그램도 586에서도 수행되도록 하위호환을 갖게 됩니다.

이 하위호환 때문에 아무리 최신 컴퓨터도 처음 부팅시에는 빠른 x86 처럼
수행되도록 설계되었습니다. 부트섹터에서 이 빠른 x86에서 최신의 기능을 사용하는
i386+ 환경으로 만들기 위해서 적어도 다음과 같은 일을 하게 됩니다.

ORG 0x7C00 - PC가 리셋되면 부팅될 디바이스의 첫번째 섹터(512바이트)를
읽어 메모리 0x7C00에 올려 놓은 후 이 코드로 점프하게 됩니다. 따라서 부트섹터
코드는 그 메모리에서 시작한다고 지정해야 할것입니다.

A20 enable - 예전에는 실제 메모리크기가 크지 않아 1M 이하만 접근하도록
설계되었는데 이젠 메모리가 1M이상 되는 것이 많아 1M 이상을 실제 접근 하기 위해서
해주어야 합니다.

Protected mode - 처음 부팅할땐 x86 Real Mode라는 세그먼트:오프셋
지정 방식으로 수행되지만 80286이상에서는 보호모드가 지원되었고 (최대16M가능)
80386에서는 4G까지 접근 가능하도록 되었습니다.

커널로드 - 부트섹터 코드는 C로된 바이너리 커널을 여러 섹터에서
읽어서 특정 메모리 번지에 로드한 후 그 시작 함수 (엔트리 포인트 함수)로 점프하게
됩니다.

5. 준비물


VC6,
- 커널의 컴파일 및 링킹을 담당합니다.
- 우리는 디스켓도 사용하지 않고 바로 컴파일해서 이미지로 뜬 후 VMWare
로드할 것입니다. ( no 플로피 부팅디스켓, no 리붓, no test machine )

Nasm
- Netwide Asm으로 at&t 계열 및 intel 계열 둘다 지원하고 target format도
많이 지원하는 어셈블러입니다. 설치과정없이 그냥 nasm.exe 만 있으면 됩니다.

6. 소스 파일들

bootsect.asm
- 간단한 보호모드(세그먼테이션방식) 진입 및 커널 로드 부트로더

main.c
- VC로 컴파일될 작은 커널

Makeboot.exe
- 부트섹터와 커널이미지를 merge하는 간단한 makeboot.c 컴파일해서 자신만의
부트 이미지 뜨는 프로그램 만들면된다.

7. 소스

7.1. bootsect.asm

[BITS 16]       ; We need 16-bit intructions for Real 
mode

[ORG 0x7C00]    ; The BIOS loads the boot sector into memory location 
0x7C00

reset_drive:

        mov ah, 0               ; RESET-command

        int 13h                 ; Call interrupt 13h

        or ah, ah               ; Check for error code

        jnz reset_drive         ; Try again if ah != 0

        ;vc에서 /base 10000 로 헀기 때문에 여기서부터 메모리를 로드
        ; 해야 한다. ES:BX 로 1000:0000h 이기 때문에 아래처럼 한다.

        mov ax, 1000h           ; es:bx <-- 1000:0000h 

        mov es, ax
        mov bx, 0h          ; Destination address = 0000:1000

        mov ah, 02h             ; READ SECTOR-command

        ; 두번째 섹터부터 3칸섹터가 커널이미지 섹터다.

        mov al, 3h             ; Number of sectors to read = 1

        mov ch, 0               ; Cylinder = 0

        ; 현재 첫번째섹터512 이며 두번째 섹터부터 3개 섹터가 커널이미지다

        mov cl, 02h             ; Sector = 2

        mov dh, 0               ; Head = 0

        int 13h                 ; Call interrupt 13h

        or ah, ah               ; Check for error code

        jnz reset_drive         ; Try again if ah != 0

A20Address:    ; Set A20 Address line here

    CLI

    CALL enableA20

    STI
    JMP Continue

enableA20:
        call enableA20o1

        jnz short enableA20done

        mov al,0d1h

        out 64h,al

        call enableA20o1

        jnz short enableA20done

        mov al,0dfh

        out 60h,al

enableA20o1:

        mov ecx,20000h

enableA20o1l:

        jmp short $+2

        in al,64h

        test al,2

        loopnz enableA20o1l

enableA20done:

        ret

Continue:   

        cli                 ; Disable interrupts, we want to be alone

        xor ax, ax

        mov ds, ax              ; Set DS-register to 0 - used by lgdt

        lgdt [gdt_desc]         ; Load the GDT descriptor

        mov eax, cr0            ; Copy the contents of CR0 into EAX

        or eax, 1               ; Set bit 0

        mov cr0, eax            ; Copy the contents of EAX into CR0

        jmp 08h:clear_pipe      ; Jump to code segment, offset clear_pipe

[BITS 32]                       ; We now need 32-bit instructions

clear_pipe:

        mov ax, 10h             ; Save data segment identifyer

        mov ds, ax              ; Move a valid data segment into the data segment register

        mov ss, ax              ; Move a valid data segment into the stack segment register

        mov esp, 090000h        ; Move the stack pointer to 090000h

        ; 10200h로 점프 200h는 vc로 링킹하면 기본 PE헤더(200h)를 붙여서 더했다.

        ; 10200h 즉 1000:0200h 로 점프

        ;jmp 1000:0200h

        jmp 10200h

        

gdt:                    ; Address for the GDT

gdt_null:               ; Null Segment

        dd 0

        dd 0

gdt_code:               ; Code segment, read/execute, nonconforming

        dw 0FFFFh

        dw 0

        db 0

        db 10011010b

        db 11001111b

        db 0

gdt_data:              ; Data segment, read/write, expand down

        dw 0FFFFh

        dw 0

        db 0

        db 10010010b

        db 11001111b

        db 0

gdt_end:		; Used to calculate the size of the GDT

gdt_desc:               ; The GDT descriptor

        dw gdt_end - gdt - 1    ; Limit (size)

        dd gdt                  ; Address of the GDT

times 510-($-$$) db 0           ; Fill up the file with zeros

        dw 0AA55h                ; Boot sector identifyer
아래처럼 어셈블 하여 오브젝 부트로더 바이너리를 얻어내야
합니다.

nasm -f bin bootsect.asm -o bootsect.bin

다음은 부트로더와 커널 이미지를 합쳐서 하나의 부팅이미지로 만들어주는 makeboot.c
입니다. 따로 프로젝트를 만들어서 간단히 컴파일해서 makeboot.exe를 만들도록
합니다.
#include 

int 
main(int argnr, char *args[])

{

  FILE 
*output, *input;

  int 
i, bytes_read, sectors_read, bytes_from_file;

  char 
buffer[512];

  if 
(argnr < 4) {

    printf("Invalid 
number of parameters.\n\n");

    printf("USAGE: 
%s [output] [input 1] [input 2] ... [input n]\n", args[0]);

    printf("Example: 
%s a.img bootsect.bin kernel.bin");

    exit(0);

  }

  output 
= fopen(args[1], "r");

// 
덮어 쓰기 할꺼냐고 물어보는 코든데 그냥 덮어쓰자. --

  if 
(output != NULL) {

    buffer[0] 
= 0;

  }

  fclose(output);

  output 
= fopen(args[1], "wb");

  for 
(i = 2; i < argnr; i++) {

    input 
= fopen(args[i], "rb");

    if 
(input == NULL) {

      printf("Missing 
input file %s. Aborting operation...", args[i]);

      fclose(output);

      exit(1);

    }

    bytes_read 
= 512;

    bytes_from_file 
= 0;

    sectors_read 
= 0;

    while(bytes_read 
== 512 && !feof(input)) {

      bytes_read 
= fread(buffer, 1, 512, input);

      if 
(bytes_read == 0)

        break;

      if 
(bytes_read != 512)

        memset(buffer+bytes_read, 
0, 512-bytes_read);

      sectors_read++;

      fwrite(buffer, 
1, 512, output);

      bytes_from_file 
+= bytes_read;

    }

    printf("%d 
sectors, %d bytes read from file %s...\n", sectors_read, bytes_from_file, 

args[i]);

    fclose(input);

  }

  fclose(output);

  return 
0;

}

다음은 커널 소스입니다.


// 
bro (bro@shinbiro.com)2004-02-17 - initial ( vc dev envronment setting )

// 
starting function

void 
start()

{   

    char* 
hello = "Hello OS!";

    unsigned 
char* vidmem = (unsigned char*)0xB8000;

    while( 
*hello != '\0' )        

    {

        *vidmem++ 
= *hello++;

        *vidmem++ 
= 7;        // 문자 기본 속성

    }   

    

    // 
커널이 끝나면 안된다. 계속 돌아야한다.   

    for(;;);

}

7.2. Visual C++ 컴파일 환경 구성하기


위의 커널 소스를 컴파일 하기 위해서 VC 컴파일 환경을 구성해보도록 합니다.

먼저 Win32 Console Application으로 간단히 프로젝트를 생성합니다.

디버깅은 못하니 그냥 Release 모드로 선택한 후 C++ 세팅을 아래처럼 합니다.
/nologo /G4 /Zp1 /ML /W3 /vmg /vd0 /GX /Od /Gf /FAc /Fa"Release/" /Fo"Release/" /Fd"Release/" /FD /c
Link탭의 옵션은 아래처럼 합니다.
/nologo /base:"0x10000" /entry:"start" /subsystem:console /incremental:no /pdb:"Release/testos.pdb" /map:"Release/testos.map" /machine:I386 /nodefaultlib /out:"testos.bin" /DRIVER /align:512 /FIXED
또한 컴파일 작업에서 바로 부트섹터도 컴파일하고 컴파일 끝나면 바로 부트 이미지도
만들도록 Pre-Link와 Post-Build를 작성합니다.

컴파일을 해봅니다.
Deleting intermediate files and output files for project 'testos - Win32 Release'.
--------------------Configuration: testos - Win32 Release--------------------
Compiling...
main.c
Linking...
LINK 
: warning LNK4096: /BASE value "10000" is invalid for Windows 95; image may 
not run 1 sectors, 512 bytes read from file bootsect.bin...
3 sectors, 1536 bytes read from file testos.bin...
testos.bin 
- 0 error(s), 1 warning(s)

8. VMWare 테스트 환경 구성하기

컴파일을 마치고 나면 아래와 같은 탐색기 모습이 될 것입니다. 마지막으로 셍성된 testos.img 가 우리의 커널 이미지가 됩니다.
zeropage:VMWareOSImplementationTest01.gif
Partcopy.exe 툴을 사용하여 부팅 디스켓에 놓을 수도 있지만 번거롭습니다. 따라서 VMWare에서 직접 이를 디스켓 이미지로 로드하도록 합니다.
zeropage:VMWareOSImplementationTest02.gif
무조건 그냥 기본으로 Next 합니다.
zeropage:VMWareOSImplementationTest03.gif
제일 중요한 것은 이렇게 만든 가상 testos 의 플로피 디스켓 부팅 설정입니다.
zeropage:VMWareOSImplementationTest04.gif
Use Floppy Image에 해당 img 패스 맞추도록 합니다.

이제 실행해봅니다.
zeropage:VMWareOSImplementationTest05.gif
Retrieved from http://wiki.zeropage.org/wiki.php/VMWare/OSImplementationTest
last modified 2021-02-07 05:28:21