반응형

이전 글에서 오브젝트 파일을 링크하고 원하는 파일 포맷으로 변환하는 방법을 알아보았는데, 이제 보호 모드 커널 디렉터리에 C 소스 파일을 추가하고 자동으로 포함하여 빌드하는 방법을 알아봅니다.

그리고 환영 메시지를 출력하는 C 코드를 추가하여 보호 모드 엔트리 포인트와 통합합니다.



C 커널의 엔트리 포인트가 될 Main.c 파일을 생성하기 전에 여러 소스 파일에서 공통으로 사용할 헤더 파일부터 생성합니다.

헤더 파일보호 모드 커널 전반에 걸쳐 사용할 것이므로, 기본 데이터 타입과 자료구조를 정의하는 데 사용합니다.

01.Kernel32 디렉터리 안Source 디렉터리 안Types.h 파일을 만들고 아래와 같이 입력합니다.

 

보호 모드 커널용 공통 헤더 파일


위의 코드에서 아래쪽에 있는 CHARACTER 타입텍스트 모드 화면을 구성하는 문자 하나를 나타내는 구조체
텍스트 모드용 비디오 메모리(0xB8000)에 문자를 편하게 출력할 목적으로 추가했습니다.

#pragma pack구조체의 크기 정렬에 관련된 지시어구조체의 크기를 1byte로 정렬하여 메모리 공간을 더 할당하지 않게 합니다.

공용 헤더 파일을 생성했으니 이제 01.Kernel32 디렉터리 안Source 디렉터리 안Main.c 파일을 추가하여 C 코드 엔트리 포인트 파일을 생성합니다.

 

C 코드 엔트리 포인트 파일(Main.c)


Main() 함수C 코드의 엔트리 포인트 함수이며, 0x10200 주소에 위치하며 이전에 작성했던 보호 모드 엔트리 포인트(EntryPoint.s) 코드에서 최초로 실행되는 C 코드입니다.

Main() 함수를 가장 앞쪽으로 위치시켜, 컴파일 시 코드 섹션의 가장 앞쪽에 위치하게 했습니다.

Main() 함수는 아직 kPrintString() 함수를 사용하여 메시지를 표시하고 무한 루프를 수행하지만, 앞으로 많은 기능들이 추가될 것입니다.

Main() 함수 아래에 있는 kPrintString() 함수화면의 특정 위치(X, Y)에 문자열을 출력해주는 함수텍스트 모드용 비디오 메모리 주소(0xB8000)에 문자를 갱신시킵니다.

이제 C 커널을 위한 파일을 생성했으니 보호 모드 엔트리 포인트를 수정하여 0x10200 주소에 로딩될 C 커널을 실행합니다.


 

→ 보호 모드 엔트리 포인트 코드 수정


이전에 작성했던 보호 모드커널의 엔트리 포인트 코드(EntryPoint.s)화면에 보호 모드로 전환했다는 메시지를 출력하고 나서 무한 루프를 수행하게 했지만, 이제 보호 모드 엔트리 포인트 이후에 C 커널 코드가 있으므로 0x10200으로 이동하게끔 변경해야 합니다.

C 커널 코드로 이동하게 수정하는 일리얼 모드에서 보호 모드로 전환 할 때처럼 CS 세그먼트 셀렉터와 이동할 선형 주소를 jmp 명령에 같이 지정해주면 됩니다.

아래는 EntryPoint.s에서 C 커널 코드로 이동하기 위해 수정한 부분입니다.

보호 모드 커널의 엔트리 포인트 코드(EntryPoint.s) 수정

 


 

→ makefile 수정


이전에 작성했던 01.Kernel32 디렉터리의 makefileEntryPoint.s 파일만을 사용하여 보호 모드 커널 이미지(Kernel32.bin)를 생성했지만, 이제는 다수의 파일을 컴파일하고 링크해야 하기 때문에 makefile을 좀 더 편리하게 수정해야 합니다.

make의 몇 가지 유용한 기능을 사용하여 Source 디렉터리에 .c 확장자의 파일만 추가하면 자동으로 포함하여 빌드하게 수정합니다.

.c 파일을 자동으로 빌드 목록에 추가하려면, 빌드를 수행할 때마다 Source 디렉터리에 있는 *.c 파일을 검색하여 소스 파일 목록에 추가해야 하는데
make에서는 이러한 작업을 위해 디렉터리에 있는 파일을 검색하는 와일드카드 기능을 제공합니다.

Source 디렉터리에 있는 *.c 파일을 모두 검색해서 CSOURCEFILES 변수에 넣고 싶다면 다음과 같이 와일드카드를 사용하여 입력합니다.

참고)
$(wildcard : 디렉터리에 특정 패턴의 파일을 추출하는 wildcard 함수
Source/*.c) : Source 디렉터리에 확장자가 .c인 모든 파일을 의미

Source 디렉터리내의 모든 .c 파일을 CSOURCEFILES 변수로 지정하는 예


디렉터리에 있는 모든 C 파일을 검색했다면 이 파일들에 대한 빌드 룰만 정해주면 자동으로 빌드할 수 있습니다.

지금까지의 makefile각 파일에 대해 빌드 룰을 개별적으로 기술했는데, 이렇게 사용하면 빌드에 필요한 파일이 많으면 관리가 어렵고, 파일이 추가되고 삭제될 때마다 룰을 변경해야 하므로 실수하면 빌드 오류나 실행 도중 오류가 발생할 수 있습니다.

이러한 문제를 파일 패턴에 대해 동일한 룰을 적용함으로써 간단히 처리합니다.

만약 모든 .c 파일은 'gcc -c' 라는 컴파일 과정을 통해 .o 파일로 변환된다면, 아래와 같이 써서 모든 .c 파일을 .o 파일로 컴파일합니다.

참고)
%.o : .o 확장자로 생성
%.c : .c 확장자가 있는 모든 파일명
gcc -c $< : Dependency의 첫 번째 항목

 

확장자가 .c인 파일을 .o로 변경하는 룰을 지정하는 예


와일드카드와 패턴 룰 기능을 이용하면, Source 디렉터리 내의 모든 C 파일을 자동으로 컴파일할 수 있습니다.

일반적으로 오브젝트 파일은 소스 파일과 같은 이름이고, 확장자만 .o로 변경되므로 소스 파일 목록에 포함된 파일의 확장자를 .c에서 .o로 수정하면 됩니다.

특정 패턴을 치환하려면 patsubst 기능을 사용하면 되고, &(patsubst 수정할 패턴, 교체할 패턴, 입력 문자열)의 형식으로 사용합니다.

CSOURCEFILES의 내용에서 확장자 .c를 .o로 수정하고 싶다면 아래와 같이 사용합니다.

참고)
$(patsubst : 패턴 치환 함수
%.c : .c의 확장자를 가지는 모든 문자열
%.o : 확장자를 .o로 교체
$(CSOURCEFILES)) : CSOURCEFILES 변수에 담긴 값

CSOURCEFILES 내의 목록의 확장자를 .c에서 .o로 수정하여 COBJECTFILES 변수로 지정하는 예


하지만 C 커널 엔트리 포인트 함수가장 앞쪽에 배치하려면 엔트리 포인트 오브젝트 파일을 COBJECTFILES의 맨 앞에 둬야 합니다.

만일 C 커널의 엔트리 포인트를 포함하는 오브젝트 파일 이름이 Main.o라고 가정하면, Main.o 파일을 COBJECTFILES에서 맨 앞에 두려면 아래와 같이 subst를 사용합니다.

참고)
subst : 문자열 치환 함수
Main.o, , $(COBJECTFILES)) : COBJECTFILES에 있는 문자열 중에 Main.o를 공백으로 치환
Kernel32.elf: $(CENTRYPOINTOBJECTFILE) $(COBJECTFILES) : Main.o 오브젝트 파일을 가장 앞쪽으로 이동


subst 기능으로 Main.o 오브젝트 파일을 가장 먼저 링크 하는 예



앞의 makefile의 내용 중 .c 부분만 .asm으로 수정하고, GCC 컴파일러 옵션 대신 NASM을 사용하게 변경하면 끝이지만 단, 컴파일된 어셈블리어 오브젝트 파일과 C언어 오브젝트 파일은 같이 링크되어야 하므로 이를 고려하여 컴파일 옵션을 설정해야 합니다.

GCC의 오브젝트 파일ELF32 파일 포맷 형태를 따르므로 NASM의 오브젝트 파일 역시 동일한 포맷으로 생성되게 컴파일 옵션에 '-f elf32'를 추가합니다.

참고)
특정 오브젝트 파일을 가장 먼저 링크하는 방법은 커맨드 라인 인자의 가장 앞쪽에 두는 방법도 있지만, 링커 스크립트의 .text 섹션 부분을 수정해도 같은 결과입니다.

기존의 링커 스크립트를 수정하여 Main.o를 가장 먼저 링크하게 만들려면 OUTPUT_FORMAT("binary")와 .text 섹션 부분에서 {와 } 사이 맨 처음 부분에 Main.o(.text)라고 적으면 됩니다.



소스 파일에 관련된 헤더 파일을 찾게 하려면 GCC의 옵션 중 makefile용 규칙을 만들어주는 전처리기(Preprocessor) 관련 옵션(-M 옵션)을 사용하여, 자동으로 헤더 파일을 추출할 수 있게 합니다.

그중 -MM 옵션을 사용하면 stdio.h와 같은 시스템 헤더 파일을 제외한 나머지 헤더 파일에 대한 의존 관계를 출력할 수 있기에 -MM 옵션을 이용하여 소스 코드를 모두 검사하고 그 결과를 파일로 저장하면, 소스 파일별 헤더 파일의 의존 관계(Dependency)를 확인할 수 있습니다.

참고)
-MM : 시스템 헤더 파일을 제외한 나머지 헤더 파일의 의존성을 검사하여 화면에 출력하는 옵션
> : 화면의 출력을 파일로 저장하는 기호

Main.c 및 Test.c의 의존 관계를 Dependency.dep 파일로 저장하는 예


Dependency.dep 파일makefile에 포함해야 각 파일의 의존 관계를 분석하여 정확한 빌드를 수행할 수 있습니다.

make는 수행 시 다른 makefile을 포함하는 기능을 제공하며, include 지시어가 그러한 역할을 담당합니다.

include 지시어는 해당 파일이 없으면 에러를 발생시키기 때문에 최초 빌드 시나 오브젝트 파일을 정리하고 나서 다시 빌드할 때 Dependency.dep 파일이 없으면 빌드 에러가 발생할 수 있습니다.

이를 피하기 위해 현재 디렉터리를 검사해서 Dependency.dep 파일이 있을 때만 포함하면 되는데 make의 조건문과 wildcard 함수를 조합하면 됩니다.

참고)
ifeq (Dependency.dep, $(wildcard Dependency.dep)) : wildcard 함수의 결과가 Dependency.dep와 같으면 endif까지의 구문 수행
$(wildcard Dependency.dep) : 현재 디렉터리에 Dependency.dep 패턴이 있는 파일 목록

현재 디렉터리에 Dependency.dep 파일이 있는 경우에만 include를 수행하는 예


이제 이러한 내용을 바탕으로 실제로 빌드에 사용될 makefile을 작성합니다.

FS64 OS커널 디렉터리는 소스 디렉터리(Source)와 임시 디렉터리(Temp)로 다시 구분되고, 커널 빌드 작업은 임시 디렉터리를 기준으로 수행하기 때문에 Dependancy.dep 파일의 내용과 경로를 같게 하려면 make를 수행하는 디렉터리를 변경하는 옵션 -C를 이용하여 임시 디렉터리로 변경한 후 makefile를 수행합니다.

최종 결과물인 보호 모드 커널 이미지컴파일과 링크 과정이 끝난 후에 보호 모드 엔트리 포인트와 바이너리로 변환된 C 커널을 결합하여 생성합니다.

아래는 새로 작성된 01.Kernel32 디렉터리의 makefile입니다.

새롭게 작성된 보호 모드 커널의 makefile(01.Kernel32 디렉터리) 1

 

새롭게 작성된 보호 모드 커널의 makefile(01.Kernel32 디렉터리) 2


이제 makefile까지 준비되었으니 보호 모드 커널을 빌드하고 바로 실행하면 됩니다.

혹시라도 VirtualOS.asm 파일VirtualOS.bin 파일이 아직 남아있다면 지워줍니다.

반응형

+ Recent posts