[Device Driver] 1편. hello_kernel 모듈 만들기

나우히즈·2025년 12월 12일

안녕하세요.

이 게시글을 시작으로 디바이스 드라이버 + Yocto 프로젝트를 진행해보려 합니다.
기본적인 커널 메시지를 찍는 모듈을 만들어보는 것부터 시작하여, 카메라 디바이스 드라이버를 만들고 만들어진 디바이스 드라이버를 레이어로 올려 yocto를 통해 이미지를 빌드해보기까지 해보겠습니다.


사전 작업

저는 raspberryPi4(이하 라파) 보드에 프로젝트를 진행할 예정이나, 현재 맥북과 라파 간 네트워크 연결에 어려움이 있어 가상머신을 이용해 간단한 커널 모듈을 만들어보도록 하겠습니다.

진행 사항

1️⃣ Ubuntu 24.04.3 ARM64 다운로드 완료
2️⃣ UTM 설치 (DMG 버전)
3️⃣ VM 생성 → Ubuntu 설치
4️⃣ apt install 을 통해 커널 모듈 빌드환경 세팅

여기서 UTM 은 기존에 많이 사용하던 VirtualBox와 유사한 하이퍼바이저 프로그램입니다.
맥북 M1에 최적화된 가상머신이 UTM 이라고 하여, 해당 프로그램을 설치 후 우분투 이미지로 빌드해보았습니다.

이후 4단계에서는 아래 커맨드를 실행하여 커널 빌드 환경을 설치합니다.

sudo apt update
sudo apt install -y build-essential linux-headers-$(uname -r)

여기서 $(uname -r) 을 통해 현재 실행중인 커널의 버전문자열을 얻는다.

나는 실행 결과 아래와 같이 나왔는데

 6.8.0-90-generic
│ │   │    └─ 커널 flavour (generic)
│ │   └───── Ubuntu patch level
│ └───────── minor
└─────────── major

이와 같은 구성으로 버전 형식을 표기한다고 함.

build-essential : gcc, g++, make, libc-dev 등 기본적인 빌드 유틸리티
linux-headers-6.8.0-90-generic : 커널 헤더파일(<linux/kernel.h> 등), 커널 Kbuild 시스템 을 포함함.

이를 통해 우리는 아래 사항들을 진행할 수 있게된다.

커널 모듈(.ko) 빌드
insmod, rmmod
char / i2c / platform driver
대부분의 디바이스 드라이버 개발

현재는 커널 모듈에 대한 빌드를 진행해볼 것이기에 이정도로 하고,
실제 커널을 전체 재빌드하거나 커널 설정 변경(menuconfig), 커널 패치를 적용하려면 별도의 설치가 필요함.(추후 진행해보도록 하자)

/lib/modules 는 “커널이 사용할 수 있는 모든 모듈을, 커널 버전별로 정리해 둔 저장소”다.
그리고 이 디렉토리는 커널 버전과 1:1로 묶인다.

이와 같이 커널 버전 별로 사용 가능 모듈이 커널 버전에 맞게 저장된다.

그러면 사전 작업은 모두 완료된다.

정리

  • 커널 모듈 빌드 진행을 위한 사전 작업으로 커널 헤더 및 커널 빌드에 필요한 세팅을 잊지말고 해야한다.
  • /lib/modules 에 커널이 사용할 수 있는 모든 모듈이 정리된다.

hello kernel을 커널 메시지로 찍어보자

hello.c 를 생성해보자.

커널 모듈 소스코드 작성

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

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello kernel module loaded!\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye kernel module unloaded!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("sihwan");
MODULE_DESCRIPTION("First kernel module on UTM");

이와 같이 모듈이 커널에 등록될 때(init) 사용될 함수와 해제될 때(exit) 사용될 함수를 명시해준다.
-> hello_init(), hello_exit() 함수
이후 module_init(hello_init), module_exit(hello_exit) 으로 모듈 동작에 호출될 함수들을 연결짓는다.

여기서 static 을 사용하여 함수선언하는 이유는

커널 모듈에서 static은
“심볼을 최소화해서 커널 전체를 오염시키지 않기 위한 안전장치”다.

우리가 일반적으로 static을 사용하는 이유와 동일하다. static이 붙은 함수는 해당 소스파일 내에서만 영향을 끼칠 수 있다. 만약 외부에 동일한 함수명의 파일이 있게 된다면 충돌문제가 발생할 수 있고, 특히 커널은 충돌에 민감하다. static을 명시하여 타 모듈 파일의 이름과 겹치는 경우를 대비하고도록 하자.

이후 Makefile을 작성하자.

Makefile 작성

obj-m += hello.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

여기서 obj-m 는 “이 오브젝트를 커널 모듈(.ko) 로 빌드해라”는 Kbuild 지시자다.

이전에 makefile 설정 세팅(menuconfig)를 진행할 때, 세 가지 옵션이 있었음.

obj-y -> 커널에 내장(built-in)
obj-m -> 모듈(.ko) 로 빌드
obj-n -> 빌드하지 않음

make 명령을 통해 실행해보게 되면 소스코드가 빌드되어 ko 파일(모듈)이 생성된다.

여기 all: 명령으로 적힌 라인을 해설하자면,
-C 옵션은 이후 인자로 오는 경로로 이동하여 메이크파일을 실행한다는 의미이다.

1️⃣ /lib/modules/$(uname -r)/build
• 현재 실행 중인 커널의 빌드 시스템

2️⃣ make -C
• 커널 Makefile 실행

3️⃣ M=$(PWD)
• “외부 모듈 소스는 여기 있다”

4️⃣ modules
• 커널에게 말함: “내장 커널 말고, 외부 모듈만 빌드해라”

즉, /lib/modules/$(uname -r)/build 디렉토리로 이동하여 커널 메이크파일을 실행하는데, 우리가 직접 만든 외부 모듈 소스코드의 위치를 M=$(PWD)로 기입한다. 추가로 modules를 기입하여 외부 모듈 빌드만 되도록 동작시키는 것이다.

“커널 기준으로, 내 코드만 빌드해라” 는 의미이다.

실행 결과

그렇게 생성한 메이크파일을 통해 빌드하면,

이를 insmod hello.ko 를 하여 모듈을 커널에 올리게되면,
dmesg | tail 로 확인이 가능하다.

앞서 테스트를 해보아서 커널 메시지가 남아있지만 맨 마지막에 hello kernel 이 떠있음을 볼 수 있다.

rmmod hello 를 하고나서 커널 메시지를 확인해보면,

이처럼 커널 모듈이 해제됨과 동시에 등록해둔 printk 함수로 커널에 메시지가 박힌다.


정리

  • 커널 버전에 맞게 커널 모듈 빌드가 필요하다.
  • insmod, rmmod 를 통해 모듈을 커널에 올렸다, 내렸다 가능하다. (별도의 재부팅, 재빌드 없이)
  • 커널 빌드를 위해 Makefile에서는 Kbuild라는 시스템을 활용한다.

0개의 댓글