ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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

    '프로젝트 > 운영체제 만들기' 카테고리의 다른 글

    C 런타임  (2) 2023.06.04
    크로스 컴파일[작성중]  (0) 2023.06.03
    GIT 명령어  (1) 2023.06.01
    메모리 맵  (0) 2023.06.01
    커널 이미지  (0) 2023.05.31
Designed by Tistory.