ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [리눅스] Linux kernel headers
    Linux/kernel 2023. 8. 3. 02:28

    글의 참고

    - https://docs.kernel.org/kbuild/modules.html

    - https://unix.stackexchange.com/questions/47330/what-exactly-are-linux-kernel-headers


    글의 전제

    - 밑줄로 작성된 글은 강조 표시를 의미한다.

    - 그림 출처는 항시 그림 아래에 표시했다.


    글의 내용

    : /lib/modules/$(shell uname -r)/build/ 폴더는 커널 헤더 및 커널 소스를 심볼릭 링크하는 폴더이다. 커널 헤더는 말 그대로 커널에서 사용하는 모든  헤더 파일의 집합체를 의미한다. 일반적으로 커널 헤더와 커널 소스는 `/usr/src/*`에 위치한다. 아래에서, `build/` 폴더가 `커널 헤더`를 가리키는 것을 확인할 수 있다. `/usr/src/*` 에 커널 소스와 커널 헤더 모두 존재할 수 도 있다. 어떤 기준으로 `build/` 폴더가 커널 소스 폴더를 링크할지 헤더 폴더를 링크할 지는 모르겠지만, 일단 현재 동작하고 있는 커널 버전과 동일한 폴더를 링크한다.

    yohda@yohda-800G5M-800G5W:/lib/modules/5.4.0-150-generic$ ll
    total 5884
    drwxr-xr-x  6 root root    4096 Jul  1 23:49 ./
    drwxr-xr-x 14 root root    4096 Jul  1 23:49 ../
    lrwxrwxrwx  1 root root      40 May 20 00:23 build -> /usr/src/linux-headers-5.4.0-150-generic/
    drwxr-xr-x  2 root root    4096 May 20 00:23 initrd/
    drwxr-xr-x 17 root root    4096 Jun  2 12:11 kernel/
    drwxr-xr-x  2 root root    4096 Jul  1 23:49 misc/
    -rw-r--r--  1 root root 1409864 Jul  1 23:49 modules.alias
    -rw-r--r--  1 root root 1386822 Jul  1 23:49 modules.alias.bin
    -rw-r--r--  1 root root    8105 May 20 00:23 modules.builtin
    -rw-r--r--  1 root root   10257 Jul  1 23:49 modules.builtin.bin
    -rw-r--r--  1 root root   64088 May 20 00:23 modules.builtin.modinfo
    -rw-r--r--  1 root root  610863 Jul  1 23:49 modules.dep
    -rw-r--r--  1 root root  853494 Jul  1 23:49 modules.dep.bin
    -rw-r--r--  1 root root     330 Jul  1 23:49 modules.devname
    -rw-r--r--  1 root root  219908 May 20 00:23 modules.order
    -rw-r--r--  1 root root     947 Jul  1 23:49 modules.softdep
    -rw-r--r--  1 root root  637389 Jul  1 23:49 modules.symbols
    -rw-r--r--  1 root root  770284 Jul  1 23:49 modules.symbols.bin
    drwxr-xr-x  3 root root    4096 Jun  2 12:11 vdso/

    : 그런데 커널 헤더는 왜 필요할까? 라즈베리파이와 같은 오픈 소스 하드웨어 같은 칩들은 용량이 크지 않아 굳이 커널과 같이 무거운 소스 전체를 가지고 있는 것은 쓸데 없는 용량 낭비가 될 수 있다. 그런데 문제는 커널 소스가 없으면 빌드를 할 수 가 없다. 당연하지 않은가? 커널 API를 사용해서 드바이스 드라이버 모듈을 작성해야 하는데, 커널 소스가 없다는 소리는 API가 없다는 소리와 마찬 가지이니 말이다. 

     

    : 그래서 커널 헤더가 존재한다. 헤더에는 일반적으로 함수의 인터페이스가 작성되어 있다. 인터페이스가 뭐지? 인터페이스는 객체(커널)와 객체(외부 모듈)를 연결해준다. 결국, 커널 헤더는 모듈이 커널과 통신할 수 있게 하는 인터페이스 역할을 한다. 그리고, 모듈 빌드시에 컴파일 에러를 일으키지 않게 만들어준다. 컴파일 에러를 발생시키지 않는다는 것이 무슨 말일까? 컴파일러는 함수의 구현부는 궁금해 하지 않는 다는 소리이다. 함수의 정의부만 있으면 빌드는 정상적으로 돌아간다는 소리다. 그리고 외부 모듈이 커널에 동적으로 로드되면, 컴파일시에 사용했던 커널 헤더 인터페이스를 기준으로 현재 동작하고 있는 커널의 기능을 사용하게 되는 것이다. 

     

    : 그런데 주의점이 있다. 모듈이 커널 헤더를 기준으로 빌드가 되면 반드시 빌드시 사용했던 커널 헤더 버전과 현재 동작하고 있는 커널 버전이 같아야 한다.

    외부 모듈과 커널의 버전 확인

    : modinfo ${MODULE_NAME} 으로 모듈의 버전을 확인할 수 있고, `uname -a` 혹은 `uname -r`로 현재 동작하고 있는 커널의 버전을 알 수 있다.

     

    : 현재 동작하고 있는 커널이 5.4라고 하고, 커널 헤더가 4.4라고 해보자. 그렇면 외부 모듈이 빌드 될 때, /lib/modules/$(shell uname -r)/build/ 를 기준으로 빌드를 한다. 그런데 저 build/ 폴더가 커널 헤더를 심볼릭 링크 한다고 보자. 그렇면 해당 모듈은 커널의 4.4 버전으로 컴파일 된 것이다. 그런데, 모듈에서 사용하고 있는 함수의 파라미터나 리턴값들이 현재 동작하고 5.4에서 바뀌었다면 그건 대형참사다. 예를 들어, 아래의 함수를 보자.

    " 커널 4.4 - s32 yohda_send(const struct device *dev, u8 data)
    " 커널 5.4 - s32 yohda_send(const struct device *dev, u8 data, u8 reg)

     

    : 그래서, 이런 문제를 피하기 위해 리눅스 커널은 동적 로딩하려는 외부 모듈의 커널 버전이 현재 동작하고 있는 커널 버전과 다르면, 로딩 하지 않는다. 외부 모듈이 ELF로 빌드 되는데, 이 때, `.modinfo` 라는 섹션이 만들어진다. 이 섹션에 외부 모듈이 빌드될 때, 어떤 커널 버전으로 빌드가 되었는지 명시되어 있다. 

     

    : 커널 헤더는 하위 호환은 되지만, 상위 호환은 안된다. 예를 들어, 이전 버전 커널 헤더로 빌드된 프로그램이 새로운 커널에서 동작할 수는 있지만, 새로운 커널 헤더로 빌드된 프로그램이 이전 버전 커널에서는 동작할 수 없다.

    Kernel headers are backwards compatible, but not forwards compatible. This means that a program built against a C library using older kernel headers should run on a newer kernel (although it may not have access to new features), but a program built against newer kernel headers may not work on an older kernel.

    - 참고 : https://docs.kernel.org/kbuild/headers_install.html
Designed by Tistory.