Rust 커널 프로그래밍 가이드 — Ubuntu에서 Rust for Linux 빌드 환경 만들기

업스트림 소스를 --depth 1로 clone해 rust_minimal.o를 빌드하는 데는 성공했지만, 그 트리는 지금 부팅된 우분투 배포 커널과 버전이 다른 별개의 트리였다. 실제로 insmod까지 되려면 지금 실행 중인 커널 자체를 기준으로 빌드해야 한다. 그 과정에서 네 가지 장벽을 만났고, 이 글은 각 장벽의 원인과 해결, 그리고 최종적으로 rust_template.koinsmod/dmesg/rmmod까지 확인한 결과를 정리한다. 환경은 이전과 동일한 UBUNTU_TEST_HOST(Ubuntu 24.04.4 LTS, 커널 6.17.0-35-generic)다.

장벽증상원인해결
1. 심볼릭 링크rust/ 링크가 깨짐HWE 메타패키지 명명 버그로 링크가 기대하는 패키지명이 실제로 존재하지 않음링크를 실제 설치된 경로로 직접 재생성
2. rustc ABIrustavailable/E0514 실패rustup 배포 rustc는 버전 문자열이 같아도 배포판이 소스에서 직접 빌드한 rustc와 크레이트 메타데이터가 다름apt의 rustc-1.82 + rust-1.82-src 패키지로 교체
3. module! 매크로 APIparams 필드 미지원 에러배포 커널의 kernel crate는 2026-05-27 스냅샷이라 업스트림에 나중에 추가된 params 지원이 없음params 필드 제거 후 재작성
4. 컴파일러 불일치LLVM=1.mod.c 컴파일 실패커널 자체는 GCC 13으로 빌드됐는데 LLVM=1이 전체 툴체인을 clang으로 바꾸려 해 충돌LLVM=1 제거, GCC로 빌드

배포 커널의 Rust 지원 확인

먼저 지금 부팅된 커널 자체가 CONFIG_RUST로 빌드됐는지부터 확인한다.

$ uname -r
6.17.0-35-generic

$ grep -E "^CONFIG_RUST=|^CONFIG_RUST_IS_AVAILABLE=" /boot/config-$(uname -r)
CONFIG_RUST_IS_AVAILABLE=y
CONFIG_RUST=y

$ ls -la /lib/modules/$(uname -r)/build
/lib/modules/6.17.0-35-generic/build -> /usr/src/linux-headers-6.17.0-35-generic

CONFIG_RUST=y가 확인됐고, out-of-tree 모듈 빌드의 표준 진입점인 build 심볼릭 링크도 이미 linux-headers 패키지로 준비되어 있었다.

1단계 장벽 — 깨진 rust 심볼릭 링크

헤더 트리 안의 rust/는 별도 패키지를 가리키는 심볼릭 링크인데, linux-lib-rust-6.17.0-35-generic 패키지를 설치해도 링크가 깨져 있었다.

$ sudo apt-get install -y linux-lib-rust-6.17.0-35-generic
...
Setting up linux-lib-rust-6.17.0-35-generic (6.17.0-35.35~24.04.1) ...

$ ls -la /usr/src/linux-headers-6.17.0-35-generic/ | grep rust
lrwxrwxrwx 1 root root 49 rust -> ../linux-hwe-6.17-lib-rust-6.17.0-35-generic/rust

$ ls /usr/src/ | grep rust
linux-lib-rust-6.17.0-35-generic

링크가 기대하는 linux-hwe-6.17-lib-rust-6.17.0-35-generic 패키지는 apt에 존재하지 않는다(hwe-6.17- 접두사가 빠진 이름으로만 설치됨 — HWE 메타패키지 쪽 명명 버그로 보인다). 링크를 실제 경로로 직접 재생성했다.

$ sudo ln -sfn ../linux-lib-rust-6.17.0-35-generic/rust \
    /usr/src/linux-headers-6.17.0-35-generic/rust

$ ls /usr/src/linux-headers-6.17.0-35-generic/rust/ | head -3
bindings
bindings.o
build_error.o

2단계 장벽 — rustup stable로는 rustavailable부터 실패

링크를 고친 뒤 rustup stable(1.97.1)로 rustavailable을 돌리면, 커널의 커스텀 타깃 스펙 JSON을 최신 rustc가 다른 스키마로 해석해 깨진다.

error: error loading target specification: target-pointer-width:
invalid type: string "64", expected u16 at line 7 column 32

배포 커널의 rust/kernel.orustc 1.82.0 (f6e511eec 2024-10-15) (built from a source tarball)로 미리 컴파일되어 있었다(/boot/config-6.17.0-35-genericCONFIG_RUSTC_VERSION_TEXT로 확인 가능). rustup으로 정확히 같은 버전(1.82.0)을 설치해도 결과는 나아지지 않았다.

$ rustup toolchain install 1.82.0
$ rustup run 1.82.0 rustc --version
rustc 1.82.0 (f6e511eec 2024-10-15)

$ make RUSTC=$(rustup which --toolchain 1.82.0 rustc) ...
error[E0514]: found crate `core` compiled by an incompatible version of rustc
  = note: crate `core` compiled by rustc 1.82.0 (f6e511eec 2024-10-15)
          (built from a source tarball): .../libcore.rmeta
  = help: please recompile that crate using this compiler

버전·커밋 해시가 같아도 크레이트 메타데이터는 호환되지 않는다. (built from a source tarball)이 힌트다 — 배포 커널은 Canonical이 소스에서 직접 빌드한 rustc로 만들어졌고, rustup 공식 바이너리와는 내부 빌드 설정이 달라 semver가 같아도 ABI가 어긋난다.

해결책은 rustup이 아니라 apt가 제공하는, 배포판이 직접 빌드한 rustc를 쓰는 것이다.

$ sudo apt-get install -y rustc-1.82 rust-1.82-src

$ rustc-1.82 --version
rustc 1.82.0 (f6e511eec 2024-10-15) (built from a source tarball)

$ ls /usr/src/rustc-1.82.0/library/core/src/lib.rs
/usr/src/rustc-1.82.0/library/core/src/lib.rs

rust-1.82-src 패키지는 소스를 커널 빌드 스크립트가 기대하는 정확한 경로(/usr/src/rustc-<version>/library)에 그대로 풀어준다. 이 조합으로 바꾸자 rustavailable과 E0514 에러가 둘 다 사라졌다.

3단계 장벽 — module! 매크로의 params 필드 미지원

4부에서 쓴 params: 필드가 있는 템플릿을 그대로 컴파일하면 이번엔 새로운 에러가 난다.

error: proc macro panicked
  = help: message: Unknown key "params". Valid keys are:
          ["type", "name", "authors", "description", "license", "alias", "firmware"]

배포 커널의 module! 매크로는 2026-05-27 스냅샷이라 업스트림에 나중에 추가된 params 지원이 아직 없다. params 필드를 빼고 다시 작성했다.

// SPDX-License-Identifier: GPL-2.0

//! Rust kernel module template sample.

use kernel::prelude::*;

module! {
    type: RustTemplate,
    name: "rust_template",
    authors: ["Junorion"],
    description: "Rust kernel module template",
    license: "GPL",
}

struct RustTemplate {
    id: u32,
}

impl kernel::Module for RustTemplate {
    fn init(_module: &'static ThisModule) -> Result<Self> {
        let id: u32 = 1;

        if id == 0 {
            pr_err!("id must be greater than 0\n");
            return Err(EINVAL);
        }

        pr_info!("Rust template loaded (id={})\n", id);

        Ok(RustTemplate { id })
    }
}

impl Drop for RustTemplate {
    fn drop(&mut self) {
        pr_info!("Rust template unloaded (id was {})\n", self.id);
    }
}

4단계 장벽 — LLVM=1과 GCC로 빌드된 커널의 충돌

습관적으로 붙이던 LLVM=1을 쓰면 Rust 컴파일(RUSTC)은 성공하지만, 모듈 래퍼(.mod.c) 컴파일 단계에서 clang이 GCC 전용 플래그를 이해하지 못해 실패한다.

warning: the compiler differs from the one used to build the kernel
  The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.3.0) 13.3.0
  You are using:           Ubuntu clang version 18.1.3

  RUSTC [M] rust_template.o        # 여기까지는 성공
clang: error: unknown argument: '-fconserve-stack'
clang: error: unsupported argument 'bounds-strict' to option '-fsanitize='
clang: error: unsupported option '-mrecord-mcount' for target 'x86_64-unknown-linux-gnu'

이 커널은 GCC 13(x86_64-linux-gnu-gcc-13)으로 빌드됐는데, LLVM=1CC/LD/AR 전체를 clang으로 바꿔 .config에 박힌 GCC 빌드 정보와 충돌한다. Rust 컴파일은 RUSTC만 지정하면 되므로 LLVM=1을 빼고 GCC로 나머지를 빌드하면 된다.

최종 빌드와 insmod/rmmod 실전 검증

out-of-tree 모듈용 Makefile은 표준 형태 그대로다.

KDIR := /lib/modules/$(shell uname -r)/build
PWD  := $(shell pwd)

obj-m += rust_template.o

default:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

네 가지 장벽을 다 걷어낸 뒤 빌드하면 마지막까지 통과한다.

$ make RUSTC=/usr/bin/rustc-1.82 BINDGEN=$(which bindgen)
  RUSTC [M] rust_template.o
  MODPOST Module.symvers
  CC [M]  rust_template.mod.o
  CC [M]  .module-common.o
  LD [M]  rust_template.ko
  BTF [M] rust_template.ko
Skipping BTF generation for rust_template.ko due to unavailability of vmlinux

$ ls -la rust_template.ko
-rw-rw-r-- 1 noble noble 681320 rust_template.ko

실제로 로드해본다.

$ sudo insmod rust_template.ko
$ lsmod | grep rust_template
rust_template          12288  0

$ sudo dmesg | tail -3
rust_template: loading out-of-tree module taints kernel.
rust_template: module verification failed: signature and/or required key missing - tainting kernel
rust_template: Rust template loaded (id=1)

$ sudo rmmod rust_template
$ sudo dmesg | tail -1
rust_template: Rust template unloaded (id was 1)

Rust template loaded (id=1)Rust template unloaded (id was 1) 둘 다 실제 dmesg에 그대로 찍혔다. 컴파일 검증을 넘어, 지금 이 컴퓨터가 실행 중인 커널에 Rust 모듈을 실제로 얹고 내리는 것까지 확인한 것이다.

주의사항

  • 서명되지 않은 out-of-tree 모듈은 insmod 시점에 커널을 taint시킨다(“module verification failed” 경고). 실서비스 환경이라면 모듈 서명 절차가 별도로 필요하다.
  • 여기서 겪은 네 가지 문제(심볼릭 링크, rustc 버전, params 미지원, LLVM/GCC 충돌)는 이 Ubuntu 24.04 HWE 6.17.0-35 스냅샷 한정 사례다. 다른 배포판·다른 커널 버전에서는 증상이 다르거나 아예 없을 수 있다. 핵심은 “배포 커널이 어떤 rustc로, 어떤 C 컴파일러로 빌드됐는지”를 CONFIG_RUSTC_VERSION_TEXT와 dmesg/insmod 경고에서 먼저 확인하고 거기에 맞추는 것이다.
  • 1~4부의 코드는 업스트림 최신 마스터를 clone해 컴파일 검증한 것이라, 이 글처럼 특정 배포 커널에 얹으려면 API 드리프트(이번엔 params)를 다시 확인해야 할 수 있다.
  • rustup으로 설치한 툴체인은 배포 커널이 자체 빌드한 rustc와 버전 문자열이 같아도 크레이트 메타데이터가 다를 수 있다는 걸 실제로 확인했다. 배포 커널 기준으로 빌드해야 한다면 rustup보다 apt의 버전별 rustc-N.NN 패키지를 먼저 찾아보는 게 지름길이다.

마무리

“컴파일이 된다”와 “이 컴퓨터에 실제로 로드된다”는 다른 수준의 검증이다. 네 가지 장벽 모두 각각 다른 층위의 버전 불일치(패키징, rustc ABI, 매크로 API, C 툴체인)였다 — Rust for Linux를 실전에 쓰려면 코드 자체보다 빌드 환경 정합성에 시간을 더 쓰게 될 가능성이 높다.

참고

답글 남기기