본문 바로가기

스마트팩토리?

[라즈베리파이] vscode로 커널 개발 시 intellisence 에러 없애기

해결 방법

vscode 설정 중 C / C++ intellisence와 관련된 설정 파일인 "c_cpp_properties.json" 파일의 내용을 다음과 같이 수정한다.

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/lib/modules/6.6.51+rpt-rpi-2712/source/include",
                "/usr/src/linux-headers-6.6.51+rpt-rpi-2712/arch/arm64/include/generated",
                "/usr/src/linux-headers-6.6.51+rpt-rpi-2712/include",
                "/usr/src/linux-headers-6.6.51+rpt-common-rpi/arch/arm64/include"
            ],
            "defines": [ "__KERNEL__", "MODULE" ],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c17",
            "cppStandard": "gnu++17",
            "intelliSenseMode": "linux-gcc-arm64"
        }
    ],
    "version": 4
}

설정은 ctrl + shift + P ( 커맨드 팔레트 ) 활성화 후 "C/C++: Edit Configurations"을 통해 변경할 수 있다.

 


최근 라즈베리파이를 활용해 리눅스 기반 커널 드라이버를 공부하고 있다. 라즈베리파이 5 공식 문서에 따르면 Arm Cortex A-76 쿼드코어 CPU를 사용하고 있기 때문에 디바이스 드라이버를 개발해보면서 임베디드를 개발하기 좋을 것이라 생각했다. 실제로 코드를 작성했을 때도 모듈은 정상적으로 잘 동작했다.

#include <linux/module.h>
#include <linux/init.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("blaxsior GNU/Linux");
MODULE_DESCRIPTION("blaxsiors linux driver hello world");

/**
 * @brief called when module is loaded into kernel
 */
static int __init ModuleInit(void) {
  printk("Hello, Kernel World!\n"); // cannot printf => no commandline
  return 0; 
}

/**
 * @brief called when module is removed from kernel
 */
static void __exit ModuleExit(void) {
  printk("Bye, Kernel World...\n");
}

module_init(ModuleInit);
module_exit(ModuleExit);

moduleinit / exit 가 정상적으로 동작

문제는 다른 부분에서 발생했다. 나는 라즈베리 파이를 vscode + remote ssh을 이용해 컴퓨터 환경에서 개발하고 있는데, vscode의 기본 C/C++ intellisence 설정으로는 linux/init.h 헤더 파일을 찾지 못한다. 이에 따라 linux.init.h 파일에 포함된 __init / __exit 매크로나 MODULE 설정을 정의하는 매크로 등을 코드 자동완성 없이 사용해야 하는 문제가 있다.

linux/init.h를 인식하지 못함

linux/init.h 인식하기 ( /usr vs /lib/modules )

linux/module.h 파일은 인식하면서 linux/init.h 파일은 인식하지 못하는 이유가 무엇일까? 이름은 동일하지만, "커널 모듈" 개발을 위한 linux/module.h를 인식하는 것이 아니기 때문이다.

linux/module.h 파일은 두 종류의 경로에서 찾아볼 수 있다. ( 참고 )

  • /usr/include: 표준 C 라이브러리를 컴파일할 때 사용되는 파일들을 포함 
  • /lib/modules/커널버전/source/include: 커널 모듈을 컴파일할 때 사용되는 파일들을 포함

 이때 C/C++ intellisence는 현재 프로젝트 폴더 경로와 표준 라이브러리가 포함된 /usr/include을 인식한다. 이 점을 고려하면, 위 코드에서 인식한 linux/module.h 파일은 우리가 원하는 커널 개발을 위한 헤더가 아니라는 것을 짐작할 수 있다. 실제로 /usr/include에는 linux/init.h 파일이 없다.

init을 이름에 포함한 파일이 존재하지 않음

한편 /lib/modules/커널버전/source/include 폴더에는 커널 개발에 사용될 수 있는 일부 헤더 / 소스 파일 및 우리가 원하는 linux/init.h 파일이 포함되어 있다.

커널 모듈 개발과 관련된 소스 파일들
init.h 파일이 포함되어 있음

이를 통해, 에러 메시지가 발생한 이유가 "커널 모듈" 관련 파일을 인식하지 못하기 때문이라는 것을 알게되었다. 우리가 원하는 대로 커널 모듈 관련 소스 파일을 인식할 수 있도록 vscode 설정에 추가해보자. 설정은 ctrl + shift + P ( 커맨드 팔레트 ) 활성화 후 "C/C++: Edit Configurations"을 통해 변경할 수 있다.

초기 설정은 대략 다음과 같다.

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**"
           ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c17",
            "cppStandard": "gnu++17",
            "intelliSenseMode": "linux-gcc-arm64"
        }
    ],
    "version": 4
}

includePath에 /lib/modules/커널버전/source/include 경로를 추가하자. 현재 사용 중인 커널 버전은 uname -r 커맨드 명령으로 알아낼 수 있다.

현재 커널 버전을 알아냄

"includePath": [
    "${workspaceFolder}/**",
    "/lib/modules/6.6.51+rpt-rpi-2712/source/include"
],

커널 버전 경로를 추가하면 일단 linux/init.h 파일을 인식할 수 있다. 하지만 두 종류의 추가적인 예외 메시지가 발생한다.

  • cannot open source file "~.h": linux/module.h에서 의존하는 파일을 인식하지 못함
  • function returning array is not allowed: 배열을 반환하는 함수는 허용되지 않음

 

 코드는 정상적으로 컴파일 되므로, 위 에러는 코드가 아니라 인텔리센스의 문제다. 두 문제를 해결해보자.

인텔리센스에게 "커널 모듈" 프로젝트로 인식시키기

https://www.linuxquestions.org/questions/linux-newbie-8/what-is-the-meaning-of-using-__kernel__-and-module-macro-838753/#post4130967

https://stackoverflow.com/a/76551810

인텔리센스는 현재 프로젝트가 일반적인 C/C++ 프로그램을 개발한다고 이해한다. 이때 커널 모듈을 개발하기 위해서는 2 종류의 매크로가 선언되어 있어야 한다.

  • __KERNEL__: 커널과 유저 공간 코드를 구분하는 매크로. 활성화 시 커널 내에서만 사용되는 특별한 함수나 구조체를 사용할 수 있게 된다.
  • MODULE: 커널 모듈 컴파일과 관련된 매크로. 활성화해야 모듈 관련 매크로 등을 정상적으로 인식한다.

위 두 매크로를 c_cpp_properties.json의 defines에 추가하면, 인텔리센스가 관련 코드를 인식해 오류를 던지지 않는다.

 "defines": ["__KERNEL__", "MODULE"],

배열 오류가 발생하지 않음.

나머지 헤더 파일 경로를 추가적으로 등록하기

/lib/modules/커널버전 경로는 커널 모듈을 개발하기 위한 코드를 포함하지만, 모든 헤더 파일까지 모두 포함하지는 않는다. 커널을 개발하지도 않는데 굳이 커널 관련 헤더 파일을 가지고 있을 필요가 없기 때문에 둘을 분리하여 관리하는 것으로 알고 있다. 커널 개발을 위한 헤더 파일은 다음 명령으로 다운받을 수 있다.

sudo apt install -y raspberrypi-kernel-headers

또는, 커널 이름을 직접적으로 명시해서 다운받을 수도 있다.

sudo apt install -y linux-headers-6.6.51+rpt-rpi-2712

위 헤더 파일들은 커널 모듈을 개발하는데 사용된다. 따라서 인텔리센스가 정상적으로 동작하려면, 위 헤더들을 인텔리센스가 인식할 수 있도록 등록해줘야 한다.

"includePath": [
    "${workspaceFolder}/**",
    "/lib/modules/6.6.51+rpt-rpi-2712/source/include",
    "/usr/src/linux-headers-6.6.51+rpt-rpi-2712/include",
    "/usr/src/linux-headers-6.6.51+rpt-rpi-2712/arch/arm64/include/generated",
    "/usr/src/linux-headers-6.6.51+rpt-common-rpi/arch/arm64/include"
],

위 코드에는 3개의 경로가 추가적으로 등록되었다. 각각에 대해 설명해본다.

/usr/src/linux-headers-6.6.51+rpt-rpi-2712/include

일반적인 추가 헤더가 포함되어 있는 폴더다.

/usr/src/linux-headers-6.6.51+rpt-rpi-2712/arch/arm64/include/generated

내가 사용하는 커널 헤더 기준으로 32비트 / 64비트 아키텍처 중 무엇을 사용하는지에 따라 다른 arm/~.h 파일을 이용한다. 의도적으로 바꾸지 않는 이상 라즈베리파이 5는 ARM Cortex-76 64bit 아키텍처를 이용하므로, arm64 이하 경로를 연결하여 정상적인 arm 패키지를 인식할 수 있도록 한다.

/usr/src/linux-headers-6.6.51+rpt-common-rpi/arch/arm64/include

라즈베리파이의 리눅스 커널 헤더는 rpt-common-rpi에 있는 헤더 파일을 포함한다. 개발 단계에서도 rpt-common-rpi에 저장된 헤더 파일이 필요하므로, 해당 파일 경로를 등록한다.

위와 같이 경로를 등록하면, 에러 없이 vscode C/C++ 인텔리센스를 이용할 수 있다. 만약 추가적인 예외가 발생한다면 find 명령으로 /lib/modules 와 /usr 경로에서 파일을 검색해 추가적으로 경로를 등록하자.

에러가 발생하지 않는 모습

후기

처음에는 관련된 정보를 찾아봐도 완벽하게 들어 맞는 경우가 없어서 어려움을 많이 겪었다. vscode 자체의 버그는 아닐까 고민해보기도 했고, 인텔리센스가 없다고 치고 개발할까 생각하기도 했다.

하지만, 인텔리센스를 정상적으로 동작하게 하는 과정에서 많은 내용을 알 수 있었다. 파일 레벨에서 init.h 파일을 검색하고 관련 정보를 찾아보면서 /usr 폴더의 linux 관련 헤더(C 라이브러리)와 /lib/modules 의 linux 관련 헤더(커널 모듈 개발)의 차이점이나 __KERNEL__, MODULE 같은 매크로의 존재도 알 수 있었다.

헤더 파일의 위치를 찾는 과정에서 커널 모듈이 여러 경로에 있는 헤더 파일을 이용한다는 것을 알게된 점도 흥미로웠다. 특정 아키텍처에서 사용하는 파일은 arm64 폴더로 분리되어 있던 것이나, 커널 헤더 자체도 rpt-common-rpi 등의 패키지로 나뉘어져 있다는 것을 알게 되어 기존보다 라즈베리파이나 임베디드 시스템에 대한 이해도가 높아진 것 같다.

vscode로 라즈베리파이 디바이스 드라이버를 개발하는 누군가에게 도움이 될 수 있다면 좋겠다.