Dev/Linux

Linux Kernel Module 개발 입문 - Hello World 부터 시작하기

onepaperhoon 2026. 3. 28. 20:19

1. 커널 모듈이란?

Linux 커널은 하나의 거대한 프로그램입니다. 그런데 모든 드라이버를 커널에 정적으로 포함시키면 커널 크기가 엄청나게 커지고, 새 드라이버를 추가할 때마다 커널을 다시 컴파일해야 합니다.

이 문제를 해결하는 것이 Kernel Module(커널 모듈) 입니다.

커널 모듈 = 실행 중인 커널에 동적으로 추가하거나 제거할 수 있는 코드 조각

USB를 꽂으면 드라이버가 자동으로 로드되고, 뽑으면 언로드되는 것이 바로 커널 모듈 덕분입니다.

Linux 커널 (실행 중)
┌─────────────────────────────────────┐
│  Core Kernel                     │
│  ┌──────────┐  ┌──────────┐        │
│  │ Module A │  │ Module B │  ← 동적 로드/언로드 가능
│  └──────────┘  └──────────┘        │
│  ┌──────────┐                     │
│  │ Module C │  ← 우리가 만들 것    │
│  └──────────┘                     │
└─────────────────────────────────────┘

커널 모듈로 만들 수 있는 것들:

  • 디바이스 드라이버 (GPIO, I2C, SPI, UART 등)
  • 파일시스템 (ext4, FAT 등)
  • 네트워크 프로토콜
  • 시스템 콜 후킹 (보안, 모니터링)

2. 유저스페이스 vs 커널스페이스

커널 모듈을 이해하려면 이 개념이 반드시 필요합니다.

Linux는 메모리를 두 영역으로 엄격하게 분리합니다.

메모리 공간

높은 주소 ┌─────────────────────┐
          │                  │
          │   Kernel Space   │ ← 커널, 드라이버, 모듈
          │   (보호된 영역)    │    직접 하드웨어 접근 가능
          │                  │
          ├────────────────────┤ ← 경계선 (ARM: 0xC0000000)
          │                  │
          │   User Space     │ ← 일반 앱 (bash, python 등)
          │   (제한된 영역)    │    하드웨어 직접 접근 불가
          │                  │
낮은 주소 └─────────────────────┘

유저스페이스

  • 일반 애플리케이션이 실행되는 영역
  • 하드웨어에 직접 접근 불가
  • 잘못된 메모리 접근 → 프로세스만 죽음 (시스템은 안전)
  • printf(), malloc(), read() 같은 libc 함수 사용 가능

커널스페이스

  • 커널과 드라이버가 실행되는 영역
  • 하드웨어에 직접 접근 가능
  • 잘못된 메모리 접근 → 커널 패닉, 시스템 다운
  • libc 사용 불가 → printk(), kmalloc() 같은 커널 API 사용

⚠️ 커널 모듈은 커널스페이스에서 실행됩니다. printf() 대신 printk(), malloc() 대신 kmalloc()을 써야 합니다. 잘못 짜면 시스템이 죽습니다.

유저스페이스에서 하드웨어에 접근하려면?

User App
   │
   │  시스템 콜 (read, write, ioctl ...)
   ▼
Kernel (드라이버)
   │
   │  레지스터 접근
   ▼
Hardware

유저 앱은 시스템 콜을 통해 커널에 요청하고, 커널(드라이버)이 대신 하드웨어를 제어합니다.


3. 커널 모듈 기본 구조

커널 모듈의 최소 구조는 딱 두 함수입니다.

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

/* 모듈 로드 시 실행 — insmod 할 때 */
static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, Kernel!\n");
    return 0;  /* 0 반환 = 성공, 음수 반환 = 실패 */
}

/* 모듈 언로드 시 실행 — rmmod 할 때 */
static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, Kernel!\n");
}

module_init(hello_init);  /* init 함수 등록 */
module_exit(hello_exit);  /* exit 함수 등록 */

/* 모듈 메타데이터 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("OnePaperHoon");
MODULE_DESCRIPTION("Hello Kernel Module");

각 요소 설명

__init / __exit 매크로

static int __init hello_init(void)

__init은 이 함수가 초기화 시에만 쓰인다는 힌트입니다. 모듈 로드 후 해당 함수 코드를 메모리에서 해제해 공간을 절약합니다.

printk() vs printf()

printk(KERN_INFO "Hello, Kernel!\n");

커널에는 printf()가 없습니다. 대신 printk()를 씁니다. 앞의 KERN_INFO는 로그 레벨입니다.

로그 레벨의미
KERN_EMERG 시스템 불능
KERN_ERR 에러
KERN_WARNING 경고
KERN_INFO 일반 정보
KERN_DEBUG 디버그

MODULE_LICENSE("GPL")

반드시 선언해야 합니다. GPL이 아닌 라이선스를 선언하면 일부 커널 심볼을 사용할 수 없고, 로드 시 "tainted kernel" 경고가 뜹니다.


4. Makefile 작성법

커널 모듈은 일반 C 프로그램과 빌드 방식이 다릅니다. 커널 빌드 시스템(Kbuild)을 활용합니다.

# Makefile

obj-m += hello.o          # 빌드할 모듈 이름 (.o 확장자)

# Raspberry Pi에서 직접 빌드할 경우
KDIR := /lib/modules/$(shell uname -r)/build

all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

Makefile 핵심 설명

obj-m

obj-m은 "이 파일을 모듈로 빌드하라"는 Kbuild 문법입니다. obj-y는 커널에 정적으로 포함시키는 것이고, obj-m은 동적 모듈(.ko 파일)로 빌드합니다.

$(KDIR)

현재 실행 중인 커널의 빌드 디렉토리입니다. uname -r로 커널 버전을 가져와 경로를 자동으로 맞춥니다. 크로스 컴파일(다른 머신에서 빌드)할 때는 이 경로를 대상 보드의 커널 소스로 바꿉니다.

-C $(KDIR) M=$(PWD)

커널 빌드 시스템 디렉토리로 이동해서(-C), 현재 디렉토리(M=$(PWD))의 모듈을 빌드하라는 뜻입니다.


5. 빌드 및 실행 — insmod / rmmod / lsmod

빌드

$ make

성공하면 hello.ko 파일이 생성됩니다. .ko가 커널 오브젝트(Kernel Object), 즉 모듈 파일입니다.

$ ls -l
-rw-r--r-- hello.c
-rw-r--r-- Makefile
-rw-r--r-- hello.ko   ← 이게 생겼으면 성공

모듈 로드 — insmod

$ sudo insmod hello.ko

insmod는 insert module의 약자입니다. 커널에 모듈을 삽입합니다.

로드 확인 — lsmod

$ lsmod | grep hello
hello                  16384  0

lsmod는 현재 로드된 모듈 목록을 보여줍니다. grep으로 필터링해서 확인합니다.

printk 출력 확인 — dmesg

$ dmesg | tail -5
[  123.456789] Hello, Kernel!

printk() 출력은 터미널에 바로 보이지 않습니다. 커널 로그 버퍼에 저장되고, dmesg 명령으로 확인합니다.

모듈 언로드 — rmmod

$ sudo rmmod hello
$ dmesg | tail -5
[  123.456789] Hello, Kernel!
[  145.678901] Goodbye, Kernel!

rmmod는 remove module의 약자입니다.

명령어 정리

명령어기능
insmod hello.ko 모듈 로드
rmmod hello 모듈 언로드
lsmod 로드된 모듈 목록
modinfo hello.ko 모듈 정보 출력
dmesg 커널 로그 확인

6. 실습 — Raspberry Pi에서 Hello, Kernel!

환경 준비

# 커널 헤더 설치 (Raspberry Pi OS 기준)
$ sudo apt update
$ sudo apt install kernel-headers-$(uname -r)

# 설치 확인
$ ls /lib/modules/$(uname -r)/build
```

### 파일 구성
```
hello_module/
├── hello.c
└── Makefile

hello.c

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

static int __init hello_init(void)
{
    printk(KERN_INFO "[hello] module loaded\n");
    printk(KERN_INFO "[hello] kernel version: %s\n", UTS_RELEASE);
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "[hello] module unloaded\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("OnePaperHoon");
MODULE_DESCRIPTION("Hello Kernel Module for Raspberry Pi");
MODULE_VERSION("1.0");

빌드 및 실행

# 빌드
$ make
make -C /lib/modules/6.1.21+/build M=/home/pi/hello_module modules
  CC [M]  /home/pi/hello_module/hello.o
  MODPOST /home/pi/hello_module/Module.symvers
  CC [M]  /home/pi/hello_module/hello.mod.o
  LD [M]  /home/pi/hello_module/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-6.1.21+'

# 로드
$ sudo insmod hello.ko

# 확인
$ dmesg | tail -3
[ 1234.567] [hello] module loaded
[ 1234.568] [hello] kernel version: 6.1.21+

# 언로드
$ sudo rmmod hello
$ dmesg | tail -1
[ 1289.012] [hello] module unloaded

7. 자주 하는 실수 모음

❌ 실수 1: printf() 사용

/* 나쁜 예 — 컴파일 에러 */
printf("Hello\n");

/* 좋은 예 */
printk(KERN_INFO "Hello\n");

커널 모듈에서 libc 함수는 사용할 수 없습니다.

❌ 실수 2: init에서 0이 아닌 값 반환

static int __init hello_init(void)
{
    /* 초기화 실패 상황 */
    return 1;  /* 나쁜 예 — 양수 반환 */
    return -ENOMEM;  /* 좋은 예 — errno 음수값 반환 */
}

init 함수는 실패 시 반드시 음수 errno 값을 반환해야 합니다. 양수를 반환하면 예상치 못한 동작이 생길 수 있습니다.

❌ 실수 3: 커널 버전 불일치

$ sudo insmod hello.ko
insmod: ERROR: could not insert module hello.ko: Invalid module format

모듈은 빌드한 커널 버전과 로드할 커널 버전이 정확히 일치해야 합니다. 커널 업데이트 후에는 반드시 재빌드하세요.

# 현재 커널 버전 확인
$ uname -r
6.1.21+

# 모듈이 빌드된 커널 버전 확인
$ modinfo hello.ko | grep vermagic
vermagic: 6.1.21+ SMP preempt mod_unload modversions ARMv7

8. 마치며

커널 모듈은 Linux 드라이버 개발의 시작점입니다.

커널 모듈은 커널스페이스에서 실행된다 — 실수하면 시스템이 죽는다.
printf 대신 printk, malloc 대신 kmalloc.
빌드한 커널 버전과 로드할 커널 버전이 일치해야 한다.

Hello World 수준이지만, 이 구조를 이해하면 GPIO 제어, I2C 드라이버, character device 구현까지 자연스럽게 이어집니다.