-
GCC[작성중]프로젝트/운영체제 만들기 2023. 6. 2. 00:39
글의 참고
- https://gcc.gnu.org/onlinedocs/gcc/Standards.html
- https://gcc.gnu.org/onlinedocs/cpp/Search-Path.html
글의 전제
- 내가 글을 쓰다가 궁금한 점은 파란색 볼드체로 표현했다. 나도 모르기 때문에 나중에 알아봐야 할 내용이라는 뜻이다.
- 밑줄로 작성된 글은 좀 더 긴 설명이 필요해서 친 것이다. 그러므로, 밑 줄 처친 글이 이해가 안간다면 링크를 따라서 관련 내용을 공부하자.
- `글의 참조`에서 빨간색 볼드체로 체크된 링크는 이 글을 작성하면 가장 많이 참조한 링크다.
- `운영체제 만들기` 파트에서 퍼온 모든 참조 글들과 그림은 반드시 `이 글과 그림을 소스 코드로 어떻게 구현을 해야할까` 라는 생각으로 정말 심도있게 잠시 멈춰서 생각해봐야 실력이 발전한다.
글의 내용
: C 옵션
" C 옵션은 컴파일 및 어셈블 과정만 진행하고 링킹은 진행하지 않는 옵션이다. 즉, 목적 파일만 만들고 실행 파일은 만들지 않는다. 이 옵션을 자주 깜빡하곤 하는데, OS 개발시에 생각보다 중요한 옵션이다. 이 옵션과 관련된 에러를 참고하자.
: O 옵션
" 이 옵션을 사용하면 최종 실행 파일을 만든다. 주의할게 있다. 이 옵션만 단독으로 사용하면, 컴파일 및 어셈블을 진행하고 최종 링킹작업까지 실행 파일을 만든다. 이 옵션과 관련된 에러로 위의 `-c` 옵션과 관련된 에러를 참고하자.
: -fPIC
"
: Include 옵션
" `gcc -I${INC_PATH}` 명령어는 헤더 파일의 폴더를 명시해서 다른 파일들에 인클루드 길이를 줄여준다. 예를 들어, 아래와 같은 예시를 보자. 참고로 저건 영어로 `L(엘)`이 아니다. 대문자 `I(아이)`다.
1" /home/yohda/workspace/boot.c
2" /home/yohda/workspace/include/error/error.h: boot.c 파일은 아래와 같다.
#include "error/error.h" int main() { return 0; }
: 인클루드 패스로 저걸 인식하려면 gcc 옵션을 다음과 같이 작성하면 된다.
gcc -I/home/yohda/workspace/include
: 많은 예시에서 상대경로로 작성해도 정상적으로 인식하는거 같은데, 내 경우는 제대로 인식을 못했다. 그래서 절대 경로로 작성하니 정상적으로 인식이 잘 되었다. 만약 여러 개의 헤더 파일 패스들이 포함되어야 할 경우 `-I` 옵션을 여러 번 사용하면 된다.
gcc -I/home/yohda/workspace/include -I/home/yohda/workspace/block -I/home/yohda/workspace/fs
: gcc의 인클루드 관련 몇 가지 알고 가면 좋을 내용이 있다. 인클루드에서 쌍따운표가 아닌 괄호로 닫는 헤더 파일들은 해당 시스템의 기본 헤더 파일이다. 이 헤더 파일들의 경로는 모든 플랫폼마다 다르지만, gcc의 `-v` 옵션으로 쉽게 알 수 있다. gcc에서 `-v` 옵션을 사용하면 gcc가 찾는 헤더 파일들에 대한 디테일한 위치들을 모두 출력한다. 그래서, 만약 `-I` 옵션을 사용했는데, 여전히 패스가 잘 인식되지 않은 거 같으면 `-v` 옵션을 사용해 보면 gcc가 어떻게 헤더 파일들을 찾고 있는지 알 수 있다. 제대로 인식되지 않으면, 아래와 같은 문구가 나올 가능성이 있다.
ignoring nonexistent directory " ... "
: 즉, 내가 `-I` 옵션을 통해 작성한 헤더 파일 패스가 GCC 한테는 존재하지 않는 파일이라는 것이다.
: 그리고 `-nostdinc`라는 옵션이 있다. 이름에서 알 수 있다시피, 표준 헤더 파일을 사용하지 않겠다는 내용이다. 베어 메탈 프로그래밍은 한다면 표준 헤더 파일을 거의 사용할 일이 없기 때문에 기본 옵션으로 사용한다.
: M 옵션
"
: MM 옵션
" GCC는 소스 파일의 의존성을 자동으로 인식해서 빌드되는 소스 파일들에 대한 의존성 파일을 만들어준다. `-M` 옵션과는 다르게, 시스템 관련 헤더 파일들은 생략해서 만들어준다.
: Wall 옵션
" 컴파일시 나오는 모든 경고 문구들도 출력한다.
- Freestanding 옵션
: `freestanding` 옵션은 C 언어 표준과 관련이 있는 옵션이다. C 표준에서는 프로그램이 실행되는 2가지 실행 환경을 정의한다.
1" hosted
2" freestanding: `freestanding` 환경에서는 표준 라이브러리가 존재하지 않는다. 프로그램의 엔트리 포인트도 `main`일 필요가 없다. 컴파일러에게 `-freestanding` 옵션을 주었다는 것은 표준 라이브러리를 사용하지 않겠다고 말하는 셈이다. OS 커널이 FREESTANDING 환경에서 동작하는 프로그램의 좋은 예시다. 그리고 운영 체제가 제공하는 기능들을 사용하는 프로그램이 동작하는 환경이 HOSTED 환경이라고 한다.
: FREESTANDING 환경이 프로그램에는 지켜야 하는 규칙이 있다. 이 프로그램들은 기본적으로 HOSTED 환경에서 실행되는 프로그램을 위해서 몇 가지 라이브러리를 제공해야 한다.
" float.h, iso646.h, limits.h, stdalign.h, stdarg.h, stdbool.h, stddef.h, stdint.h, stdnoreturn.h
: 위의 파일들은 오직 `typedef`와 `#define`으로만 구성되어 있기 때문에, C 파일이 하나도 필요없다.
: GCC는 HOSTED 환경과 FREESTANDING 환경에 컴파일을 모두 지원한다. 기본적으로 HOSTED 환경 컴파일을 진행한다. GCC가 HOSTED 환경으로 컴파일할 경우, 컴파일되는 프로그램이 main, printf, scanf 등의 함수를 사용하면 이 함수들을 C 표준(ISO C)에서 정의한 함수라고 가정하여 컴파일한다. 즉, 이미 표준 함수로 정의된 함수라고 가정한다. 그러나 GCC가 FREESTANDING 환경으로 컴파일할 경우(`-freestanding`), 컴파일되는 프로그램에서 사용하는 main, printf, scanf 등의 함수들은 C 표준에서 정의한 함수라고 가정하지 않는다. 즉, FREESTANDING 환경의 프로그램은 C 표준 함수 이름만 가져다 쓸 뿐이지, 기능은 나만의 OS에 맞는 커스텀 함수들이 되는 것이다.
: 만약, OS 개발시에 `-freestanding` 옵션을 실수로 깜빡하고 C 표준 라이브러리 함수들과 이름이 겹치는 함수들을 사용하면 시스템 C 표준 라이브러리 함수들을 가져와서 컴파일하게 된다.
- __attribute__((packed))
: 일단 이 변수는 C 표준 기능이 아니다. GCC 확장 기능이다. 이 변수가 구조체 선언 끝에 선언되면, 해당 구조체는 가능한한 가장 작은 정렬을 하게 된다. 즉, 1바이트 정렬을 한다는 소리다. 다른 말로 패딩을 추가하지 말라는 뜻이다. 아래의 구조체는 원래라면 8바이트겠지만, __attribute__((packed)) 때문에 5바이트가 된다.
struct file { char a; int b; } __attribute__((packed));
: 이 속성은 비트 단위도 지원을 한다.
struct t1 // 7 bytes { int a:12; // 0:11 int b; // 16:47 int c:4; // 48:51 }__attribute__((packed));
: 위의 예시에서 일반적인 데이터 타입인 `int b`는, 즉, 비트 필드로 선언하지 않은 일반적인 데이터 형은 바이트 단위로 정렬이 된다. 그래서 위의 예시를 보면 `int a:12`는 뒤쪽에 4비트 패딩이 생긴다. 왜냐면, `int a:12`뒤에 `int b`가 선언되기 때문이다. 이러한 패딩이 발생하지 않게 하고 싶다면, `int c:4`를 `int a:12` 뒤쪽에 배치하면 패딩은 발생하지 않는다. 이러한 접근은 퍼포먼스 면에서 좋지 않기 때문에 사용하지 않는 것이 좋다.
- __attribute__((aligned(N)))
: 이 속성도 C 표준 기능이 아닌, GCC 확장 기능이다. 이 속성을 구조체 및 변수에 사용하면 해당 구조체 및 변수의 주소는 N에 대하여 정렬된다. 이 말은 해당 주소가 N으로 나누어 떨어진다는 뜻이다. 주소를 N으로 나누어 떨어지게 하기 위해 기본적으로 패딩을 추가한다.
struct data { int a __attribute__( ( aligned ( 8 ) ) ) ; char ch __attribute__( ( aligned ( 1 ) ) ) ; float s __attribute__( ( aligned ( 4 ) ) ) ; };
: 위와 같이 하면 어떻게 될까?
Address Member ------- ------- 2280712 a [address aligned on multiple of 8] 2280713 a 2280714 a 2280715 a 2280716 ch [address aligned on multiple of 1] 2280717 (unused) 2280718 (unused) 2280719 (unused) 2280720 s [address aligned on multiple of 4] 2280721 s 2280722 s 2280723 s
: 변수 s의 주소가 4바이트 나누어 떨어지게 하기 위해 ch 뒤에 3바이트 패딩이 추가된다. 만약, s가 2바이트로 정렬되면 어떻게 될까? 아래와 같을 것이다.
struct data { int a __attribute__((aligned(8))) ; char ch __attribute__((aligned(1))) ; float s __attribute__((aligned(2))) ; }; Address Member ------- ------- 2280712 a [address aligned on multiple of 8] 2280713 a 2280714 a 2280715 a 2280716 ch [address aligned on multiple of 1] 2280717 (unused) 2280718 s [address aligned on multiple of 4] 2280719 s 2280720 s 2280721 s