Welcome to My World (www.dgmayor.com)

소프트웨어/C & 자료구조 & 커널 & DB

9. C언어로 OS 만들기

dgmayor 2022. 6. 13. 17:56
728x90

이제 본격적으로 C언어로 OS를 개발해봅시다. 전산학을 공부한 사람이라면 어떤 언어로 프로그램을 만들던 결국 어셈블리어로 변환되고 그 후 기계어로 변해 동작한다는 것을 알 겁니다. 지금까지 짠 코드들은 모두 어셈블리어로 개발했습니다. 해당 코드와 합치기 위해선 C언어로 개발한 코드를 어셈블리어로 변환하고 궁극적으로 기계어로 변환해서 우리의 OS 이미지(image)에다 붙여넣을 것입니다.

우리의 목표는 C언어로 개발을 하되, 소스코드를 기계어로 만들어 통합하는 것입니다. C언어에서 기계어를 추출하는 방법은 여러 가지입니다. 저는 Linux 계열에서 제공하는 gcc 컴파일러를 사용할 예정입니다. 그런데 개발을 Windows 상에서 하고 있었으니... Linux를 vmware에 설치해야겠죠?

 요약하면, 다음과 같은 환경을 구축하면 됩니다.

1. Linux 설치 ( Ubuntu )
2. nasm 설치 ( Ubuntu 안에 )
3. gcc 설치 ( Ubuntu 안에 )
4. GNU 링커 ( Ld )
5. 공유폴더 설정 ( Ubuntu와 Windows간 소스공유를 하기 위함 )
https://m.blog.naver.com/PostView.nhn?blogId=cjh226&logNo=221159795371&proxyReferer=https%3A%2F%2Fwww.google.co.kr%2F


여러분들의 탁월한 구글링 실력을 믿고... 설명은 생략하도록 하겠습니다. ( 공유폴더 셋팅은 어려울 수 있으니 링크를 올립니다 )

완료되면 다음과 같이 윈도우와 우분투를 넘나들면서 개발을 할 수가 있게 됩니다! 또 우분투가 제공하는 각종 강력한 기능들 또한 쓸 수가 있습니다!

실시간으로 파일이 공유되는 아름다운(?) 광경

아주 좋습니다. 이제 다음과 같이 아주 짧은 코드를 작성해봅시다. ( 리눅스에서 하던, 윈도우에서 하던 선택은 자유입니다! )

// main.c void main() { int line = 5; char str[11] = "HelloWorld"; char *video = (char*)(0xB8000 + 160 * line); for (int i = 0; str[i] != 0; i++) { *video++ = str[i]; *video++ = 0x03; // 첫 째는 배경색, 둘 때는 글자 자체 색 } return; }

간단한 코드입니다. 비디오 세그먼트 부분에 ( Bx8000 ) 5번째 줄에 HelloWorld 를 쓰는 코드입니다. 이제부터 여러 단계의 명령어를 거쳐 main.c 를 기계어로 바꾸고, 이를 기존의 우리 코드와 합친 후 실행을 해 보도록 하겠습니다.

먼저 첫 번째 명령어입니다. " gcc -c -m32 -ffreestanding main.c -o main.o "

해당 명령어는 c언어를 기계어로 바꾸지만 아직 링크(Link)는 하지 않은 오브젝트(obj) 상태입니다. 간단히 말하자면 다른 오브젝트 파일들과 합치기 전의 상태를 의미합니다. c언어로 개발할 때 수십개의 소스(.c)파일이 결국 하나로 묶여 실행파일이 되는데 그 전 단계라고 보시면 됩니다. -가 붙은 여러 개의 문자들은 컴파일 옵션인데 일단 따라해보시기 바랍니다.

두 번째 명령어입니다. " ld -melf_i386 -Ttext 0x10200 -nostdlib main.o -o main.img "

첫 번째 명령어로 얻은 main.o를 가공해서 main.img를 얻는 과정입니다. 여기서 0x10200 옵션이 매우 중요합니다. 이는 해당 코드를 0x10200 에 올리겠다고 알려주는 코드입니다. 임의의 주소를 넣어도 무방합니다만 저희가 지금까지 만든 코드가 부트섹터의 하드디스크 실행 코드에 의해 0x10000 에 적재되었습니다. 또 0x200 이 하나의 섹터 크기임을 고려해 하드디스크 실행코드를 조금 수정해서 2섹터까지 읽고 ( 그렇다면 읽는 코드는 0x10000 ~ 0x10200 와 0x10200 ~ 0x10400 이렇게 2개의 섹터가 메모리에 올라가겠죠? ) 우리의 sector2.asm도 조금 수정해 0x10200 으로 점프하는 코드를 하나 집어넣어준다면? 바로 sector2.asm 에서 main.c 로 점프하게 되는 효과를 얻을 수 있을 겁니다!

처음 읽으면 이해가 되지 않을 수 있지만, 천천히 다시 음미(?)해보시기 바랍니다.

세 번째 명령어입니다. " objcopy -O binary main.img disk.img "

main.img 에서 꼭 필요한 부분만 추출합니다. 이로 인해 disk.img을 얻을 수 있습니다. 이제 남은 것은 이 disk.img을 기존에 우리 코드로 만든 img의 뒤에 합치는 일만 남았습니다. 이는 다음과 같은 네 번째 명령어로 ubuntu에서 실행 가능합니다.

네 번째 명령어입니다. " cat Boot.img Sector2.img disk.img > final.img "

 

자! 이제 main.c를 기계어로 만들고 기존의 우리 이미지 파일에 합치는 법까지 모두 배웠습니다. 근데 한 가지 빼먹은 것이 있죠? 바로 boot.asm 과 sector2.asm을 수정하는 일입니다! boot.asm 에서는 하드디스크에서 읽는 섹터의 수를 1개에서 2개로 늘리는 것을 구현합니다. sector2.asm 에서는 0x10200로 점프하는 코드를 삽입하여 main.c의 코드를 실행할 수 있도록 합시다.

각각의 파일 코드를 다 올리지 않고, 수정하는 부분만 올리겠습니다.

먼저 Boot.asm 입니다. ( 라인 20줄 ~ 30줄 )

~~ read: mov ax, 0x1000 mov es, ax mov bx, 0 mov ah, 2 mov al, 2 ; => 원래는 mov al, 1 이였습니다. 이를 2로 바꿔 2개 섹터를 읽도록 합시다. ; 그래야 main.c를 메모리에 올릴 수 있습니다. mov ch, 0 mov cl, 2 ~~

다음은 Sector2.asm 입니다. ( 라인 10줄 ~ 20줄 )

~~ START: mov bx, DataSegment mov ds, bx mov es, bx mov fs, bx mov gs, bx mov ss, bx lea esp, [START] mov edi,0 mov esi, msg call printf jmp dword CodeSegment:0x10200 ; => jmp $을 지우고 0x10200에 점프하는 코드를 작성합시다. ~~

자 이제 코드 수정까지 마쳤으니 각각 다시 재컴파일해서 새로운 img 파일을 얻고, 이를 main.c 가공해 얻은 disk.img와 합쳐 final.img을 얻어봅시다. 그리고 대망의 실행을 시작합시다!

 
감격스러운 HelloWorld !

이제 드디어 C언어로 운영체제를 개발할 수 있는 환경을 구축했습니다!!!

하지만 앞서 진행한 4개의 명령어를 main.c 수정할 때마다 일일이 다 치는 것은 매우 고달픈 일이 아닐 수 없습니다. 따라서 개발의 편의를 위해 다음 강의에서 Linux가 제공하는 make 명령어에 대해 알아보도록 하겠습니다.

수고하셨습니다.

728x90