이제 7장으로 넘어왔어요.. C언어로 커널을 만들 수 있대요!! 넘나 기쁜것~ㅜㅠ

은 꿈.. 링커 스크립트가 뭐죠? 먹는건가요? 우걱우걱

(우분투 환경에서는 /usr/lib/ldscripts/elf_i386.x 경로이며 윈7 환경에서는 C:\cygwin\usr\cross\x86_64-pc-linux\lib\ldscripts\elf_i386.x 경로에 링커 스크립트가 위치해있습니다!! 이 파일을 복사하여 01.Kernel32 디렉터리에 붙여넣으시면 링커 스크립트 수정 준비 끗!!!)

 

혹시 아직 제 티스토리의 매애애애애앤 아래 좌측 하단의 github 링크를 못찾으신 분께서는..(여기에 소스파일 전부 다 있거든용 ㅎㅎ 책과 상이한 부분이 많다보니 열심히 삽질한 결과를 저의 github에서 훔쳐가도록 하세요!!)

 

https://github.com/yummyHit/yummyHitOS

 

이곳에 방문하셔서 소스파일을 전~부 뜯고 씹고 맛보고 즐기실 수 있습니다!

왜 무엇이 어떻게 바뀌었는가.. 하면 우선 elf_i386.x 링커 스크립트에서

 

. = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 12 ? 12 : 0, .);

 

이렇게 작성되어 있는 곳이 있는데, 이 부분이 있으면 make할 때 에러가 나더라구요!! 이러한 이유들 때문~에~ 소스파일을 올려드리게 되었습니다(절대 저의 소스를 자랑하기위함이 아니예여!!)

(참, 그리고 위에 . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 12 ? 12 : 0, .); 부분은 주석처리 해주어야 해요!!

/* . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 12 ? 12 : 0, .); */ 이렇게 해주시면 됩니다~!!)


7.2장으로 넘어가면 드디어 C언어가.... Type.h 라는 헤더파일이... 하아 감격T^T

이 헤더파일의 중요한 점은 이제부터 쓸 중요한 함수들의 모든 자료형 타입을 거의 여기에 정의해둡니다. 특히 Java 이후의 언어에는 있을 BOOL타입과 BYTE타입을 정의합니다!! Visual Studio나 GCC 어느 라이브러리에도 없는 타입을 이렇~게 선언해줍니다.

 

 

그냥 unsigned char로 사용하면 되지 않냐구요? 뭐... 되긴 하겠지만... 편리함이 아닐까요?! BOOLEAN 이라는 단어와 BYTE 라는 단어를 보면 "아! 이거 TRUE와 FALSE로 나누는 자료형이구나!" 같은 생각이 들텐데 같은 unsigned char를 쓰면 헷갈리니까요 ㅎㅎ

편리함을 위해 만들었으니 편리하게 쓰면 그만입니다! 그리고 그 아래 패킹하는 것이 보이는데.. 수학 잘하시나요? 저도 참 좋아했는데요.. 자 그럼 한 번 만나러 가보시죠!

 

 

위와 같은 두 개의 구조체가 있다고 가정해봅시다. 두 구조체 멤버는 똑같은데.. 도대체 뭐가 다르다는 건가요!?

메모리 저장 방식이 문제가 됩니다. 현재 우리의 개성파 OS는 32bit 보호모드이기 때문에 32bit를 기준으로 설명드리겠습니다.

32bit 운영체제는 메모리를 4byte씩 끊어 읽는다는 사실을 알고 계신가요!? 이 두 개의 구조체가 메모리에 할당되는 방식은 바로!!

 

 

참.. 제가 이제서야 커밍아웃을 하는건데 저 보라색 쬲이에여 나으 맥뿍도 보라보라하구 침대도 보라보라 가방도 보라보라.. 보라색이 마음에 평안을 주는 색이라고 하더라구여 야미님의 말에 의하면 ㅎㅎㅎ헿헤헿

위 그림을 보시면 이해가 되시나요? 구조체 A는 먼저 4byte를 할당한 후, 차례대로 a1, a2, a3 순서로 넣다보니 char와 short 사이에 1byte의 빈 공간이 생깁니다!

이어서 구조체 B는 4byte를 할당한 후, a1을 넣고보니 a3가 4byte짜리 변수네? 그럼 3byte는 비워야징~ ㅎㅎㅎ우왕 마니 비었다 난 사람 별로 없는게좋더라~ 처럼 기분 좋게 다 empty가 됩니다. 기분이 좋긴요.. 낭비죠 낭비 지갑에서 돈이 비어있는 느낌이랄까요?

구조체 B보다는 구조체 A가 더 효율적이지만, 양쪽 전부 empty가 생겨버리니.. 이를 방지하기 위해 있는 것이 패킹입니다!

실질적으로 구조체 A나 B 둘 다 7byte만큼의 크기이니, 32bit를 기준으로 메모리를 저장하는 것이 아닌, 우리가 직접 메모리 저장 방식을 정해주는 것입니다!

 

pack 지시자는 구조체의 바이트 정렬 값을 지정해주기 위해 사용한다

pack과 함께 사용되는 키워드로 push와 pop이 있다

push는 현재 값을 저장하면서 정렬 값을 바꾸고 싶을 때 사용한다

pop은 push에 의해 저장된 값을 다시 불러오고 싶을 때 사용한다

 

즉, #pragma pakc(push, 1) ... #pragma pack(pop) 을 한다면 1바이트를 기준으로 메모리에 push를 할 것이고, pop을 기준으로 다시 운영체제에 맞게 저장하겠다!! 라는 것이 됩니다! 우리의 OS에서 정의된 것과 함께 볼까요?

 

 

오호.. 요로코롬 패킹을 해주는군요... 패킹을 안해주면 2byte만큼의 empty가 발생할 뻔 했습니다 ㅎㅎ

참 이 구조체로 말씀드리자면 이제부터 OS Image 로딩부터 각 기법 및 인터럽트 등을 qemu를 통해 출력시키기 위한 구조체예요!! 넘나 중요한 것!! character 멤버에 글자를, color 멤버에 색을 지정할 수 있답니다!!

이제 우리는 EntryPoint.s 파일을 수정해 0x10200 위치에서부터 커널을 수행할 거예요! 우리가 작성한 Main.c 등의 C언어 소스파일을 수행할 수 있도록 하기 위해서죠! 이얏호우 내사랑 C언어가 드디어 실행된다니 둑흔둑흔..

 

책을 천천~히 따라가며 C언어를 음미하다가... 음마 이게 뭐야... 7.3장에서 깜!짝 놀랐네요..

현재 우리는 GCC 컴파일러를 빌려 컴파일을 하고있지만, 실질적으로 윈도우 7 자체에는 C언어의 어느 라이브러리도 존재하지 않아요!

그런데 갑자기 등장한 디스크 이미지를 만드는 ImageMaker.c 소스파일..

 

#include <stdio.h>

#include <stdlib.h>

#include <fcntl.h>

#include <io.h>

    /*리눅스는 io.h 대신 sys/uio.h 입니다!! */

#include <unistd.h>

/* Unix 기반만 추가하라고 되어있던데.. 전 이게 없으면 open(), close(), write() 함수 오류가 나더라구요!! */

#include <sys/types.h>

#include <sys/stat.h>

#include <errno.h>

 

위 전처리기가 원하는 헤더파일들은 아직 우리가 받아들일 수 없습니다!!(참고로 리눅스에서는 GCC 컴파일러 기반으로 돌아가기 때문에 별로 걱정안하시고 그냥 진행해주셔도 됩니다!)

우리의 이클립스에게 라이브러리 경로를 적어줘야할 것 같아요ㅜㅠ 이클립스라곤 자바를 독학할 때만 써봤는데... ㅂㄷㅂㄷ

 

이클립스 내에서 프로젝트 탐색기 -> 현재 프로젝트 명(우리의 개성파 OS 이름 폴더) 마우스 우클릭

-> Properties -> C/C++ General -> Paths and Symbols 탭 -> Includes GNU C에서 Add버튼 클릭

-> File System... 클릭 -> C:\cygwin\usr\include 와 C:\cygwin\usr\cross\lib\gcc\x86_64-pc-linux\version\include 추가 후 Apply

 

이러한 과정을 거치시면 드디어 저 헤더파일들을 읽을 수가 있습니다.

그리고 최근 GCC 컴파일러 업데이트로 약간 변동된 부분이 있더군요! 모든 것은 저의 소스파일을 참고하시기...(너 이런식으로 나오면 사람들이 악플단다? 조심하자?) 제가 직접 삽질을 해보며 무엇이 무엇이 문제일까~ 하여 찾아보았습니다!

 

1. open() 함수에 들어갈 파라미터 중 O_BINARY 가 없습니다!! 이 소스파일에서 open() 함수를 총 4개 사용하는데, 4개의 O_BINARY와 앞에 있는 pipe(|), 뒤에 있는 comma(,) 둘 다 지워주시면 됩니다.

즉, 첫번째 open() 함수부터 나열하자면


open("Disk.img", O_RDWR | O_CREAT | O_TRUNC)

open(argv[1], O_RDONLY)

open(argv[2], O_RDONLY)

open(argv[3], O_RDONLY)


이렇게 해주시면 됩니다!!(argv[2]는 32비트 보호모드시, argv[3]은 64비트 IA-32e모드시 변환하는 open()함수이기 때문에 아직 책에 나와있지 않으시다면 있는 것만 이렇게 바꿔주시면 됩니다~)

그보다... O_BINARY가 O_TEXT와 함께 GNU C 에서 사라졌다고 하기는 하는데...

~Path\sys\fcntl.h 에선 ~Path\sys\_default_fcntl.h 에 있다고하고, _default_fcntl.h 파일엔 분명히 O_BINARY와 _FBINARY를 정의하고 있는데 왜 없다고 하는것인지.. 이클립스 나뿐놈들..(외국인이니까 못읽을거야.. 그럴거야..)


2. 저의 오타 문제였지만, 혹시나 해서 주의!! 이클립스 글자 크기가 작다보니 %랑 &가 헷갈려 보이더라구요.. 포맷을 위한 %와 주소를 위한 &가 잘못 쓰이면 완벽히 잘못 된것이기 때문에 참고하시면 좋을 것 같습니다!!

 

이렇게 ImageMaker.c 파일까지 완성시켰으면 당연히 makefile을 만들텐데, 이 makefile에 한 줄의 명령을 추가하시면 편합니다!

 


ImageMaker.exe 규칙에 정의된 명령인 "cp $@ ../" 요것을 추가해주시면 좋아영!! 두번조아여 세번조아여!!

 

이 한 줄을 추가해주시면 프로젝트 빌드 후 qemu를 실행시켰을 때 아무 문제 없이 잘 실행 되실겁니다!!

 

드디어 C언어를 이용한 커널 빌드를... 저희가 성공시켰습니다 쨖쨖쨖~~ 이제 다음엔 뭔지 모를 A20게이트와 페이징기능을 한 번 다뤄보겠습니다!! 그럼 to be continue..

후룰루룰~ 드디어 C언어를 마주할 수 있는 챕터로 넘어왔...지만 우리가 할 일은 먼저 6장의 마지막에 있던 makefile을 초큼 분석해보쟈!!

갑자기 이상한 내부 매크로가 튀어나와서 흠칫했을 것이다. 아니 $< 요곤 뭐시고 $^ 요곤 뭐시다야? 뭐시긴 뭐시여.. 궁큼한 것이제~

그래서 먼저 makefile을 만들 때 규칙과 makefile의 문법에 대해 알아보고 넘어가고 싶어졌다!(이건 순수 개인적인 궁금함일 수 있으므로.. 별로 관심이 없다~ 하시는 분들께서는 ㅜㅠㅜ 눙물이나지만 저는 정말 열심히 적고 있지만 살포시 다음 포스팅으로 넘어가시면 됩니다 눙물눙물 훌쩍훌쩍)

 

저번 포스팅에서 간~략하게 make명령을 이용할 때 쓰이는 makefile 예제를 만들었던 적이 있는데, 그 때는 저어어어엉말 아주 예제 기본예제!

C언어 시작했을 때 printf("Hello, World!\n"); 하나 찍고서 "훗, 난 컴퓨터를 잘해" 라는 것과 같은예제!!

오늘은 쫌 자세하게 들어갑니다!

 

매크로 : 사용할 옵션 및 파일명 등을 한 단어로 정의하는 것

 

저 정말 설명 못해요ㅜㅠ 예를 들면서 하는게 역시 가장 이해하는데 좋은 것 같지 않나요? 헤헿

(리눅스를 써보지 않으신 분에 한해 자세~하게 설명드릴테니 조금 답답하시더라도 참아주세여!!)

 

윈도우를 쓰시던 분들은 개발할 때 Visual Studio를 사용하시다보니 자동 컴파일이 되었겠지만.. 리눅스를 사용하면 gcc 라는 컴파일러를 이용합니다! (요즘 qt로 크로스 컴파일하는 것도 재미가 쏠쏠해요~ 해킹 툴 포스팅에서 다뤄드리죠!)

gcc 명령으로 컴파일을 할 때, 기본적으로 -c 옵션(object 파일로 컴파일하는 옵션) / -o 옵션(컴파일 후 생성되는 바이너리 파일의 이름을 정하는 옵션) 두가지 옵션은 기본중의 기본!

(책을 좀 더 열심히 공부하다 보면 뭐 이런 옵션도 있나.. 싶을 정도로 옵션이 많습니다.)

 

저번(2일차)에 makefile을 들여다 보면

 

gcc -c main.c

gcc -o helloworld.exe main.o

 

이 두가지 명령이 보이는데, 즉 gcc -c main.c 를 통해 main.o 파일을 생성하고, 이 main.o 오브젝트 파일을 이용해 gcc -o helloworld.exe 명령을 수행하여 helloworld.exe 파일을 생성시키는 과정이었다.

이 예제는 1개의 소스파일을 실행파일(바이너리파일)로 컴파일하는 것인데, 실제로 개발을 하다보면 여러개의 소스파일과 링크파일들이 필요하다!! 이를 위해 있는 것이

"매크로"

 

 

위 그림과 같이 구조가 되어있다고 가정해봅시다. 이런 구조의 프로젝트일 때, 우리가 알고 있는 방식으로 makefile을 만들자면 아래처럼 되겠죠?!

 

 

지금이야.. 파일이 3개지만.. 막 20개 30개 소스파일이 있다면 옵션으로 20개의 소스파일과 10개의 헤더파일과.. 으으 다 적는 것도 일이겠죠? 이럴 때를 위해 있는 매크로 기능!! 빠밤~

 

우리의 예제에서 복잡하게 있는 옵션은 각 오브젝트 파일을 나열해둔 것! 이것을 자신만의 이름으로(보통은 OBJECTS 혹은 COBJECTS 라고 네이밍하지만, 필자는 개성을 좋아하니까 OMYGOD으로 해야징) 지정하여 쉽게 써봅씨다!!

 

 

뭐가 바뀐지 눈치채셨나여?! OMYGOD 이라는 매크로를 만들어서 $ 문자와 ( )괄호 사이에 넣었어요!! (매크로는 콜론(:)이 아닌 등호기호(=)를 사용하셔야 합니다!!)

과연 이게 실행이 되느냐구요? 흫흐흐흫흫흐 놀라지마시라!!

 

 

짜라잔~~~ stdio.h / read.c / write.c / main.c / makefile 구현부 전부 보여드렸어요!! 그냥 짤막하게 보이기 위해 구현하다보니.. write.c에서 부득이하게 printf()함수를 인용해왔네요 핳하핳핳하

정말 잘 실행되지요!? OMYGOD 매크로가 이렇게 잘 먹혀주다니 기특하네요.. 짜식..

이 매크로가 언제 쓰일지 아직 감이 1도 안오신다구요? 이 다음에 포스팅할 7장에서 쓰이겠지만... 미리 보여드리죠 이 무서운 makefile을..!

 

 

호모나 세상에!! 난 이제 OS 개발을 그만둘테야!! 하시면 안됩니다! 차근 차근 보시면 정이 드시면서 감이 잡히실거예요. 요로케 조로케 돌아가는거구나~ 하시면서 ㅎㅎ..(전 아직 이친구와 정들기 싫어서 감이 덜잡혔습니다 ㅜㅠㅜ)

이러한 매크로에도 저처럼 항상 OMYGOD을 쓸 수는 없습니다. 미리 정의된 매크로가 있거든요..

 

ASFLAGS = <- as 명령어의 옵션 세팅
CFLAGS = <- gcc 의 옵션 세팅
CPPFLAGS = <- g++ 의 옵션
LDLFAGS = <- ld 의 옵션 세팅
LFLAGS = <- lex 의 옵션 세팅
YFLAGS = <- yacc 의 옵션 세팅

 

...위 6개가 끝이 아니라 30개가 넘는 매크로가 있다고합니다... 아아.. 어찌하여 저에게 또 이런 암기의 시련을.. 왜때문이죠..!?

이러한 옵션을 통해 gcc 명령을 이용할 때 더욱 편리함이 가증됩니다. 우리가 따로 옵션을 넣어주지 않아도 이 플래그들이 옵션을 그냥 세팅해주니까요! 핳하하핳하핳하핳 귀찮은 옵션들 한방에 꺼져버렷!! 핳핳하하핳핳

 

그럼 이제 처음에 제가 궁금해했던 $<, $^와 같은 내부매크로를 알아보며 makefile에 대한 규칙은 이만 하겠습니다.

 

 

이게 도대체 뭐라고하는지 1도 이해가 안됩니다. 저도 모르겠어요 이 의미만 보면 뭐라하는지...

먼저, $@는 Target 그 자체를 의미합니다. 우리가 만든 makefile을 예를 들어

 

helloworld.exe: $(OMYGOD)

gcc -o helloworld.exe $(OMYGOD)

 

으로 규칙이 있을 때, 여기서 Target은 눈치채셨겠지만 바로바로.. helloworld.exe !!

이 규칙에서 내부매크로를 이용하면 gcc -o helloworld.exe $(OMYGOD) 부분이 gcc -o $@ $(OMYGOD) 으로 바뀐다는 것이죠!!

그럼 확장자가 없는 타겟 이름은 무엇인가..

 

main.o: stdio.h main.c

gcc -c main.c

 

여기서 main 을 의미합니다. 즉, 확장자를 뺀 이름만 의미하는 것 같네요! 그럼 이 규칙에서 내부매크로를 이용하면?!

gcc -c $*.c 이렇게 바꿀 수 있겠네요!! 짧아지는 코드를 보면 희열을 느끼는 야미(가명, 나이 뱁새)

다음 내부매크로인 $< 를 보면 최근에 갱신된 파일..? 보통 소스파일은 유지보수 혹은 개발 단계중에서 계~~속 계~~속 갱신되니까 이걸 일컫지 않을까요!?

방금 예를 들었던 규칙에서 main.c -> $*.c 가 되었는데, 이것을 또 한 번 더 줄여서 $*.c -> $< 로 바꿀 수 있습니다!

꼭 신조어같은..알쓸신잡 핳하핳 알쓸신잡 선생님들 사랑합니당



그럼 뭐 그 다음 $? 매크로는 소스파일같은 파일이 아닌 필수 조건 파일 이름이라고 하네요! 필수 조건 파일이란 역시 콜론(:) 옆에 나오는 파일들을 의미하겠죠!? 이 파일들의 이름이 변경되었을 때 참조하기 위한 매크로인 것 같..긴... 한데... 아직 쓰는 분을 본 적도 없을 뿐더러.. 아무리 쓰려 해도 어디에 어떻게 써야할지 모르겠네요 ㅜㅠㅜ 예를 못들어 드리고 이렇게 짤막한 설명으로 끝내는 점 죄송합니다 ( __ __ ) 꾸벅..

 

마지막 매크로인 $^는 현재 모든 필수 조건 파일들!! 콜론(:) 옆 필수 조건 파일들을 전~~부 의미한다고 합니다.

 

helloworld.exe: main.o read.o write.o

gcc -o $@ main.o read.o write.o

 

여기서 main.o read.o write.o 를 가장 처음에 OMYGOD 과 같은 매크로로 정의해도 되지만, $^ 라는 내부 매크로를 사용할 수도 있다는 것이죠!! 우리가 만든 makefile이 어떻게 바뀌는지 확인해봅시다.

 

 

짜잔!! 이렇게 짧아지다니 이거이거.. makefile 정들 것 같네요! 오늘은 makefile의 규칙에 대해.. 참 마지막으로 하나만 더!! makefile의 규칙이 너무 길어진다면, \(역 슬래시 기호)를 이용하세요! 뭐.. 코딩 좀 해봤다~ 하시는 분들은 가끔 파라미터가 길어져서 쓰실 때가 있었을 겁니다!

그럼 이제 C언어를 이용한 커널을 만들러 꾜!!