Rust 커널 프로그래밍 가이드

Linux 커널 Rust 프로그래밍 심층 가이드: Rust for Linux 아키텍처, bindgen/Kbuild 통합, 안전성 모델(safe/unsafe 경계), 커널 추상화 계층, misc/platform/PCI/네트워크 드라이버 작성, Workqueue/Timer/UserSlice API, KUnit 테스팅, GDB 디버깅(Debugging), C→Rust 마이그레이션 전략까지 실무 관점으로 다룹니다.

전제 조건: Rust 언어 기초 문서를 먼저 읽으세요. 소유권(Ownership), 빌림(Borrowing), 트레이트(Trait), unsafe, FFI, no_std 개념을 이해해야 합니다. 또한 커널 모듈(Kernel Module)과 디바이스 드라이버 기초 지식이 필요합니다.

학습 비용 안내: 커널 Rust는 Rust 언어(소유권·라이프타임·unsafe 규칙)와 커널 지식(서브시스템 구조·C API·드라이버 수명주기)을 동시에 요구합니다. 빌드 환경 초기 설정(LLVM + bindgen + rustup)에 30분~1시간이 소요될 수 있으며, 첫 빌드는 CPU에 따라 20~60분이 걸립니다. Linux v6.1~v6.9까지는 기능 필요에 따라 최소 버전이 단계적으로 상향되었고(6.6 → 1.71.1, 6.9 → 1.76.0), 초기(6.1~6.5)에는 nightly 툴체인이 흔히 사용되었습니다. v6.10부터 공식 최소 stable 버전(1.78.0)이 지정되었고, Linux 7.0(2026-04)에서 experimental 태그가 제거되어 공식 커널 지원 언어로 전환되었습니다.
일상 비유: Rust로 커널 드라이버를 작성하는 것은 안전모를 쓰고 건설 현장에 들어가는 것과 같습니다. C는 안전모 없이 작업하지만 숙련자에게는 빠르고, Rust는 컴파일러가 안전모 역할을 하여 메모리 안전성 버그를 컴파일 시점에 차단합니다.

핵심 요약

  • 커널의 제2 언어 — Linux 6.1(2022.12)부터 Rust가 커널 개발 언어로 공식 지원되며, 소유권 시스템으로 use-after-free·데이터 레이스 등 메모리 안전 버그를 컴파일 시점에 차단합니다.
  • 안전 추상화 계층 — rust/kernel/의 추상화가 C 커널 API를 safe Rust 인터페이스로 감싸며, unsafe 코드는 FFI 경계와 하드웨어 접근에만 제한됩니다.
  • bindgen + Kbuild 통합 — bindgen이 C 헤더에서 Rust FFI 바인딩을 자동 생성하고, Kbuild가 .rs 파일을 커널 빌드 시스템(Build System)에 자연스럽게 통합합니다.
  • 드라이버 작성 지원 — misc, platform, PCI, 네트워크 드라이버를 Rust로 작성할 수 있으며, Workqueue·Timer·UserSlice 등 커널 기능 API가 제공됩니다.
  • 점진적 마이그레이션 — 기존 C 코드와 공존하며, 새 드라이버나 안전이 중요한 서브시스템부터 점진적으로 Rust로 전환하는 전략이 권장됩니다.

단계별 이해

  1. 개발 환경 구축
    Rust 툴체인(scripts/min-tool-version.sh rustc로 지정된 stable 버전), bindgen, LLVM을 설치하고 make LLVM=1 rustavailable로 빌드 환경을 검증합니다. v6.10부터 공식 최소 stable 툴체인 버전이 지정되어 stable Rust로 커널을 빌드합니다.
  2. 첫 커널 모듈 작성
    module! 매크로(Macro)로 최소 Rust 커널 모듈을 작성하고, Module 트레이트의 init()/drop() 생명주기를 이해합니다.
  3. 안전성 모델 이해
    safe/unsafe 경계, Pin과 커널 초기화, Arc 참조 카운팅, 에러 처리(kernel::error)의 Rust 커널 패턴을 학습합니다.
  4. 실전 드라이버 구현
    misc → platform → PCI 순서로 점차 복잡한 드라이버를 작성하며, C 코드와의 FFI 인터페이스 설계를 연습합니다.

no_std 프로그래밍 (커널 환경의 기반)

no_std는 Rust 표준 라이브러리(std)를 사용하지 않는 환경입니다. 임베디드 시스템, OS 커널, 부트로더(Bootloader) 등 운영체제가 없거나 제한된 환경에서 필수입니다. 리눅스 커널 Rust는 no_std 환경입니다. 실용적으로 세 영역을 구분해야 합니다. core 크레이트(Option, Result, Iterator, slice 등)는 OS 없이도 동작하므로 커널에서 자유롭게 사용할 수 있습니다. 힙 할당이 필요한 alloc(Vec, Box, Arc)은 커널 전용 버전을 통해 사용하되, 할당 실패 시 panic 대신 Err를 반환하는 try_push()/try_new() API를 써야 합니다. 반면 스레드(Thread), 소켓(Socket), 파일 시스템(Filesystem) I/O 등 OS에 의존하는 std 기능은 커널에서 사용할 수 없습니다 — 이것이 커널 Rust 코딩에서 가장 먼저 마주치는 제약입니다.

Rust 라이브러리 계층과 no_std Rust 라이브러리 계층과 no_std 아래로 갈수록 기능이 적고 의존성이 낮아짐 — 위쪽 레이어는 아래쪽 레이어를 포함하며, 커널은 표준 라이브러리를 제외한 하위 계층을 사용합니다 ✗ 불가 std (표준 라이브러리) — OS 위에서만 동작 OS가 제공하는 기능 — 파일 · 네트워크 · 스레드 · 시간 · 환경변수 등에 의존 OS 의존 기능은 커널에서 사용할 수 없습니다 — 커널 Rust 코딩에서 가장 먼저 마주치는 제약 File TcpStream Thread HashMap println! env 포함 △ 전용 alloc (힙 할당) — 커널 전용 버전 사용 동적 메모리 할당 — 전역 할당자(allocator) 필요 커널 전용: 실패 시 panic 대신 Err 반환 — try_push / try_new, kmalloc / kfree 기반 Box Vec String Rc Arc BTreeMap 포함 ✓ 자유 core (언어 코어) — OS · 할당자 불필요 어디서나 동작하는 기초 기능 — 언어 코어를 제약 없이 제공 OS · 할당자가 필요 없어 커널 · 임베디드 · OS 모두에서 동일하게 사용 가능 Option Result Iterator fmt slice str #![no_std] + 커널 전용 alloc + core ⇒ 커널에서 Rust 핵심 기능 사용
Rust 라이브러리 계층(std ⊃ alloc ⊃ core)과 커널에서의 사용 가능 여부 — 커널은 OS에 의존하는 std를 제외하고 커널 전용 alloc과 core를 사용합니다.
// no_std 크레이트 선언
#![no_std]  // std 사용 금지 선언

// 사용 가능: core 라이브러리 전체
use core::fmt;
use core::option::Option;
use core::result::Result;

// alloc 크레이트 (전역 할당자 필요)
extern crate alloc;
use alloc::vec::Vec;
use alloc::string::String;
use alloc::boxed::Box;

// 커널의 경우: 커널 전용 alloc 크레이트 사용
// - Vec::try_push() (할당 실패 시 Err 반환)
// - Box::try_new() (할당 실패 시 Err 반환)
// - panic! 사용 금지 — 커널 oops 유발

// 커널 no_std의 핵심 차이점
// 1. println! → pr_info! (커널 로그)
// 2. Vec::push() → Vec::try_push()? (할당 실패 처리)
// 3. std::sync::Mutex → kernel::sync::Mutex
// 4. std::thread → 커널 workqueue/kthread
// 5. panic → Result 에러 전파 필수
// 6. 전역 할당자: 커널의 kmalloc/kfree

std → no_std(커널) API 대응표

std (사용자 공간(User Space))커널 Rust 대체차이점
println!("...")pr_info!("...\n")커널 로그 시스템(printk)으로 출력
Vec::push(v)Vec::try_push(v)?할당 실패 시 Err 반환 (panic 없음)
Box::new(v)Box::try_new(v)?할당 실패 처리 필수
String::from("...")CString::try_from_fmt(...)?C 호환 문자열 (NUL 종단)
std::sync::Mutexkernel::sync::Mutex커널 mutex, lockdep 통합, Pin 필요
std::thread::spawnWorkqueue / kthread커널 스레드(Kernel Thread) 모델 사용
std::time::Durationkernel::time::Jiffies커널 타이밍 단위 (jiffies)
std::fs::FileVFS 추상화직접 파일 접근 불가
panic!("...")return Err(code::EINVAL)panic은 커널 oops → 반드시 Result 사용

no_std 환경에서 필요한 lang items

// === 독립 no_std 바이너리 (임베디드/OS 개발) ===
#![no_std]
#![no_main]  // main() 대신 커스텀 진입점 사용

// panic 핸들러 — 필수 (std 없이는 기본 핸들러가 없음)
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}  // 무한 루프 (halt)
}

// 전역 할당자 — alloc 크레이트 사용 시 필수
#[global_allocator]
static ALLOC: MyAllocator = MyAllocator;

// 커널에서는 위 항목들이 이미 정의되어 있음:
// - panic_handler → kernel_panic() 호출
// - global_allocator → kmalloc/kfree 기반
// - 드라이버 개발자는 이들을 직접 정의할 필요 없음
no_std 필수 항목일반 임베디드리눅스 커널
#![no_std]직접 선언커널 빌드 시스템(Build System)이 자동 적용
#[panic_handler]직접 구현커널이 제공 (BUG/oops)
#[global_allocator]직접 구현커널이 제공 (kmalloc)
OOM 핸들러(Handler)#[alloc_error_handler]try_* API로 명시적 처리
진입점(Entry Point)#[no_main] + 커스텀module! 매크로
⚠️

커널에서 절대 사용 불가: std::*, panic!()/unwrap()/expect() (최소화), async-std/tokio, println!, format!() (커널 대체: fmt!()), 힙 할당 실패를 무시하는 모든 API. 커널 환경에서는 "할당이 항상 성공합니다"는 가정을 할 수 없습니다.

Rust in Kernel 개요

Linux 6.1(2022년 12월)부터 Rust가 커널 개발의 제2 언어로 공식 지원됩니다. Rust의 소유권 시스템과 타입 안전성은 safe Rust 경계에서 use-after-free, 버퍼 오버플로(Buffer Overflow), 데이터 레이스 같은 메모리 안전 버그를 크게 줄이며, unsafe/FFI 경계는 별도 검증이 필요합니다.

커널 Rust 프로젝트의 핵심 원칙은 "기존 C 코드를 깨뜨리지 않으면서, 새로운 코드에서 점진적으로 메모리 안전한 Rust를 도입합니다"는 것입니다. 이는 30년 이상의 C 코드베이스를 가진 Linux 커널에서 현실적이면서도 효과적인 전략입니다.

측면설명
목표새로운 드라이버·모듈을 Rust로 작성하여 메모리 안전성 향상
전략기존 C 코드를 대체하지 않고, 새로운 코드에서 점진적으로 Rust 도입
구조C API → unsafe Rust 래퍼 → safe Rust 드라이버 3계층
도구rustc (LLVM 백엔드), bindgen (C→Rust 바인딩), Kbuild 통합
제약no_std, 커널 전용 alloc, panic 금지, 커널 지정 최소 stable rustc 버전 필요 (v6.10+부터 stable 최소 버전 지정, 7.0+에서 experimental 태그 제거)
커뮤니티Rust for Linux 팀 주도, Miguel Ojeda 메인테이너, LKML을 통한 리뷰
소스 위치rust/ (인프라), rust/kernel/ (추상화), samples/rust/ (예제)

커널 소스 트리에서 Rust 코드의 위치

경로내용역할
rust/kernel/Rust 커널 크레이트safe 추상화 계층 (Mutex, Arc, Vec, Error 등)
rust/bindings/bindgen 출력C 헤더 → Rust FFI 바인딩 자동 생성
rust/helpers/C 헬퍼 함수인라인 C 함수를 Rust에서 호출 가능하게 래핑
rust/macros/절차적 매크로(Macro)module!, #[vtable], #[pin_data] 등
samples/rust/Rust 예제 모듈학습 및 빌드 검증용
drivers/*/xxx_rust.rs실제 Rust 드라이버프로덕션 드라이버 코드
ℹ️

참고 자료: 공식 문서는 docs.kernel.org/rust, 프로젝트 홈페이지는 rust-for-linux.com, 소스 저장소는 GitHub Rust-for-Linux/linux를 참조하세요.

왜 커널에 Rust인가

공개된 대규모 소프트웨어 보안 보고서에서 메모리 안전 버그(use-after-free, 버퍼 오버플로, 이중 해제(Double Free) 등)의 비중이 약 70%로 보고됩니다. C 언어는 이러한 버그를 컴파일 타임에 일반적으로 감지하기 어렵지만, Rust는 소유권(ownership) 시스템을 통해 많은 클래스를 컴파일 시점에 차단합니다.

조직/프로젝트메모리 안전 버그 비율출처/연도
MicrosoftCVE의 ~70%가 메모리 안전 이슈MSRC 블루핫 발표, 2019
Google Chrome고위험 버그의 ~70%가 메모리 안전Chromium 보안 팀, 2020
Android취약점(Vulnerability)의 ~65%가 메모리 안전Android 보안 보고서, 2022 (수치 출처 확인 필요)
Linux 커널CVE의 상당 부분이 UAF/오버플로커널 보안 메일링 리스트
Apple iOS/macOS0-day의 다수가 메모리 관련Project Zero 분석

Rust 안전성 보장 원리

원리커널에서의 의미방지하는 C 버그
소유권(Ownership)모든 커널 객체는 정확히 하나의 소유자가 있으며, 소유자가 스코프를 벗어나면 자동 해제메모리 누수, 이중 해제
빌림(Borrowing)공유 참조(&T)는 여러 개 가능하지만 수정 불가, 가변 참조(&mut T)는 독점적Use-After-Free, 데이터 레이스
수명(Lifetime)참조가 가리키는 데이터보다 오래 살 수 없음을 컴파일러가 보증댕글링 포인터
타입 시스템Send/Sync 트레이트로 스레드(Thread) 안전성 강제, Result로 에러 전파 강제잘못된 에러 코드 경로, 동기화 누락
// 개념 예시: C에서 흔한 Use-After-Free — Rust에서는 컴파일 에러
fn dangling_example() {
    let reference;
    {
        let data = Vec::new();
        reference = &data;  // ← 컴파일 에러! data보다 reference가 오래 삶
    }
    // data는 여기서 해제됨 — reference를 사용하면 UAF
    // Rust 컴파일러가 차단
}

C-Rust FFI 경계 원리

커널 Rust 코드의 핵심 설계 원칙은 "unsafe 경계를 최소화하고, 안전한 추상화 뒤에 숨긴다"는 것입니다:

커널 Rust 안전 경계 모델 커널 Rust 안전 경계 모델 unsafe는 추상화 경계 계층에만 집중하고, 그 위로는 안전한(safe) 코드 영역을 최대화합니다 (왼쪽 → 오른쪽 흐름) C 커널 API 기존 C로 작성된 커널 코드 kmalloc · mutex_lock copy_from_user · kfree ⚠ 안전성 수동 관리 FFI 경계 Rust 추상화 (unsafe) rust/kernel/ 래퍼 계층 RAII · 타입 안전 API로 감쌈 경계 내부 unsafe 블록 SAFETY 주석 필수 safe API Safe Rust 드라이버 대부분 unsafe 불필요 컴파일러가 안전성 보증 드라이버 로직 개발에 집중 ✔ safe — 안전 코드 영역 bindgen 변환 대상 코드 리뷰 집중 영역 대부분의 코드가 여기 안전성 스펙트럼 (왼쪽 → 오른쪽으로 안전도 증가) 위험 (수동 관리) unsafe 경계 계층 안전 (safe 보증) unsafe를 경계 계층에 집중 → 검토 범위를 최소화하고 안전 코드를 최대화
C 커널 API(수동 관리) → Rust 추상화(FFI/unsafe 경계, SAFETY 주석) → Safe Rust 드라이버(컴파일러 보증) 순으로 갈수록 안전성이 높아지며, unsafe 검토 대상이 중간 경계 계층으로 좁혀집니다.
ℹ️

커널 Rust의 제약: 표준 라이브러리(std)를 사용할 수 없고, no_std + 커널 전용 alloc 크레이트를 사용합니다. 힙 할당은 실패 가능하므로 try_ 접두사 API를 사용하며(Vec::try_push()), panic은 커널 oops로 이어지므로 반드시 Result로 에러를 전파해야 합니다.

커널 Rust 아키텍처 전체 구조

커널 Rust 코드의 전체 아키텍처는 여러 계층으로 구성됩니다. 각 계층은 명확한 역할을 가지며, unsafe 경계를 최소화합니다.

커널 Rust 아키텍처 전체 구조 커널 Rust 아키텍처 전체 구조 위에서 아래로 흐르는 계층 구조 — 각 계층은 명확한 역할을 가지며, unsafe 경계를 최소화합니다 Rust 드라이버 drivers/net/phy/ax88796b_rust.rs, drivers/gpu/drm/nova/ 등 — 대부분 safe 코드 Safe Rust (대부분) 사용 커널 크레이트 (rust/kernel/) Mutex, Arc, Vec, Error, Module, WorkQueue, Timer — safe API 제공 Safe API + 내부 unsafe 사용 · 의존 macros 크레이트 module! · #[vtable] 커널 alloc 크레이트 Box · Vec · try_ API bindings (bindgen) C 헤더 → Rust FFI 선언 helpers (C 헬퍼) 인라인 C 함수 래핑 FFI 경계 (unsafe) C 커널 코드 kmalloc, mutex_lock, register_chrdev, platform_driver_register — 수천 개의 C API unsafe FFI 경계
드라이버는 커널 크레이트의 safe API를 사용하고, 커널 크레이트는 macros · alloc · bindings · helpers 크레이트에 의존합니다. 이 중 bindings(bindgen 출력)와 helpers(C 헬퍼)가 C 커널 코드와의 FFI 경계를 이루므로, unsafe는 이 경계에 집중됩니다.
계층안전성 수준역할파일 위치
Rust 드라이버Safe Rust (대부분)실제 디바이스 드라이버 구현drivers/*/*.rs
커널 크레이트Safe API + 내부 unsafeC API의 safe Rust 래퍼 제공rust/kernel/*.rs
macros 크레이트proc_macro (빌드 타임)module!, #[vtable], pin_init! 등rust/macros/
alloc 크레이트커널 전용 (try_ API)Box, Vec, String (할당 실패 처리)rust/alloc/
bindings전부 unsafebindgen이 생성한 C FFI 바인딩rust/bindings/
helpersC 헬퍼 (unsafe)인라인 C 함수 래핑 (bindgen 한계 극복)rust/helpers/

이 아키텍처의 핵심 원칙은 "unsafe 경계를 최소화"하는 것입니다. bindings 계층에서 발생하는 unsafe는 커널 크레이트가 safe API로 감싸고, 드라이버 개발자는 대부분 safe Rust만 사용합니다. unsafe 코드의 정확성은 커널 크레이트 개발자가 // SAFETY: 주석으로 문서화하고, 커뮤니티가 집중 리뷰합니다.

Rust 커널 개발 환경 설정

커널 Rust 빌드를 위해서는 정확한 버전의 도구 체인이 필요합니다. 커널은 특정 rustc/bindgen 버전을 요구하며, 버전 불일치는 빌드 실패의 가장 흔한 원인입니다.

필수 도구 체인

도구용도버전 확인설치 방법
rustcRust 컴파일러scripts/min-tool-version.sh rustcrustup으로 설치
rust-srccore/alloc 소스—rustup component add rust-src
bindgenC→Rust 바인딩 생성scripts/min-tool-version.sh bindgencargo install
clangLLVM 컴파일러clang --version패키지 관리자
lldLLVM 링커(Linker)—패키지 관리자
clippy (권장)Rust 린트 도구—rustup component add clippy
# === 1단계: Rust 툴체인 설치 ===

# rustup이 없다면 먼저 설치
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 커널이 요구하는 정확한 rustc 버전으로 설정
# (커널 소스 디렉터리에서 실행)
rustup override set $(scripts/min-tool-version.sh rustc)

# 필수 컴포넌트 설치
rustup component add rust-src        # core/alloc 소스 (커널 빌드 필수!)
rustup component add clippy rustfmt  # 린트/포맷 도구 (권장)

# === 2단계: bindgen 설치 ===
cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen-cli

# === 3단계: LLVM/Clang 설치 ===
# Debian/Ubuntu
apt install llvm clang lld
# Fedora/RHEL
dnf install llvm clang lld
# Arch Linux
pacman -S llvm clang lld

# === 4단계: 환경 검증 ===
make LLVM=1 rustavailable
# 성공: "Rust is available!"
# 실패 시 누락된 도구와 필요 버전이 표시됨

# === 5단계: Kconfig에서 Rust 활성화 ===
make LLVM=1 menuconfig
# General setup → Rust support → [Y]
# (또는) 직접 .config에 추가:
# CONFIG_RUST=y

# === 6단계: 빌드 ===
make LLVM=1 -j$(nproc)

# === 선택: Rust 문서 생성 ===
make LLVM=1 rustdoc
# 결과: Documentation/output/rust/kernel/index.html

트러블슈팅

증상원인해결
rustc not foundPATH에 rustup 미등록source $HOME/.cargo/env
Rust is NOT availablerustc/bindgen 버전 불일치scripts/min-tool-version.sh로 필요 버전 확인 후 재설치
error: component 'rust-src' is not availablerust-src 컴포넌트 미설치rustup component add rust-src로 설치. v6.10+ 기준 공식 최소 stable 툴체인 사용
빌드 중 bindgen 에러bindgen/clang 버전 불일치bindgen --version 확인, 정확한 버전 재설치
링크 에러lld 미설치apt install lld
⚠️

버전 일치 필수: 커널은 최소 rustc/bindgen 버전을 요구합니다. scripts/min-tool-version.sh rustc와 scripts/min-tool-version.sh bindgen으로 확인하세요. v6.10부터 공식 최소 stable 툴체인(1.78.0)이 지정되어 stable Rust로 커널 빌드가 가능하며, Linux 7.0(2026-04)에서는 experimental 태그가 제거되었습니다. v7.1+부터는 최소 버전이 1.85.0으로 상향되었습니다. 단, 커널이 요구하는 최소 버전보다 낮은 stable 버전은 사용 불가합니다.

💡

IDE 지원: make LLVM=1 rust-analyzer 명령으로 rust-project.json을 생성하면, VS Code나 CLion에서 rust-analyzer가 커널 Rust 코드의 자동완성, 타입 추론, 에러 표시를 지원합니다.

Rust 커널 모듈 예제

Rust 커널 모듈의 진입점은 module! 매크로와 Module 트레이트입니다. C에서 MODULE_LICENSE·MODULE_AUTHOR·MODULE_DESCRIPTION과 module_init()/module_exit()를 분산해 작성하는 것을 Rust에서는 module! 블록 하나로 통합 선언하고, init() 반환값이 곧 모듈 상태 객체, Drop 구현이 곧 module_exit()에 해당합니다. 이 구조 덕분에 init에서 할당한 자원이 에러 경로이든 정상 언로드이든 Drop을 통해 반드시 해제되며, 자원 누수(Resource Leak)가 타입 시스템 수준에서 방지됩니다.

// 실습 예제: 최소 Rust 커널 모듈
// SPDX-License-Identifier: GPL-2.0
use kernel::prelude::*;

module! {
    type: MyModule,
    name: "my_rust_module",
    author: "Developer",
    description: "A simple Rust kernel module",
    license: "GPL",
}

struct MyModule {
    number: i32,
}

impl kernel::Module for MyModule {
    fn init(_module: &'static ThisModule) -> Result<Self> {
        pr_info!("Rust module loaded!\\n");
        Ok(MyModule { number: 42 })
    }
}

impl Drop for MyModule {
    fn drop(&mut self) {
        pr_info!("Rust module unloaded (number={})\\n", self.number);
    }
}

module! 매크로는 절차적 매크로로, C의 module_init()/module_exit()에 해당하는 코드를 자동 생성합니다. Drop 트레이트 구현이 module_exit 정리 함수 역할을 하며, 모듈 언로드 시 RAII 패턴으로 자원이 자동 해제됩니다.

C 모듈 vs Rust 모듈 비교

요소C 모듈Rust 모듈
진입점(Entry Point)module_init(fn)module! { ... } + impl Module
종료점module_exit(fn)impl Drop
에러 반환return -ENOMEM; (잊으면 조용히 진행)Result 필수 (컴파일러가 강제)
자원 정리수동 goto err_cleanupRAII 자동 정리 (Drop)
라이선스MODULE_LICENSE("GPL")license: "GPL"

모듈용 Kconfig / Makefile

# drivers/my_driver/Kconfig
config MY_RUST_DRIVER
    tristate "My Rust Driver"
    depends on RUST
    help
      A sample Rust kernel driver.

# drivers/my_driver/Makefile
obj-$(CONFIG_MY_RUST_DRIVER) += my_rust_driver.o
# 모듈 빌드 및 테스트
make LLVM=1 M=drivers/my_driver -j$(nproc)

# 모듈 적재
insmod drivers/my_driver/my_rust_driver.ko
dmesg | tail -3  # "Rust module loaded!" 확인

# 모듈 제거
rmmod my_rust_driver
dmesg | tail -3  # "Rust module unloaded" 확인

빌드 검증 실습 세트

아래 순서는 실제로 빌드 성공/실패를 빠르게 확인할 수 있는 최소 실습 경로입니다.

# 실습 1: 툴체인/환경 검증
make LLVM=1 rustavailable

# 실습 2: 현재 커널 트리에서 Rust 샘플 심볼 탐색
grep -R "config SAMPLE_RUST" -n samples/rust Kconfig* */Kconfig

# 실습 3: menuconfig에서 Rust + Rust samples 활성화
make LLVM=1 menuconfig

# 실습 4: Rust 샘플만 선택 빌드
make LLVM=1 M=samples/rust -j$(nproc)

# 실습 5: 결과 확인
find samples/rust -name "*.o" -o -name "*.ko"
검증 단계성공 기준실패 시 점검 포인트
rustavailableRust/LLVM 도구체인 요구사항 충족scripts/min-tool-version.sh 출력 버전과 실제 설치 버전 비교
Rust 샘플 빌드samples/rust 오브젝트 생성비활성화된 Kconfig 심볼, 누락된 rust-src, bindgen 버전 불일치
모듈 적재 테스트insmod/rmmod 후 커널 로그 정상커널/모듈 버전 불일치, 서명 정책(Secure Boot) 확인

QEMU로 빠르게 테스트하기

# QEMU에서 Rust 모듈 테스트 (전체 워크플로)

# 1. 최소 Kconfig 생성 (x86_64)
make LLVM=1 x86_64_defconfig
./scripts/config --enable RUST
./scripts/config --enable SAMPLE_RUST_MINIMAL
./scripts/config --enable MODULES
./scripts/config --enable BLK_DEV_INITRD
make LLVM=1 olddefconfig

# 2. 커널 + 모듈 빌드
make LLVM=1 -j$(nproc)

# 3. QEMU로 부팅 (initramfs가 준비되어 있다면)
qemu-system-x86_64 \
    -kernel arch/x86/boot/bzImage \
    -initrd rootfs.cpio.gz \
    -append "console=ttyS0" \
    -nographic \
    -m 512M

# 4. QEMU 내에서 Rust 샘플 모듈 테스트
# insmod rust_minimal.ko
# dmesg | grep -i rust
# rmmod rust_minimal
💡

빠른 피드백 루프: 전체 커널을 재빌드하지 않고 Rust 모듈만 빠르게 수정/빌드하려면 make LLVM=1 M=samples/rust -j$(nproc)으로 모듈만 빌드한 후, QEMU에서 insmod/rmmod로 테스트하세요. 모듈 수정 → 빌드 → 테스트 사이클이 수 초로 단축됩니다.

Kbuild 통합과 빌드 과정

Rust 코드는 Kbuild 시스템에 완전히 통합됩니다. 별도의 Cargo 빌드 시스템을 쓰지 않는 이유는 커널이 이미 Kbuild라는 단일 빌드 시스템을 표준으로 사용하기 때문입니다 — Rust라고 예외를 두면 모듈 간 의존성 추적과 교차 아키텍처(Cross-Arch) 빌드가 복잡해집니다. 실제 파이프라인(Pipeline)은 세 단계입니다. 1단계: bindgen이 include/linux/*.h를 읽어 C 구조체·함수·상수에 대한 Rust FFI 바인딩(bindings.rs)을 자동 생성합니다. 2단계: rustc가 드라이버 소스(my_driver.rs)를 컴파일하여 .o 오브젝트 파일을 만드는데, 이때 커널 전용 alloc·core·kernel 크레이트가 암묵적으로 링크됩니다. 3단계: 일반 C .o와 Rust .o가 함께 링커(Linker)로 전달되어 최종 커널 이미지나 모듈 .ko가 생성됩니다.

Kbuild Rust 빌드 파이프라인 Kbuild Rust 빌드 파이프라인 3단계 — ① bindgen이 C 헤더에서 FFI 바인딩 생성 → ② rustc가 드라이버·커널 크레이트 컴파일 → ③ lld가 C 오브젝트와 링크 ① FFI 생성 bindgen C 헤더 파일 include/linux/*.h bindgen C → Rust FFI bindings_generated.rs 전부 unsafe extern "C" 선언 입력 ② 컴파일 rustc Rust 소스 drivers/*/*.rs — 드라이버 rust/kernel/*.rs — 커널 크레이트 rustc (LLVM) 타입 · 빌림 검사 cfg(CONFIG_*) 조건 컴파일 LLVM IR → .o 오브젝트 생성 Rust .o ③ 링크 lld C 오브젝트 (.o) 기존 C 코드가 컴파일된 결과 lld (링커) C .o + Rust .o 링크 출력: vmlinux / .ko Makefile에 obj-$(CONFIG_...) += my_driver.o 규칙만 추가하면 Kbuild가 rustc 호출 · .o 생성 · 링크를 자동 수행합니다
① bindgen이 C 헤더에서 FFI 바인딩(bindings_generated.rs)을 생성하고, ② rustc가 드라이버 소스와 커널 크레이트를 컴파일해 .o를 만들며, ③ lld가 그 Rust .o를 기존 C .o와 함께 링크해 vmlinux나 모듈 .ko를 생성합니다.
# samples/rust/Makefile
obj-$(CONFIG_SAMPLE_RUST_MINIMAL) += rust_minimal.o
obj-$(CONFIG_SAMPLE_RUST_PRINT)   += rust_print.o

# 커널 전체 빌드 시 Rust 코드 포함
make LLVM=1 -j$(nproc)

Kbuild Rust 빌드 타겟

명령어설명출력
make LLVM=1 rustavailableRust 툴체인 사용 가능 여부 확인Rust is available! 또는 에러
make LLVM=1 rustfmt커널 Rust 코드 포맷팅rustfmt 적용
make LLVM=1 rustdoc커널 Rust API 문서 생성Documentation/output/rust/
make LLVM=1 rust-analyzerrust-analyzer 설정 생성rust-project.json
make LLVM=1 clippy커널 Rust 코드 린트경고/제안 출력
make LLVM=1 M=samples/rustRust 예제 모듈만 빌드*.ko 파일
# Rust 지원 활성화 전체 워크플로
rustup override set $(scripts/min-tool-version.sh rustc)
rustup component add rust-src

make LLVM=1 rustavailable         # 툴체인 확인
make LLVM=1 menuconfig            # General setup → Rust support [Y]
make LLVM=1 -j$(nproc)            # 전체 빌드
make LLVM=1 rust-analyzer         # IDE 설정 생성
⚠️

LLVM 필수: 커널 Rust 빌드는 반드시 LLVM=1 플래그가 필요합니다. GCC 백엔드로는 Rust 코드를 컴파일할 수 없습니다. LLVM 버전은 scripts/min-tool-version.sh llvm에서 최소 요구 버전을 확인하세요. rustc와 Clang이 동일한 LLVM 버전을 사용해야 링크 호환성이 보장됩니다.

bindgen과 C 바인딩 생성

bindgen은 C 헤더 파일에서 Rust FFI 바인딩을 자동 생성합니다. 커널 빌드 시 rust/bindings/bindings_generated.rs가 생성되며, 모든 함수가 unsafe입니다.

// rust/bindings/lib.rs — 자동 생성된 바인딩 모듈
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]

mod bindings_raw {
    // bindgen이 C 헤더에서 자동 생성
    include!(concat!(env!("OBJTREE"), "/rust/bindings/bindings_generated.rs"));
}

pub use bindings_raw::*;

// 바인딩 사용 예시
unsafe {
    // SAFETY: GFP_KERNEL은 프로세스 컨텍스트에서 안전하게 호출 가능
    let ptr = bindings::kmalloc(64, bindings::GFP_KERNEL);
    if !ptr.is_null() {
        bindings::kfree(ptr);
    }
}

커널 Rust 추상화 계층

커널 Rust 코드는 C API를 직접 호출하지 않고, 안전한 Rust 래퍼(Abstraction)를 통해 접근합니다. 이 추상화 계층이 커널 Rust의 핵심 가치입니다. 설계 원칙은 명확합니다: unsafe 코드는 추상화 레이어 내부에만 격리(Isolation)하고, 드라이버 작성자에게는 safe Rust 인터페이스만 노출합니다. 예를 들어 kernel::sync::Mutex<T>는 내부적으로 C의 mutex_lock()/mutex_unlock()을 unsafe 블록으로 호출하지만, 외부에는 RAII 기반의 safe API만 제공합니다. 이 패턴이 rust/kernel/ 디렉터리 전체에 적용됩니다. 추상화가 없는 C API를 새로 써야 한다면, 드라이버 자체에 unsafe 코드를 두지 말고 rust/kernel/에 래퍼를 먼저 추가하는 것이 커널 Rust의 기여 관행입니다.

C API → Rust 추상화 매핑 전체 C API → Rust 추상화 매핑 전체 각 행은 C 커널 API가 Rust 타입 시스템으로 감싸져 어떻게 안전해지는지 보여줍니다 분류 C 커널 API → Rust 추상화 안전성 보장 메모리 관리 kmalloc / kfree Box<T>, Vec<T> (try_* API) 자동 해제 이중 해제 방지 동기화 뮤텍스 mutex_lock / mutex_unlock Mutex<T> (RAII Guard) 자동 unlock 데이터 보호 강제 동기화 스핀락 spin_lock / spin_unlock SpinLock<T> (RAII Guard) 가드 패턴 컨텍스트 제약 강제 참조 카운트 refcount_inc / refcount_dec Arc<T> 자동 카운팅 누수 방지 사용자 복사 copy_to/from_user UserSlice / IoBuffer 범위 검사 에러 전파 강제 에러 처리 -ENOMEM, -EINVAL, ... Result<T, Error> (code::* 타입) 에러 무시 불가 ? 연산자 전파 워크큐 INIT_WORK / schedule_work Work<T> (WorkItem trait) 타입 안전 콜백 소유권 이전 타이머 timer_setup / mod_timer Timer<T> (TimerCallback) Pin 초기화 Arc 소유권 관리 모든 추상화의 공통 목표 — C API의 안전성 계약을 Rust 타입 시스템으로 인코딩해 위반을 컴파일 타임에 검증
C 커널 API 8종이 어떤 Rust 추상화로 감싸지는지(가운데)와 그 결과 타입 시스템이 강제하는 안전성(오른쪽)을 한 행씩 대응시킨 매핑표입니다. 추상화는 rust/kernel/에 위치하며, 드라이버는 이 safe API만 사용합니다.

추상화 작성 패턴 (C API → Safe Rust)

// 추상화 작성의 3단계 패턴
// 1단계: bindgen이 생성한 unsafe 바인딩
// extern "C" { fn mutex_lock(lock: *mut mutex); }
// extern "C" { fn mutex_unlock(lock: *mut mutex); }

// 2단계: safe 래퍼 작성 (rust/kernel/sync/mutex.rs)
pub struct Mutex<T> {
    inner: Opaque<bindings::mutex>,  // C struct을 불투명하게 래핑
    data: UnsafeCell<T>,           // 내부 가변성
}

// SAFETY: Mutex로 보호되므로 Send + Sync
unsafe impl<T: Send> Send for Mutex<T> {}
unsafe impl<T: Send> Sync for Mutex<T> {}

impl<T> Mutex<T> {
    pub fn lock(&self) -> Guard<Self> {
        // SAFETY: mutex가 init()으로 초기화된 상태이고,
        //         Guard가 Drop될 때 unlock이 보장됩니다.
        unsafe { bindings::mutex_lock(self.inner.get()) }
        Guard::new(self)  // Guard가 unlock 책임을 가짐
    }
}

// 3단계: 드라이버에서 safe하게 사용
let mut guard = my_mutex.lock();  // 잠금 획득
*guard += 1;                      // 보호된 데이터 수정
// guard가 Drop → mutex_unlock() 자동 호출

안전성 모델

커널 Rust의 안전성 모델은 "unsafe를 최소한의 경계 계층에 집중하고, 나머지는 safe Rust로 보장합니다"는 원칙에 기반합니다. 이 모델은 전체 커널 코드를 한 번에 검증하는 대신, unsafe 블록만 집중적으로 리뷰하여 검증 가능한 범위를 크게 줄입니다.

커널 Rust 안전성 피라미드 커널 Rust 안전성 피라미드 아래로 갈수록 코드량이 많고, 위로 갈수록 컴파일러가 보증하는 코드 비중이 커집니다 unsafe는 하위 두 계층(bindgen 바인딩 · Rust 추상화)에만 존재합니다 코드량 적음 코드량 많음 Safe Rust 드라이버 코드 컴파일러가 안전성을 보증 ✓ 컴파일러 보증 Rust 추상화 계층 (rust/kernel/) SAFETY 주석 · 수동 리뷰 — unsafe fn을 safe API로 래핑 △ 수동 리뷰 bindgen 바인딩 (rust/bindings/) C 헤더에서 자동 생성 — 모든 함수가 unsafe unsafe · 자동 생성 C 커널 API (include/, kernel/) 전통적 코드 리뷰 — 메모리 안전을 프로그래머가 책임 수동 관리 핵심: unsafe는 하위 두 계층에 집중 → 리뷰 비용 절감, 상위 계층은 컴파일러가 보증
계층별로 안전성을 누가 책임지는지(오른쪽 라벨)를 보여주는 피라미드입니다. unsafe는 하위 두 계층(C 커널 API · bindgen 바인딩)에만 존재하고, 그 위의 Rust 추상화 계층이 unsafe를 safe API로 감싸므로 드라이버 코드는 컴파일러가 검증합니다. 피라미드의 폭은 코드량 비중(아래로 갈수록 방대한 C 코드베이스)을 나타냅니다.

안전성 계층 구조

계층안전성 수준코드 위치검증 방법
Safe Rust 드라이버컴파일러가 보증drivers/xxx/src/컴파일 통과 = 메모리 안전
Rust 추상화 계층SAFETY 주석 + 리뷰rust/kernel/수동 리뷰 + SAFETY 근거
bindgen 바인딩자동 생성 (unsafe)rust/bindings/C API 계약 준수 검증
C 커널 APIC 규칙 (수동 관리)include/, kernel/전통적 코드 리뷰
// === Safe Rust 패턴: 컴파일러가 안전성 보증 ===
fn safe_example(data: &[u8]) -> Result {
    let mutex = Mutex::new(Vec::new());
    let mut guard = mutex.lock();  // RAII 잠금
    guard.try_push(data[0])?;
    // guard가 스코프를 벗어나면 자동으로 unlock
    // → C에서 흔한 "lock 후 unlock 누락" 버그 불가능
    Ok(())
}

// === Unsafe Rust: C 함수 호출 시 필요 ===
// 모든 unsafe 블록에는 반드시 SAFETY 주석을 작성해야 합니다.
// 이 주석은 "왜 이 코드가 안전한지"를 명확히 설명합니다.

// SAFETY: ptr은 probe()에서 kmalloc으로 할당된 유효한 메모리를 가리키며,
//         이 함수의 호출 시점에서 ptr에 대한 다른 참조가 없으므로
//         데이터 레이스가 발생하지 않습니다.
unsafe { bindings::some_c_function(ptr) };

// === SAFETY 주석 작성 가이드 ===
// 나쁜 예: "// SAFETY: 안전합니다"  ← 근거 없음!
// 좋은 예:
// SAFETY: 다음 조건들이 충족됩니다:
//   1. `ptr`은 `Box::into_raw()`에서 얻은 유효한 포인터입니다.
//   2. `ptr`은 정확히 한 번만 `Box::from_raw()`로 변환됩니다.
//   3. `ptr`이 가리키는 메모리는 이 시점에서 다른 참조가 없습니다.
let obj = unsafe { Box::from_raw(ptr) };

안전 불변조건(Safety Invariant) 패턴

// 커널 Rust에서 자주 사용되는 패턴:
// 구조체 수준의 안전 불변조건을 문서화하고 강제합니다.

/// MMIO 디바이스 레지스터 접근 래퍼.
///
/// # Invariants
///
/// - `base`는 유효하게 매핑된 MMIO 영역의 시작 주소입니다.
/// - `size`는 매핑된 영역의 크기이며, 모든 접근은 이 범위 내에서 이루어집니다.
/// - 이 구조체가 존재하는 동안 MMIO 매핑은 해제되지 않습니다.
struct MmioRegion {
    base: *mut u8,
    size: usize,
}

impl MmioRegion {
    // safe 메서드: 범위 검사를 통해 안전성 보장
    fn read32(&self, offset: usize) -> Result<u32> {
        if offset + 4 > self.size {
            return Err(code::EINVAL);  // 범위 초과 방지
        }
        // SAFETY: offset이 범위 내임을 위에서 검증했고,
        //         base는 불변조건에 의해 유효한 MMIO 주소입니다.
        Ok(unsafe { core::ptr::read_volatile(self.base.add(offset) as *const u32) })
    }
}
💡

SAFETY 주석 규칙: 커널 Rust 코딩 가이드라인에 따라, 모든 unsafe 블록에는 // SAFETY: 주석으로 안전성 근거를 명시해야 합니다. 이는 커널 코딩 규칙이며, 누락 시 패치(Patch) 리뷰에서 거부됩니다. 구조체(Struct)에는 /// # Invariants 섹션으로 안전 불변조건을 문서화합니다.

⚠️

unsafe ≠ 위험: unsafe는 "이 코드의 안전성을 프로그래머가 보증합니다"는 의미이지, "이 코드가 위험하다"는 뜻이 아닙니다. 올바르게 작성된 unsafe 코드는 safe 코드와 동일하게 안전합니다. 핵심은 SAFETY 주석을 통해 그 근거를 투명하게 드러내는 것입니다.

Pin과 커널 초기화 패턴

커널 Rust에서는 Pin<T>을 활용한 in-place 초기화가 핵심 패턴입니다. C 커널의 많은 구조체(mutex, list_head, timer_list 등)는 초기화 후 메모리 주소가 고정되어야 하는 자기 참조 구조체입니다. Rust에서 이를 안전하게 다루기 위해 pin_init! 매크로와 PinInit 트레이트가 도입되었습니다.

왜 Pin 초기화가 필요한가?

문제C에서의 처리Rust Pin 초기화
mutex는 초기화 후 이동 불가mutex_init() 후 이동하지 않는 것은 프로그래머 책임Pin<T>이 이동을 타입 수준에서 방지
list_head는 자기 참조INIT_LIST_HEAD() 후 이동하면 댕글링#[pin] 필드는 이동 불가 보장
이동 시 메모리 주소 변경프로그래머가 주의해야 함컴파일러가 이동 시도를 차단
// === Pin 기반 초기화 패턴 ===
use kernel::sync::{new_mutex, Mutex};
use kernel::init::PinInit;

// #[pin_data]: 이 구조체에 Pin 필드가 있음을 선언
#[pin_data]
struct SharedState {
    #[pin]
    data: Mutex<Vec<u8>>,  // Pin 필드: 이동 불가
    name: CString,              // 일반 필드: 이동 가능
    count: u32,                 // 일반 필드
}

impl SharedState {
    // PinInit을 반환하는 생성자 — 아직 초기화되지 않은 "계획"을 반환
    fn new(name: &CStr) -> impl PinInit<Self> {
        pin_init!(Self {
            // <- 연산자: Pin 필드의 in-place 초기화
            data <- new_mutex!(Vec::new(), "SharedState::data"),
            // : 연산자: 일반 필드 초기화 (이동 가능)
            name: CString::try_from_fmt(fmt!("{}", name))?,
            count: 0,
        })
    }
}

// === 다양한 할당 방식 ===

// 1. Box::pin_init() — 힙 할당 + Pin 초기화
let state = Box::pin_init(SharedState::new(c_str!("example")))?;

// 2. Arc::pin_init() — 공유 힙 할당 + Pin 초기화
let shared = Arc::pin_init(SharedState::new(c_str!("shared")))?;

// 3. UniqueArc::pin_init() — 단독 소유 후 Arc로 변환
let unique = UniqueArc::pin_init(SharedState::new(c_str!("unique")))?;
let shared: Arc<SharedState> = unique.into();

pin_init! 매크로 문법

// pin_init! 매크로의 두 가지 초기화 연산자:
//
// field: value       — 일반 초기화 (값을 이동하여 대입)
// field <- init_expr — in-place 초기화 (Pin 필드용)
//                       init_expr은 PinInit 트레이트를 구현해야 함
//
// ? 사용 가능: 초기화 실패 시 에러 전파

#[pin_data]
struct DriverState {
    #[pin]
    lock: Mutex<DriverInner>,
    #[pin]
    timer: Timer<Self>,
    #[pin]
    work: Work<Self>,
    irq: u32,
    name: CString,
}

impl DriverState {
    fn new(irq: u32, name: &CStr) -> impl PinInit<Self> {
        pin_init!(Self {
            lock <- new_mutex!(DriverInner::default(), "DriverState::lock"),
            timer <- Timer::new(),
            work <- Work::new(),
            irq,                                             // 동명 축약
            name: CString::try_from_fmt(fmt!("{}", name))?,  // ? 에러 전파
        })
    }
}
💡

init vs pin_init: init! 매크로는 Pin이 필요 없는 일반 in-place 초기화에 사용합니다. pin_init!은 Pin 필드가 있는 구조체에 사용합니다. 두 매크로 모두 fallible(실패 가능)하며, ?로 에러를 전파할 수 있습니다. 커널에서는 메모리 할당이 실패할 수 있으므로, 이 fallible 특성이 필수적입니다.

에러 처리 (kernel::error)

커널 Rust는 C의 -ENOMEM, -EINVAL 등의 errno 패턴을 Result<T, Error>로 래핑합니다. C 반환값과 자동 변환되며, ? 연산자를 통해 에러가 호출 체인을 따라 자동 전파됩니다.

C vs Rust 에러 처리 비교

// C: 에러 처리를 잊으면 조용히 진행됨 (컴파일러가 강제하지 않음)
int probe(struct platform_device *pdev) {
    struct resource *res;
    void __iomem *base;
    int irq;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    // res가 NULL인지 확인하지 않으면? → NULL 역참조 → 커널 oops
    base = devm_ioremap_resource(&pdev->dev, res);
    // IS_ERR(base) 확인을 빼먹으면? → 잘못된 포인터 사용
    irq = platform_get_irq(pdev, 0);
    // irq < 0 확인을 빼먹으면? → 잘못된 IRQ 번호 사용
    return 0;
}
// Rust: Result를 처리하지 않으면 컴파일 에러
fn probe(dev: &mut platform::Device) -> Result<Self::Data> {
    let res = dev.get_resource(0).ok_or(code::ENXIO)?;   // None → ENXIO
    let base = res.ioremap()?;                            // 실패 시 자동 전파
    let irq = dev.get_irq(0).ok_or(code::ENXIO)?;        // None → ENXIO
    // ? 연산자: 에러가 있으면 즉시 Err를 반환
    // 에러 처리를 빼먹을 수 없음!
    Ok(Box::try_new(DeviceData { base, irq })?)
}

kernel::error API 상세

// 개념 예시: kernel::error 기반 errno 매핑
use kernel::error::{Error, Result, code};

fn allocate_resource(size: usize) -> Result<Vec<u8>> {
    if size == 0 {
        return Err(code::EINVAL);  // -EINVAL에 대응
    }
    if size > 1024 * 1024 {
        return Err(code::ENOMEM);  // 1MB 초과 시 거부
    }
    let mut v = Vec::new();
    v.try_reserve(size).map_err(|_| code::ENOMEM)?;  // 할당 실패 변환
    Ok(v)
}

// 에러 체이닝: 여러 fallible 연산 순차 실행
fn complex_init() -> Result {
    let buf = allocate_resource(4096)?;     // 실패 시 즉시 반환
    let dev = find_device()?;               // 실패 시 즉시 반환
    let irq = request_irq(&dev)?;           // 실패 시 즉시 반환
    // 모든 것이 성공해야 여기 도달
    Ok(())
}

// C에서 Rust 함수 호출 시 자동 변환:
// Ok(()) → 0, Ok(42) → 42, Err(EINVAL) → -EINVAL
C errnoRust code의미대표적 사용 상황
-ENOMEMcode::ENOMEM메모리 부족kmalloc/Vec 할당 실패
-EINVALcode::EINVAL잘못된 인자파라미터 검증 실패
-ENOENTcode::ENOENT엔트리 없음파일/디바이스 미발견
-EBUSYcode::EBUSY리소스 사용중디바이스 이미 열림
-EAGAINcode::EAGAIN다시 시도비차단(Non-blocking) I/O 시 데이터 없음
-EPERMcode::EPERM권한 없음CAP 검사 실패
-ENODEVcode::ENODEV디바이스 없음DT 노드 미발견
-ENXIOcode::ENXIO디바이스/주소 없음리소스/IRQ 미발견
-EFAULTcode::EFAULT잘못된 주소copy_from_user 실패
-ETIMEDOUTcode::ETIMEDOUT타임아웃하드웨어 응답 대기 초과
-ENOTTYcode::ENOTTY부적절한 ioctl지원하지 않는 ioctl 명령
💡

Result vs panic: 커널에서는 unwrap()이나 expect()로 인한 panic이 커널 oops를 유발합니다. 따라서 항상 ? 연산자와 Result를 사용하여 에러를 전파해야 합니다. 유일한 예외는 "이 상황이 발생하면 프로그램 로직 자체가 잘못된 것"이라고 확신할 때의 assert! 사용입니다.

Arc와 참조 카운팅

커널에서 여러 컨텍스트(스레드, 인터럽트(Interrupt) 핸들러(Handler), 워크큐 등)에서 공유해야 하는 객체는 Arc<T>(Atomic Reference Counted)로 래핑합니다. C의 refcount_t + kref를 Rust 타입 시스템으로 안전하게 추상화한 것입니다.

C 패턴Rust Arc 대응안전성 개선
refcount_inc()arc.clone()overflow 검사 포함, 0→1 전환 방지
refcount_dec_and_test()자동 (Drop 시 감소)감소/해제 원자적(Atomic) 보장
kref_put(release_fn)자동 Drop 호출release 함수 호출 누락 불가
kref_get() / put() 쌍clone() / drop 쌍RAII로 쌍 불일치 방지
// 개념 예시: Arc와 ArcBorrow 사용 패턴
use kernel::sync::{Arc, ArcBorrow};

#[pin_data]
struct SharedResource {
    #[pin]
    data: Mutex<Vec<u8>>,
    id: u32,
}

// === Arc 생성 (pin_init과 함께) ===
let resource = Arc::pin_init(pin_init!(SharedResource {
    data <- new_mutex!(Vec::new(), "SharedResource::data"),
    id: 1,
}))?;
// 이 시점에서 refcount = 1

// === clone(): 참조 카운트만 증가 (데이터 복사 없음) ===
let resource_clone = resource.clone();
// refcount = 2

// === ArcBorrow: 참조 카운트 증가 없이 임시 참조 ===
// C의 "빌려온 참조" 개념 — 참조 카운트 조작 오버헤드 없음
fn use_resource(res: ArcBorrow<'_, SharedResource>) {
    pr_info!("Using resource ID {}\\n", res.id);
    // ArcBorrow의 수명이 원본 Arc보다 짧음을 컴파일러가 보증
}

// === 콜백에서 Arc 사용 패턴 ===
// 타이머나 워크큐 콜백에 Arc를 전달하면
// 콜백 실행 중에도 객체가 살아있음을 보장
fn schedule_work(resource: &Arc<SharedResource>) {
    let cloned = resource.clone();  // refcount 증가
    // cloned를 워크큐/타이머에 전달
    // 콜백 완료 후 cloned가 drop되면 refcount 감소
}

// === Drop: 자동 자원 해제 ===
drop(resource_clone);  // refcount = 1
drop(resource);        // refcount = 0 → Drop::drop() 호출 → 메모리 해제
// C에서 kref_put() 호출을 잊어서 발생하는 메모리 누수 원천 차단
ℹ️

커널 Arc vs 표준 라이브러리 Arc: 커널의 kernel::sync::Arc는 std::sync::Arc와 유사하지만 차이가 있습니다. (1) new() 대신 try_new() / pin_init()로 생성 (할당 실패 처리), (2) ArcBorrow 타입으로 참조 카운트(Reference Count) 없는 빌림 지원, (3) Weak 참조는 이 문서 시점 기준으로 아직 제공되지 않습니다. 표준 라이브러리의 Arc는 커널에서 사용할 수 없습니다.

Rust misc 디바이스 드라이버

Misc(miscellaneous) 디바이스 드라이버는 커널에서 가장 간단한 문자 디바이스 모델로, /dev/에 자동으로 디바이스 노드가 생성됩니다. 주 번호(major number) 10을 공유하며, 부 번호(minor number)만 다릅니다. Rust misc 드라이버는 file::Operations 트레이트를 구현하고, #[vtable] 매크로가 C의 file_operations vtable을 자동 생성합니다.

Rust Misc 디바이스 드라이버 흐름 Rust Misc 디바이스 드라이버 흐름 사용자 공간의 open / read / write / ioctl이 system call을 거쳐 VFS → file_operations → Rust 핸들러로 전달됩니다 #[vtable] 매크로는 컴파일 타임에 Rust impl에서 C file_operations vtable을 생성하고, 등록·해제는 RAII로 자동화됩니다 사용자 공간 (User Space) 프로세스 open / read / write / ioctl /dev/rust_misc misc 노드 system call 커널 공간 (Kernel Space) VFS 계층 file_operations 호출 file_operations (C vtable) C가 정의한 함수 포인터 테이블 Safe Rust 드라이버 impl file::Operations #[vtable] 매크로 Rust impl → C vtable (컴파일 타임) Arc<DeviceState> — 공유 상태 여러 fd가 공유 · container_of 대체 misc는 주 번호 10을 공유하고 부 번호(minor)로 구분됩니다 모듈 생명 주기 (RAII) ① insmod Module::init() ② Registration 생성 misc_register() 자동 호출 ③ 활성 /dev/rust_misc 사용 가능 ④ rmmod → Drop misc_deregister() 자동 호출 등록 ②와 해제 ④ 모두 자동 — C처럼 module_init/module_exit 등록이 필요 없고, 에러 경로에서도 자원이 반드시 해제됩니다
사용자 open/read/write/ioctl → VFS → file_operations → Rust 핸들러의 런타임 호출 흐름과, #[vtable] 매크로가 컴파일 타임에 Rust impl에서 C file_operations vtable을 만들어 내는 빌드 타임 경로(점선)를 구분해 표시합니다. 등록·해제는 Module::init()·Drop이 misc_register()·misc_deregister()를 자동 호출하는 RAII 흐름을 따르므로, 에러 경로에서도 자원이 반드시 해제됩니다.
C 구조체/함수Rust 대응역할
struct miscdevicemiscdev::Registration<T>디바이스 등록/해제 (RAII)
struct file_operations#[vtable] impl file::Operationsopen/read/write/ioctl 핸들러
misc_register()Registration::new_pinned()디바이스 등록
misc_deregister()Drop 자동 호출모듈 해제 시 자동 등록 해제
container_of()Arc<DeviceState> 공유 상태파일 컨텍스트에서 디바이스 상태 접근
// 개념 예시: Rust misc 디바이스 드라이버 패턴
use kernel::prelude::*;
use kernel::{miscdev, file};
use kernel::sync::{Arc, Mutex, new_mutex};

module! {
    type: RustMiscDev,
    name: "rust_miscdev",
    author: "Developer",
    description: "Rust misc device driver example",
    license: "GPL",
}

#[pin_data]
struct DeviceState {
    #[pin]
    counter: Mutex<u64>,
}

#[vtable]
impl file::Operations for DeviceState {
    type Data = Arc<DeviceState>;

    fn open(shared: &Arc<DeviceState>, _file: &file::File) -> Result {
        let mut cnt = shared.counter.lock();
        *cnt += 1;
        pr_info!("Device opened {} times\\n", *cnt);
        Ok(())
    }

    fn read(shared: &Arc<DeviceState>, _file: &file::File,
            data: &mut impl file::IoBufferWriter, _offset: u64) -> Result<usize> {
        let cnt = shared.counter.lock();
        let msg = format!("count: {}\\n", *cnt);
        data.write_slice(msg.as_bytes())?;
        Ok(msg.len())
    }
}

struct RustMiscDev {
    _dev: Pin<Box<miscdev::Registration<DeviceState>>>,
}

impl kernel::Module for RustMiscDev {
    fn init(_module: &'static ThisModule) -> Result<Self> {
        let state = Arc::pin_init(pin_init!(DeviceState {
            counter <- new_mutex!(0u64, "DeviceState::counter"),
        }))?;
        let reg = miscdev::Registration::new_pinned(
            fmt!("rust_misc"), state)?;
        Ok(RustMiscDev { _dev: reg })
    }
}

misc 드라이버 동작 흐름

# 모듈 빌드 (커널 소스 트리에서)
make LLVM=1 M=samples/rust modules

# 모듈 로드
sudo insmod samples/rust/rust_miscdev.ko
dmesg | tail -3   # "rust_miscdev: loaded" 확인

# 디바이스 노드 확인
ls -la /dev/rust_misc    # crw------- 1 root root 10, 120 ...

# 디바이스 읽기
sudo cat /dev/rust_misc  # "count: 1" 출력

# 모듈 제거 (Drop → misc_deregister 자동 호출)
sudo rmmod rust_miscdev
💡

RAII 패턴: C에서는 module_exit()에서 명시적으로 misc_deregister()를 호출해야 합니다. Rust에서는 Registration의 Drop 구현이 자동으로 호출합니다. 에러 경로에서도 자원이 반드시 해제되므로, 자원 누수 버그가 원천 차단됩니다.

Platform 드라이버 예제

Platform 드라이버는 SoC 내장 디바이스(UART, SPI, I2C, GPIO 등)를 위한 드라이버 모델입니다. Device Tree와 연동되어 하드웨어 리소스(메모리 영역, IRQ, 클록)를 자동으로 바인딩합니다. C의 platform_driver_register() 패턴을 Rust의 트레이트와 매크로로 안전하게 표현합니다.

C 구조체/함수Rust 대응역할
struct platform_drivertrait platform::Driver드라이버 인터페이스
.probe()fn probe()디바이스 발견 시 초기화
.remove()fn remove() / Drop디바이스 제거 시 정리
platform_get_resource()dev.get_resource()메모리 리소스 획득
platform_get_irq()dev.get_irq()IRQ 번호 획득
of_match_table[]of_match_table: [...]Device Tree 호환 문자열
// 개념 예시: platform 드라이버 전체 구조
use kernel::{prelude::*, platform, of, io_mem::Resource};
use core::sync::atomic::{AtomicBool, Ordering};

// 디바이스 데이터 — probe()에서 생성, 드라이버 수명 동안 유지
struct DeviceData {
    mem: IoMem,               // 매핑된 MMIO 영역
    irq: u32,                  // IRQ 번호
    enabled: AtomicBool,       // 디바이스 활성 상태
}

struct MyPlatformDriver;

impl platform::Driver for MyPlatformDriver {
    type Data = Box<DeviceData>;

    fn probe(dev: &mut platform::Device, _id: Option<&Self::IdInfo>)
        -> Result<Self::Data>
    {
        pr_info!("Probing device: {}\\n", dev.name()?);

        // 1. Device Tree 노드 확인
        let _node = dev.of_node().ok_or(code::ENODEV)?;

        // 2. 메모리 리소스 획득 및 매핑
        let res = dev.get_resource(0).ok_or(code::ENXIO)?;
        let mem = res.ioremap()?;  // MMIO 영역 매핑

        // 3. IRQ 획득
        let irq = dev.get_irq(0).ok_or(code::ENXIO)?;

        // 4. 하드웨어 초기화
        // SAFETY: mem은 방금 매핑한 유효한 MMIO 영역
        unsafe { mem.writel(0x01, 0x00) };  // 디바이스 활성화

        pr_info!("Device probed: IRQ={}, mem={:#x}\\n", irq, res.start());

        // 5. 디바이스 데이터 생성 (힙 할당)
        Ok(Box::try_new(DeviceData {
            mem,
            irq,
            enabled: AtomicBool::new(true),
        })?)
    }

    fn remove(data: &Self::Data) {
        // 디바이스 비활성화
        data.enabled.store(false, Ordering::Relaxed);
        // SAFETY: probe()에서 매핑한 유효한 MMIO
        unsafe { data.mem.writel(0x00, 0x00) };  // 디바이스 비활성화
        pr_info!("Device removed\\n");
        // Box<DeviceData>가 drop되면서 메모리 자동 해제
    }
}

// 모듈 등록 매크로
kernel::module_platform_driver! {
    type: MyPlatformDriver,
    name: "my_platform_drv",
    author: "Developer",
    license: "GPL",
    of_match_table: [
        // Device Tree의 compatible 문자열과 매칭
        of::DeviceId::new(c_str!("vendor,my-device")),
        of::DeviceId::new(c_str!("vendor,my-device-v2")),
    ],
}

대응하는 Device Tree 바인딩

// Device Tree 예시 (.dts)
my_device@10000000 {
    compatible = "vendor,my-device";
    reg = <0x10000000 0x1000>;     // MMIO 영역 (base, size)
    interrupts = <0 42 4>;          // SPI, IRQ 42, level high
    clocks = <&clk_apb>;            // 클록 소스
    status = "okay";                // 디바이스 활성화
};
💡

RAII 리소스 관리: Rust platform 드라이버에서는 probe()에서 획득한 리소스(메모리 매핑(Mapping), IRQ 등)가 Data 타입에 의해 소유됩니다. 드라이버가 제거될 때 Drop이 자동 호출되어 리소스가 해제되므로, C에서 흔한 remove() 함수의 리소스 해제 누락 버그를 방지합니다.

PCI 드라이버 (Rust)

PCI 디바이스를 Rust로 제어하는 패턴입니다. BAR(Base Address Register) 매핑, 인터럽트 등록, DMA 설정 등 PCI 드라이버의 핵심 요소를 다룹니다. Rust로 PCI 드라이버를 작성할 때 특히 이점이 두드러지는 영역이 두 가지입니다. 첫째, BAR MMIO 매핑 영역은 IoMem<T> 타입으로 감싸지므로 probe()에서 획득한 MMIO 참조가 스코프를 벗어나면 자동으로 iounmap()이 호출됩니다 — C 드라이버에서 에러 경로에 iounmap()을 빠뜨리는 실수가 원천 차단됩니다. 둘째, IRQ 핸들러(Interrupt Handler)의 수명(Lifetime)이 디바이스 데이터 구조의 수명에 컴파일 타임으로 묶입니다 — C에서 free_irq() 전에 디바이스 데이터를 해제하여 발생하는 use-after-free가 타입 시스템에서 방지됩니다.

Rust PCI 드라이버 구조 Rust PCI 드라이버 구조 매칭은 C PCI 서브시스템이 DeviceId로 수행하고, 매칭에 성공하면 Rust 드라이버의 probe()가 호출됩니다 probe()에서 얻은 BAR · IRQ · DMA 자원은 Rust 타입이 소유하고, Drop에서 자동 해제됩니다 ① 등록과 매칭 — 드라이버 진입 경로 PCI 서브시스템 (C) pci_register_driver() DeviceId 매칭 probe() Rust PCI 드라이버 impl pci::Driver pci_device_table! 사용 Rust PCI 추상화 pci::Driver trait pci::Device · IoMem<T> MMIO 하드웨어 BAR · IRQ · DMA MMIO 레지스터 → 매칭에 성공하면 C 서브시스템이 Rust 드라이버의 probe()를 호출하고, 드라이버는 추상화 계층의 메서드로 하드웨어를 제어합니다 ② probe() 실행 순서 — Rust API와 C 대응 dev.enable_device()? 디바이스 활성화 C: pci_enable_device() dev.set_master() DMA 버스 마스터 C: pci_set_master() dev.iomap_region()? BAR 매핑 · RAII C: pci_iomap() Box::try_new(driver)? 드라이버 상태 생성 C: remove() 불필요 → 각 단계에서 얻은 자원은 그 타입이 소유하며(IoMem<T> · IRQ 핸들러), 실패 시 앞 단계까지의 자원도 Drop에서 해제됩니다 ③ RAII 이점 — Drop 시 자동 자원 해제 pci::Device 래퍼가 Drop되면 모든 리소스가 자동 해제 — C에서 remove()의 역순 cleanup 코드가 필요 없습니다 · IoMem<T> — MMIO 매핑이 스코프를 벗어나면 자동 iounmap(), 에러 경로 누수 차단 · IRQ 핸들러 — 수명이 디바이스 데이터와 컴파일 타임으로 결합되어 use-after-free 방지
① C PCI 서브시스템이 DeviceId로 매칭해 Rust 드라이버의 probe()를 호출하고, 드라이버는 rust/kernel/pci.rs 추상화(pci::Driver trait, pci::Device, IoMem<T>)를 거쳐 하드웨어를 제어합니다. ② probe()의 각 단계는 C API를 safe Rust 메서드로 감싼 것이며, ③ 획득한 자원은 타입이 소유하므로 Drop에서 자동 해제됩니다.
// 개념 예시: Rust PCI 드라이버 구조
use kernel::{prelude::*, pci};

struct MyPciDriver;

kernel::pci_device_table!(
    MY_PCI_TABLE,
    MODULE_PCI_TABLE,
    <MyPciDriver as pci::Driver>::IdInfo,
    [
        (pci::DeviceId::new(0x1234, 0x5678), ()),  // vendor:device ID
    ]
);

impl pci::Driver for MyPciDriver {
    type IdInfo = ();

    fn probe(
        dev: &mut pci::Device,
        _id: &pci::DeviceId,
        _info: &Self::IdInfo,
    ) -> Result<Box<Self>> {
        dev.enable_device()?;
        dev.set_master();

        // BAR 0 매핑
        let bar = dev.iomap_region(0, c_str!("my_pci_drv"))?;

        pr_info!("PCI device probed: {:04x}:{:04x}\\n",
            dev.vendor_id(), dev.device_id());

        Ok(Box::try_new(MyPciDriver)?)
    }
}
C PCI APIRust 대응Rust 이점
pci_enable_device()dev.enable_device()?에러 반환을 ? 로 자동 전파
pci_set_master()dev.set_master()DMA 버스(Bus) 마스터 활성화
pci_iomap()dev.iomap_region()?RAII — Drop 시 자동 unmap
pci_disable_device()Drop 자동 호출remove() 구현 불필요
MODULE_DEVICE_TABLE(pci, ...)pci_device_table!타입 안전한 ID 테이블
💡

RAII와 PCI remove(): C PCI 드라이버에서 remove() 콜백(Callback)은 pci_iounmap(), pci_release_regions(), pci_disable_device() 등을 역순으로 호출해야 합니다. Rust에서는 Box<MyPciDriver>가 Drop될 때 모든 RAII 래퍼가 자동으로 역순 해제되므로, 별도의 cleanup 코드가 필요 없습니다.

네트워크 드라이버 (Rust)

Rust로 네트워크 디바이스 드라이버를 작성하는 패턴입니다. PHY(Physical Layer) 드라이버가 Rust 네트워크 드라이버의 초기 진입점이며, 실제로 ax88796b PHY 드라이버가 Rust로 커널에 머지된 첫 사례입니다. PHY 드라이버는 비교적 단순한 구조이면서도 네트워크 스택(Network Stack)의 핵심 계층에 해당하여, Rust 드라이버의 실용성을 증명하는 중요한 이정표입니다.

네트워크 드라이버 계층Rust 지원 상태주요 API
PHY 드라이버안정적 (6.8+)kernel::net::phy
NIC 드라이버개발 중kernel::net::dev (진행 중)
소켓(Socket)/프로토콜미지원향후 계획
// 실제 사례 기반: Rust PHY 드라이버 (ax88796b)
// 이 드라이버는 Linux 6.8에서 머지된 최초의 Rust 네트워크 PHY 드라이버입니다.
use kernel::{prelude::*, net::phy};

kernel::module_phy_driver! {
    drivers: [PhyAX88796B],
    device_table: [
        phy::DeviceId::new_with_driver::<PhyAX88796B>(),
    ],
    name: "ax88796b_rust",
    author: "FUJITA Tomonori",
    description: "Rust Asix PHY driver",
    license: "GPL",
}

struct PhyAX88796B;

#[vtable]
impl phy::Driver for PhyAX88796B {
    // PHY 디바이스 이름과 ID
    const NAME: &CStr = c_str!("Asix Electronics AX88796B");
    const PHY_DEVICE_ID: phy::DeviceId =
        phy::DeviceId::new_with_exact_mask(0x003b1841);

    // 소프트 리셋 — genphy 기본 구현 위임
    fn soft_reset(dev: &mut phy::Device) -> Result {
        dev.genphy_soft_reset()
    }

    // 링크 상태 읽기
    fn read_status(dev: &mut phy::Device) -> Result {
        dev.genphy_read_status()
    }
}

PHY 드라이버 API 상세

// PHY 드라이버가 구현할 수 있는 주요 콜백들
#[vtable]
impl phy::Driver for MyPhyDriver {
    const NAME: &CStr = c_str!("My Custom PHY");
    const PHY_DEVICE_ID: phy::DeviceId =
        phy::DeviceId::new_with_exact_mask(0xDEADBEEF);

    // 초기화 — 디바이스 발견 시 호출
    fn config_init(dev: &mut phy::Device) -> Result {
        pr_info!("PHY config init\\n");
        // 레지스터 초기화
        dev.write(0x1F, 0x0000)?;  // 페이지 0 선택
        dev.write(0x00, 0x1140)?;  // 기본 설정
        Ok(())
    }

    // 소프트 리셋
    fn soft_reset(dev: &mut phy::Device) -> Result {
        // BMCR 레지스터의 리셋 비트 설정
        dev.write(0x00, 0x8000)?;

        // 리셋 완료 대기 (비트가 자동으로 클리어됨)
        let mut retries = 100;
        while retries > 0 {
            let val = dev.read(0x00)?;
            if val & 0x8000 == 0 {
                return Ok(());
            }
            retries -= 1;
        }
        Err(code::ETIMEDOUT)
    }

    // 링크 상태 읽기
    fn read_status(dev: &mut phy::Device) -> Result {
        dev.genphy_read_status()?;

        // 추가 상태 정보 읽기 (벤더 특화)
        let ext_status = dev.read(0x1B)?;
        if ext_status & 0x0100 != 0 {
            pr_info!("Link partner supports EEE\\n");
        }
        Ok(())
    }

    // 서스펜드/리줌 — 전력 관리
    fn suspend(dev: &mut phy::Device) -> Result {
        dev.genphy_suspend()
    }

    fn resume(dev: &mut phy::Device) -> Result {
        dev.genphy_resume()
    }
}
ℹ️

PHY 레지스터(Register) 접근: dev.read(reg)/dev.write(reg, val)은 내부적으로 MDIO 버스를 통해 PHY 칩의 레지스터에 접근합니다. 이 함수들은 safe Rust로 래핑되어 있으며, 버스 잠금(Lock)과 타이밍을 자동으로 처리합니다. 레지스터 주소 0x00~0x1F는 IEEE 802.3 표준으로 정의된 공통 레지스터이고, 0x10 이상은 벤더별 확장입니다.

Workqueue 사용

Workqueue는 프로세스(Process) 컨텍스트에서 비동기 작업을 실행하는 메커니즘입니다. 인터럽트 핸들러나 타이머(Timer) 콜백에서 슬립(Sleep)이 필요한 작업(메모리 할당, mutex 잠금, I/O 등)을 수행할 때 사용합니다. C의 INIT_WORK()/schedule_work()에 대응합니다.

Workqueue 종류C APIRust 추상화특징
시스템 WQsystem_wqworkqueue::system()범용, 공유 워커 풀
고우선순위 WQsystem_highpri_wqworkqueue::system_highpri()높은 우선순위(Priority) 작업
정렬 WQalloc_ordered_workqueue()커스텀 생성순서 보장(Ordering) (직렬 실행)
// 개념 예시: Workqueue 패턴
use kernel::workqueue::{self, Work, WorkItem};
use kernel::sync::Arc;
use core::sync::atomic::{AtomicU64, Ordering};

#[pin_data]
struct MyWorkItem {
    #[pin]
    work: Work<MyWorkItem>,   // Work 구조체 (자기참조 → Pin 필수)
    counter: AtomicU64,         // 실행 카운터
    message: CString,           // 작업 관련 데이터
}

impl WorkItem for MyWorkItem {
    type Pointer = Arc<Self>;  // 소유권 관리 포인터

    fn run(this: Arc<Self>) {
        // 프로세스 컨텍스트에서 실행됨
        // → 슬립, mutex, 메모리 할당 모두 가능
        let val = this.counter.fetch_add(1, Ordering::Relaxed);
        pr_info!("Work executed (counter={}): {}\\n", val, &*this.message);

        // 슬립 가능한 작업 수행 예시
        // let mut guard = some_mutex.lock(); // OK!
        // let buf = Vec::try_with_capacity(1024)?; // OK!
    }
}

// === 워크 생성 및 스케줄 ===
let item = Arc::pin_init(pin_init!(MyWorkItem {
    work <- Work::new(),
    counter: AtomicU64::new(0),
    message: CString::try_from_fmt(fmt!("batch job"))?,
}))?;

// 시스템 workqueue에 작업 등록
workqueue::system().enqueue(item.clone());

// === 인터럽트 핸들러에서 하단부(bottom half) 위임 패턴 ===
// 인터럽트 핸들러 (하드 IRQ):
//   1. 하드웨어 상태 읽기 (빠른 작업)
//   2. workqueue에 처리 작업 등록
//   3. IRQ 해제
// Workqueue 콜백 (프로세스 컨텍스트):
//   1. 데이터 처리, 메모리 할당
//   2. 사용자 공간 통지
💡

타이머 vs Workqueue: 타이머 콜백은 softirq 컨텍스트에서 실행되어 빠르지만 슬립 불가. Workqueue는 프로세스 컨텍스트에서 실행되어 느리지만 슬립 가능. 인터럽트 핸들러에서 시간이 오래 걸리는 작업은 Workqueue로 위임(bottom-half 패턴)하세요.

타이머 사용

커널 Rust에서 타이머는 지연(Latency)된 작업 실행에 사용됩니다. C의 timer_list와 hrtimer에 대응하는 Rust 추상화가 제공되며, TimerCallback 트레이트를 구현하여 콜백 동작을 정의합니다. 타이머 구조체는 자기 참조이므로 Pin으로 고정해야 합니다.

타이머 종류C APIRust 추상화해상도용도
일반 타이머timer_listkernel::time::Timerjiffies 단위 (보통 1~10ms)주기적 점검, 타임아웃
고해상도 타이머(hrtimer)hrtimerkernel::time::hrtimer (6.15+)나노초정밀 타이밍, 주기적 인터럽트
지연 작업delayed_workWorkqueue + 타이머 조합jiffies지연된 프로세스 컨텍스트 작업
// 개념 예시: Timer 패턴 — 주기적 타이머
use kernel::time::{Timer, TimerCallback, Jiffies};
use kernel::sync::Arc;
use core::sync::atomic::{AtomicU32, Ordering};

#[pin_data]
struct TimerData {
    #[pin]
    timer: Timer<Self>,           // 타이머 구조체 (자기참조 → Pin 필수)
    count: AtomicU32,              // 발동 횟수 카운터
    max_count: u32,               // 최대 실행 횟수 (0이면 무한)
}

impl TimerCallback for TimerData {
    type Pointer = Arc<Self>;    // 콜백에서 사용할 포인터 타입

    fn run(this: Arc<Self>) {
        let cnt = this.count.fetch_add(1, Ordering::Relaxed);
        pr_info!("Timer fired {} times\\n", cnt + 1);

        // max_count가 0이면 무한 반복, 아니면 횟수 제한
        if this.max_count == 0 || cnt + 1 < this.max_count {
            // 1초 후 다시 스케줄 (주기적 타이머)
            this.timer.schedule(Jiffies::from_secs(1));
        } else {
            pr_info!("Timer stopped after {} executions\\n", cnt + 1);
        }
    }
}

// 타이머 생성 및 시작
let timer_data = Arc::pin_init(pin_init!(TimerData {
    timer <- Timer::new(),
    count: AtomicU32::new(0),
    max_count: 10,  // 10번 발동 후 자동 중지
}))?;

// 최초 스케줄: 500ms 후 첫 실행
timer_data.timer.schedule(Jiffies::from_millis(500));

// 타이머 취소 (필요 시)
// timer_data.timer.cancel();

// Arc가 drop되면 타이머도 자동 정리 (RAII)
⚠️

타이머 콜백은 인터럽트 컨텍스트(softirq)에서 실행됩니다. 따라서 콜백 내에서 슬립이 가능한 연산(mutex 잠금, 메모리 할당 등)을 수행할 수 없습니다. 슬립이 필요한 작업은 Workqueue와 조합하여 프로세스 컨텍스트에서 실행해야 합니다.

💡

RAII와 타이머: Rust의 Drop 트레이트 덕분에, 타이머를 소유한 구조체가 해제될 때 타이머도 자동으로 취소됩니다. C에서는 del_timer_sync()를 명시적으로 호출해야 했지만, Rust에서는 이를 잊을 수 없습니다.

커널 실행 컨텍스트 비교

컨텍스트메커니즘슬립 가능선점(Preemption) 가능사용 API
하드 IRQ인터럽트 핸들러❌❌SpinLock, atomic ops
Softirq/Tasklet타이머 콜백, 네트워크 RX❌❌SpinLock, atomic ops
WorkqueueWorkItem::run()✅✅Mutex, Vec::try_push, I/O
프로세스시스템 콜(System Call), ioctl✅✅모든 safe API
kthread커널 쓰레드✅✅모든 safe API

UserSlice: 사용자 공간(User Space) 통신

커널과 사용자 공간 사이의 데이터 전송은 UserSlice를 통해 안전하게 수행됩니다. C의 copy_from_user()/copy_to_user()에 대응하며, Rust 타입 시스템으로 방향성(읽기/쓰기)과 경계를 강제합니다.

C APIRust 추상화방향설명
copy_to_user()IoBufferWriter::write_slice()커널 → 사용자커널 데이터를 사용자 버퍼(Buffer)에 복사
copy_from_user()IoBufferReader::read_slice()사용자 → 커널사용자 데이터를 커널 버퍼로 복사
get_user()UserSliceReader::read()사용자 → 커널단일 값 읽기
put_user()UserSliceWriter::write()커널 → 사용자단일 값 쓰기
// 개념 예시: UserSlice 기반 사용자 공간 I/O 패턴
use kernel::user_ptr::{UserSlice, UserSliceReader, UserSliceWriter};

// === 파일 read 구현 (커널 → 사용자) ===
fn read(_data: &Self::Data, _file: &File,
    data: &mut impl IoBufferWriter, offset: u64
) -> Result<usize> {
    // offset != 0이면 이미 전체를 읽은 것이므로 EOF 반환
    if offset != 0 { return Ok(0); }

    let msg = b"Hello from Rust kernel!\\n";

    // 사용자 버퍼가 충분한지 확인
    if data.len() < msg.len() {
        return Err(code::EINVAL);
    }

    // 커널 데이터를 사용자 공간으로 안전하게 복사
    // 내부적으로 copy_to_user()를 호출하며,
    // 잘못된 사용자 주소 시 -EFAULT를 반환
    data.write_slice(msg)?;
    Ok(msg.len())
}

// === 파일 write 구현 (사용자 → 커널) ===
fn write(shared: &Self::Data, _file: &File,
    data: &mut impl IoBufferReader, _offset: u64
) -> Result<usize> {
    let len = data.len();

    // 최대 크기 제한 (커널 메모리 과도 사용 방지)
    if len > 4096 {
        return Err(code::EINVAL);
    }

    // 커널 버퍼 할당 (실패 가능 → try_resize)
    let mut buffer = Vec::new();
    buffer.try_resize(len, 0u8)?;

    // 사용자 데이터를 커널 버퍼로 안전하게 복사
    data.read_slice(&mut buffer)?;

    pr_info!("Received {} bytes from user: {:?}\\n", len,
        &buffer[..core::cmp::min(len, 32)]);  // 최대 32바이트만 출력
    Ok(len)
}

// === ioctl에서 UserSlice 직접 사용 ===
fn ioctl(shared: &Self::Data, _file: &File,
    cmd: u32, arg: usize
) -> Result<i32> {
    match cmd {
        0x1001 => {
            // 사용자 포인터에서 구조체 읽기
            let slice = UserSlice::new(arg, core::mem::size_of::<MyIoctlData>());
            let mut reader = slice.reader();
            let mut data = MyIoctlData::default();
            reader.read_raw(&mut data)?;
            // ... data 처리 ...
            Ok(0)
        },
        _ => Err(code::ENOTTY),
    }
}
⚠️

사용자 포인터 안전성: 사용자 공간 포인터는 항상 신뢰할 수 없습니다. Rust의 UserSlice 추상화는 (1) 잘못된 주소 시 -EFAULT 반환, (2) 경계를 초과한 접근 방지, (3) 방향성(읽기/쓰기) 타입 레벨 강제를 보장합니다. 절대로 사용자 포인터를 raw pointer로 직접 역참조(Dereference)하지 마세요.

Custom Trait 구현

커널 Rust 드라이버에서 Custom Trait은 하드웨어 추상화 인터페이스를 정의하는 핵심 패턴입니다. C 커널의 struct file_operations, struct platform_driver 같은 vtable 패턴을 Rust의 트레이트로 표현하면 타입 안전한 다형성을 얻을 수 있습니다.

C 커널 패턴Rust 트레이트 패턴장점
struct file_operationstrait FileOperations미구현 함수 포인터 NULL 접근 방지
struct platform_drivertrait platform::Driver필수 메서드 구현 강제
함수 포인터 콜백트레이트 메서드 + #[vtable]컴파일 타임 타입 검사
// 개념 예시: 디바이스 공통 인터페이스 trait
trait HardwareDevice {
    // 필수 메서드 — 구현체가 반드시 제공해야 함
    fn init(&mut self) -> Result;
    fn read_register(&self, offset: u32) -> Result<u32>;
    fn write_register(&mut self, offset: u32, value: u32) -> Result;

    // 연관 상수 — 디바이스별 메타데이터
    const VENDOR_ID: u32;
    const DEVICE_NAME: &'static str;

    // 기본 구현 — 오버라이드 가능
    fn reset(&mut self) -> Result {
        self.write_register(0x0, 0x1)?;
        pr_info!("Device {} reset\\n", Self::DEVICE_NAME);
        Ok(())
    }

    // 기본 구현: 상태 덤프
    fn dump_status(&self) -> Result {
        let status = self.read_register(0x0)?;
        pr_info!("[{}] status: {:#x}\\n", Self::DEVICE_NAME, status);
        Ok(())
    }
}

// UART 디바이스 구현
struct UartDevice { base_addr: usize }

impl HardwareDevice for UartDevice {
    const VENDOR_ID: u32 = 0x1234;
    const DEVICE_NAME: &'static str = "UART-16550";

    fn init(&mut self) -> Result {
        // UART 초기화: FIFO 활성화 + 8N1 설정
        self.write_register(0x10, 0x8)?;  // FCR: FIFO Enable
        self.write_register(0x14, 0x3)?;  // LCR: 8 data bits
        Ok(())
    }
    fn read_register(&self, offset: u32) -> Result<u32> {
        // SAFETY: base_addr은 probe()에서 검증된 유효한 MMIO 주소이며,
        //         이 참조가 유효한 동안 base_addr 영역은 매핑 상태를 유지합니다.
        unsafe {
            Ok(kernel::io::readl((self.base_addr + offset as usize) as *const u32))
        }
    }
    fn write_register(&mut self, offset: u32, value: u32) -> Result {
        // SAFETY: 위와 동일한 근거
        unsafe {
            kernel::io::writel(value, (self.base_addr + offset as usize) as *mut u32);
        }
        Ok(())
    }
}

// SPI 디바이스 — 같은 trait, 다른 구현
struct SpiDevice { base_addr: usize, cs_pin: u8 }

impl HardwareDevice for SpiDevice {
    const VENDOR_ID: u32 = 0x5678;
    const DEVICE_NAME: &'static str = "SPI-Controller";

    fn init(&mut self) -> Result {
        self.write_register(0x00, 0x01)?;  // SPI Enable
        self.write_register(0x04, 0x07)?;  // Clock divider
        Ok(())
    }
    // read_register, write_register 구현 생략 (유사 패턴)
    fn read_register(&self, offset: u32) -> Result<u32> { /* ... */ Ok(0) }
    fn write_register(&mut self, _offset: u32, _value: u32) -> Result { Ok(()) }

    // reset을 오버라이드 — SPI는 CS 핀도 리셋해야 함
    fn reset(&mut self) -> Result {
        self.write_register(0x0, 0x1)?;
        pr_info!("SPI CS pin {} deasserted\\n", self.cs_pin);
        Ok(())
    }
}

// 트레이트 객체 사용 — 런타임 다형성
fn initialize_devices(devices: &mut [&mut dyn HardwareDevice]) -> Result {
    for dev in devices.iter_mut() {
        dev.init()?;
        dev.dump_status()?;
    }
    Ok(())
}

#[vtable] 매크로 상세

커널의 #[vtable] 절차적 매크로는 Rust 트레이트를 C의 vtable(함수 포인터 구조체)로 자동 변환합니다. 이 매크로가 생성하는 코드를 이해하면 커널 Rust의 C FFI 브릿지를 더 잘 파악할 수 있습니다.

// #[vtable] 매크로가 하는 일:
// 1. 트레이트의 각 메서드에 대응하는 C 함수 포인터를 생성
// 2. 선택적 메서드(기본 구현)는 HAS_xxx 상수로 존재 여부 추적
// 3. NULL 대신 기본 구현을 C 측에 전달

// Rust 측: 트레이트 정의
#[vtable]
trait FileOperations {
    // 필수 메서드: HAS_OPEN = true (항상)
    fn open(&self) -> Result;

    // 선택적 메서드: HAS_READ = 구현 여부에 따라 true/false
    fn read(&self, _buf: &mut [u8]) -> Result<usize> {
        Err(code::EINVAL)  // 기본 구현
    }
}

// 매크로가 자동 생성하는 코드 (개념):
// impl<T: FileOperations> T {
//     const HAS_OPEN: bool = true;
//     const HAS_READ: bool = /* 구현체가 read를 오버라이드했는지 */;
// }
//
// C 측 vtable 구성:
// struct file_operations {
//     .open = Some(rust_open_wrapper),
//     .read = if T::HAS_READ { Some(rust_read_wrapper) } else { None },
// }
#[vtable] 기능설명C 대응
필수 메서드미구현 시 컴파일 에러NULL이면 런타임 에러
선택적 메서드기본 구현으로 안전한 폴백NULL 검사 필요
HAS_xxx 상수C 측에서 함수 존재 여부 확인수동 NULL 비교
타입 검사시그니처 불일치 → 컴파일 에러잘못된 포인터 할당 가능
ℹ️

#[vtable]의 핵심 가치: C에서 file_operations.read = NULL이면 커널이 -EINVAL을 반환하는 것이 기본 동작이지만, 이 동작은 호출 시점에서만 확인됩니다. Rust의 #[vtable]은 미구현 필수 메서드를 컴파일 타임에 잡아내고, 선택적 메서드는 안전한 기본 구현을 자동 제공합니다.

커널 Rust 테스팅 (KUnit, rustdoc)

커널 Rust 코드는 세 가지 레벨의 테스트를 지원합니다: KUnit 단위 테스트(커널 내부 실행), rustdoc 문서 테스트(문서 내 코드 검증), 통합 테스트(모듈 로드/언로드). 각 테스트 방법의 특성과 적합한 사용 시나리오를 이해하면 효과적인 테스트 전략을 수립할 수 있습니다.

커널 Rust 테스팅 3단계 파이프라인 커널 Rust 테스팅 3단계 파이프라인 세 가지 테스트 레벨을 빠른 피드백 → 느린 검증 순으로 실행합니다 — 각 단계는 이전 단계가 통과한 뒤에만 진행됩니다 Level 1 · rustdoc make LLVM=1 rustdoctest /// # Examples / ```rust 블록 실행 문서-코드 동기화 + compile_fail 검증 속도: 매우 빠름 검증 대상 · 공개 API 사용법 · 문서 예제 컴파일 Level 2 · KUnit kunit.py run --arch=um #[cfg(CONFIG_KUNIT)] mod tests 커널 내부에서 #[test] 어서션 실행 속도: 빠름 검증 대상 · 내부 함수 로직 · 에러 코드 · 할당 실패 Level 3 · 통합 테스트 QEMU + insmod / rmmod 실제 하드웨어 I/O · 디바이스 open / read / write 사용자 공간 속도: 느림 검증 대상 · 모듈 로드/언로드 · 하드웨어 상호작용 실행 순서: Level 1 → 2 → 3 — 이전 단계가 통과하면 다음 단계가 진행되며, 어떤 단계라도 실패하면 즉시 중단합니다 CI 파이프라인 명령어 예시 (순서대로 실행) ① make LLVM=1 rustdoctest # 1단계 · 문서 테스트 (매우 빠름) ② make LLVM=1 CLIPPY=1 # 2단계 · clippy 린트 검사 ③ python3 tools/testing/kunit/kunit.py run --arch=um # 2단계 · KUnit 단위 테스트 ④ qemu-system-x86_64 ... # 3단계 · QEMU 부팅 + insmod/rmmod 통합 테스트
커널 Rust 코드는 rustdoc 문서 테스트 → KUnit 단위 테스트 → QEMU 통합 테스트 3단계로 검증됩니다. CI에서는 clippy 린트 검사가 KUnit과 같은 2단계에 포함되며, 각 단계는 이전 단계가 통과한 뒤에만 진행되고 실패 시 즉시 중단됩니다.
테스트 방법실행 환경대상속도커버리지
KUnit커널 내부 (부팅 시 또는 UML)내부 로직, 유닛 테스트빠름개별 함수/모듈
rustdoc호스트 (문서 빌드 시)API 사용 예제 검증매우 빠름공개 API
통합 테스트실제 커널/QEMU모듈 로드, 디바이스 I/O느림전체 동작

KUnit 테스트 작성

// KUnit 테스트 — 커널 내부에서 실행
// #[cfg(CONFIG_KUNIT)]으로 테스트 빌드에서만 포함
#[cfg(CONFIG_KUNIT)]
mod tests {
    use kernel::prelude::*;
    use kernel::kunit;

    // 기본 어서션 테스트
    #[test]
    fn test_vec_push() {
        let mut v = Vec::new();
        v.try_push(42).expect("push failed");
        assert_eq!(v.len(), 1);
        assert_eq!(v[0], 42);
    }

    // 에러 코드 검증 테스트
    #[test]
    fn test_error_codes() {
        use kernel::error::code;
        let err: Result<()> = Err(code::EINVAL);
        assert!(err.is_err());

        // Result 변환 검증
        let ok: Result<i32> = Ok(42);
        assert_eq!(ok.unwrap(), 42);
    }

    // 커널 메모리 할당 테스트
    #[test]
    fn test_box_allocation() {
        let boxed = Box::try_new(100u64);
        assert!(boxed.is_ok());
        assert_eq!(*boxed.unwrap(), 100);
    }

    // 문자열 처리 테스트
    #[test]
    fn test_cstring() {
        let s = CString::try_from_fmt(fmt!("hello {}", 42));
        assert!(s.is_ok());
    }
}

rustdoc 테스트

Rust 문서 주석(///) 내의 코드 블록은 자동으로 테스트됩니다. 커널에서는 make rustdoctest로 실행합니다. 이 방식은 문서와 코드의 동기화를 보장합니다.

// rustdoc 테스트 — 문서 내 코드가 자동으로 테스트됨
/// 두 숫자를 더합니다.
///
/// # Examples
///
/// ```
/// use kernel::prelude::*;
/// assert_eq!(add(2, 3), 5);
/// assert_eq!(add(-1, 1), 0);
/// ```
///
/// # Errors
///
/// 이 함수는 에러를 반환하지 않습니다.
pub fn add(a: i32, b: i32) -> i32 { a + b }

/// 실패 케이스를 테스트하는 문서 테스트
///
/// ```should_panic
/// // should_panic 어트리뷰트: panic이 발생해야 통과
/// let v: Vec<i32> = Vec::new();
/// let _ = v[0]; // index out of bounds → panic
/// ```
///
/// ```compile_fail
/// // compile_fail: 컴파일이 실패해야 통과
/// let x: u32 = -1_i32; // 타입 불일치
/// ```

테스트 실행 명령

# === KUnit 테스트 실행 ===

# 방법 1: UML(User-Mode Linux)에서 빠르게 실행
./tools/testing/kunit/kunit.py run --arch=um
# Rust 관련 테스트만 필터링
./tools/testing/kunit/kunit.py run --arch=um --filter="rust_*"

# 방법 2: 전체 커널 빌드와 함께 실행
# Kconfig: CONFIG_KUNIT=y, CONFIG_RUST=y
make LLVM=1 -j$(nproc)
# 부팅 시 자동으로 KUnit 테스트 실행, dmesg로 결과 확인
dmesg | grep -A5 "kunit"

# === Rust 문서 테스트 ===
make LLVM=1 rustdoctest

# === Rust 커널 문서 생성 ===
make LLVM=1 rustdoc
# 결과: Documentation/output/rust/

# === 통합 테스트: QEMU에서 모듈 로드 ===
# 1. 모듈 빌드
make LLVM=1 M=samples/rust -j$(nproc)
# 2. QEMU로 커널 부팅 후
insmod rust_minimal.ko
dmesg | tail -5  # "Rust minimal sample (init)" 확인
rmmod rust_minimal
dmesg | tail -5  # "Rust minimal sample (exit)" 확인

테스트 어서션 매크로 비교

매크로사용처동작예시
assert!(expr)일반false이면 panicassert!(v.len() > 0)
assert_eq!(a, b)일반a ≠ b이면 panic + 양쪽 값 출력assert_eq!(2+2, 4)
assert_ne!(a, b)일반a = b이면 panicassert_ne!(ptr, null_mut())
debug_assert!()일반debug 빌드에서만 검사debug_assert!(idx < len)
kunit::assert!()KUnit실패해도 panic 않고 테스트 실패 보고KUnit 테스트 내부 전용
💡

테스트 전략 권장: (1) 내부 로직은 KUnit으로 단위 테스트, (2) 공개 API는 rustdoc 테스트로 예제 겸 검증, (3) 하드웨어 상호작용은 QEMU 통합 테스트로 검증하세요. KUnit은 UML에서 실행하면 실제 하드웨어 없이도 빠르게 피드백을 받을 수 있습니다. 커널 코드에서는 assert!가 panic을 유발하므로, KUnit 전용 어서션을 사용하는 것이 좋습니다.

Clippy 린트와 커널 코딩 스타일(Coding Style)

커널 Rust 코드는 make LLVM=1 CLIPPY=1으로 Clippy 린트를 실행합니다. 커널 빌드 시스템(Build System)은 기본적으로 여러 Clippy 경고를 에러로 취급하며, 커널 특화 린트 규칙이 적용됩니다.

Clippy 린트규칙커널 적용
clippy::dbg_macrodbg!() 사용 금지pr_debug! 사용
clippy::unwrap_usedunwrap() 사용 금지? 또는 ok_or() 사용
clippy::expect_usedexpect() 사용 금지? 또는 ok_or() 사용
clippy::needless_return불필요한 return 제거표현식 기반 반환 사용
clippy::redundant_closure불필요한 클로저 래핑 제거함수 참조 직접 전달
# Clippy 실행 (커널 전체)
make LLVM=1 CLIPPY=1

# 특정 모듈만 Clippy 검사
make LLVM=1 CLIPPY=1 M=samples/rust

# rustfmt으로 코드 포맷팅
make LLVM=1 rustfmt

# rust-analyzer 설정 생성 (IDE 지원)
make LLVM=1 rust-analyzer

커널 Rust 문서 주석 규칙

/// 디바이스 레지스터를 읽습니다.
///
/// `offset`에 해당하는 32비트 레지스터 값을 반환합니다.
/// offset은 MMIO 영역 내에 있어야 합니다.
///
/// # Errors
///
/// offset이 MMIO 영역을 벗어나면 [`EINVAL`]을 반환합니다.
///
/// # Examples
///
/// ```
/// let val = device.read32(0x00)?;
/// pr_info!("Status: {:#x}\n", val);
/// ```
///
/// # Safety (unsafe fn인 경우)
///
/// 호출자는 `self.base`가 유효한 MMIO 매핑임을 보장해야 합니다.
pub fn read32(&self, offset: usize) -> Result<u32> {
    // 구현...
    Ok(0)
}
ℹ️

문서 주석 필수 섹션: 커널 Rust 코딩 가이드에 따라 공개 API에는 (1) 함수 설명, (2) # Errors (에러 반환 가능 시), (3) # Examples (사용 예시), (4) # Safety (unsafe fn인 경우)를 작성해야 합니다. # Panics 섹션은 panic 가능 조건을 명시하지만, 커널에서는 panic을 최소화해야 합니다.

커널 Rust 디버깅 (panic, oops, GDB)

커널 Rust 코드의 디버깅은 C 코드와 유사하지만, Rust의 심볼 이름이 맹글링(name mangling)되어 있어 추가 도구가 필요합니다. Rust v0 맹글링 형식을 이해하고, 적절한 도구를 사용하면 효과적으로 디버깅할 수 있습니다.

커널 Rust 디버깅 워크플로 커널 Rust 디버깅 워크플로 맹글링된 Rust 심볼을 rustfilt·addr2line으로 해독해 소스 위치를 찾아 수정까지 이어지는 5단계 흐름과 증상별 도구·설정 가이드 ① 5단계 디버깅 워크플로 ① 버그 발생 oops / panic 콜스택 확보 ② dmesg 수집 콜스택 + 맹글링 심볼 확인 ③ 디맹글링 ★ rustfilt → 심볼 해독 addr2line → 파일:라인 ④ 심층 분석 GDB / ftrace 원인 추적 ⑤ 수정 패치 작성 재검증 → 실무 요령: dmesg | rustfilt 한 줄 파이프로 맹글링 심볼을 즉시 해독할 수 있습니다 (사용법은 아래 도구 표 참고) ② 증상별 디버깅 도구 선택 panic / oops · dmesg + rustfilt · addr2line -f -i 메모리 오류 · KASAN · KMSAN · unsafe 영역 검토 로직 오류 · pr_info! · pr_debug! · GDB + QEMU 잠금 / 데드락 · lockdep · ftrace 성능 이슈 · perf record / stat · ftrace + flamegraph → safe Rust는 메모리 오류를 컴파일 타임에 차단하므로, 메모리 오류가 나면 unsafe 영역과 FFI 경계부터 의심합니다 ③ 필수 Kconfig 설정 가이드 D · 디버그 심볼 CONFIG_DEBUG_INFO=y CONFIG_RUST_DEBUG_ASSERTIONS=y M · 메모리 / 동기화 CONFIG_KASAN=y (메모리) CONFIG_LOCKDEP=y (잠금 순서) T · 추적 / 프로파일링 CONFIG_FTRACE=y (추적) CONFIG_FUNCTION_TRACER=y 부트 파라미터: nokaslr (GDB 안정화) · dyndbg="+p" (동적 디버그) · kasan.fault=panic (즉시 중단)
커널 Rust 디버깅은 ① oops/panic 콜스택 확보 → ② dmesg 수집 → ③ 디맹글링(★ 핵심 단계: rustfilt·addr2line) → ④ 심층 분석 → ⑤ 수정·재검증의 5단계로 진행하며, 증상별 도구(KASAN·lockdep·perf 등)와 Kconfig 옵션(DEBUG_INFO·FTRACE·nokaslr 등)을 조합해 원인을 추적합니다.
디버깅 도구용도Rust 지원 수준주요 명령/사용법
rustfiltRust 심볼 디맹글링완벽dmesg | rustfilt
addr2line주소→소스 변환완벽addr2line -e vmlinux -f 0x...
GDB대화형 디버깅좋음 (타입 정보 지원)break rust_module::init
ftrace함수 호출 추적(Call Trace)좋음 (맹글링 이름)echo '_RNv*' > set_ftrace_filter
KASAN메모리 안전성 검사unsafe 블록에만 해당CONFIG_KASAN=y
pr_info!/pr_debug!로그 출력 (printk)네이티브 지원pr_info!("val={}\n", x)
objdump디스어셈블리 분석좋음objdump -d vmlinux | rustfilt
perf성능 프로파일링(Profiling)좋음 (맹글링 해제 필요)perf record / perf report

pr_info! / pr_debug! 디버깅

// 커널 Rust에서 가장 기본적인 디버깅 방법
use kernel::prelude::*;

// 로그 레벨별 매크로
pr_emerg!("Emergency: system is unusable\\n");     // KERN_EMERG
pr_alert!("Alert: action must be taken\\n");       // KERN_ALERT
pr_crit!("Critical condition\\n");                  // KERN_CRIT
pr_err!("Error: something went wrong\\n");          // KERN_ERR
pr_warn!("Warning: unusual condition\\n");           // KERN_WARNING
pr_notice!("Notice: normal but significant\\n");     // KERN_NOTICE
pr_info!("Info: driver loaded, irq={}\\n", irq);    // KERN_INFO
pr_debug!("Debug: register val={:#x}\\n", val);     // KERN_DEBUG (CONFIG_DYNAMIC_DEBUG)

// Rust의 Debug/Display 트레이트 활용
pr_info!("State: {:?}\\n", my_state);              // Debug 포맷
pr_info!("Buf (hex): {:02x?}\\n", &buf[..16]);    // 16진수 배열

Rust 심볼 디맹글링

커널 oops 메시지에서 Rust 함수 이름은 _R로 시작하는 맹글링된 형태로 나타납니다. rustfilt로 사람이 읽을 수 있는 형태로 변환합니다.

# rustfilt 설치
cargo install rustfilt

# 맹글링된 심볼 디맹글링
echo "_RNvNtCs1234_my_module3foo4init" | rustfilt
# → my_module::foo::init

# 커널 oops 출력 전체를 파이프로 디맹글링
dmesg | rustfilt

# 또는 특정 모듈의 심볼 목록 확인
nm vmlinux | rustfilt | grep "my_rust_module"

# objdump으로 디스어셈블 + 디맹글링
objdump -d vmlinux | rustfilt | less

addr2line으로 소스 위치 추적

# 커널 oops에서 나온 주소를 소스 파일:라인으로 변환
addr2line -e vmlinux -f 0xffffffff81234567
# → my_module::MyDriver::probe
# → drivers/my_driver/src/lib.rs:42

# 여러 주소를 한 번에 변환
addr2line -e vmlinux -f -i 0xffffffff81234567 0xffffffff81234890
# -i: 인라인된 함수 정보도 포함

# 콜스택 전체를 소스 위치로 변환
# oops 메시지에서 RIP/PC 주소 추출 후:
for addr in 0xffffffff81234567 0xffffffff81234890 0xffffffff81234abc; do
    echo -n "$addr: "; addr2line -e vmlinux -f $addr | tr '\n' ' '; echo
done

GDB + QEMU 원격 디버깅

# 1. QEMU에서 커널을 디버그 모드로 실행
qemu-system-x86_64 \
    -kernel arch/x86/boot/bzImage \
    -initrd rootfs.cpio.gz \
    -append "console=ttyS0 nokaslr" \
    -nographic \
    -s -S  # -s: gdb server on :1234, -S: 시작 시 대기

# 2. GDB 연결
gdb vmlinux
(gdb) target remote :1234

# 3. Rust 함수에 브레이크포인트 설정
# 맹글링된 이름 또는 디맹글링된 이름 모두 사용 가능
(gdb) break rust_module::init
(gdb) break _RNvNtCs1234_my_module4init  # 맹글링 이름

# 4. Rust 변수 검사
(gdb) print *self
(gdb) print self.inner.data  # Rust 구조체 필드 접근

# 5. Rust 타입 정보 확인
(gdb) ptype my_module::MyDriver
(gdb) info locals  # 현재 스코프의 로컬 변수

# 6. 디버그 심볼이 충분한지 확인
# Kconfig에서 CONFIG_DEBUG_INFO=y 필수
(gdb) continue

ftrace로 Rust 함수 추적

# Rust 함수를 ftrace로 추적
echo function > /sys/kernel/debug/tracing/current_tracer

# 특정 Rust 모듈의 함수만 필터링
# (맹글링된 이름 사용)
echo '_RNv*my_module*' > /sys/kernel/debug/tracing/set_ftrace_filter

# 또는 available_filter_functions에서 검색
cat /sys/kernel/debug/tracing/available_filter_functions | rustfilt | grep my_module

# 추적 시작/종료
echo 1 > /sys/kernel/debug/tracing/tracing_on
# ... 테스트 수행 ...
echo 0 > /sys/kernel/debug/tracing/tracing_on

# 결과 확인 (디맹글링 적용)
cat /sys/kernel/debug/tracing/trace | rustfilt

KASAN과 Rust unsafe 디버깅

KASAN(Kernel Address Sanitizer)은 unsafe 블록 내의 메모리 오류를 런타임에 탐지합니다. Safe Rust에서는 메모리 오류가 발생하지 않으므로, KASAN은 주로 unsafe 코드와 FFI 경계를 검증하는 데 활용됩니다.

# KASAN 활성화 (Kconfig)
# CONFIG_KASAN=y
# CONFIG_KASAN_GENERIC=y  (또는 CONFIG_KASAN_SW_TAGS)
make LLVM=1 menuconfig
# Kernel hacking → Memory Debugging → KASAN

# KASAN 탐지 시 커널 로그 예시:
# BUG: KASAN: slab-use-after-free in _RNvNtCs..._my_driver4read
# Read of size 4 at addr ffff888012345678 by task test/1234
# Call Trace:
#  _RNvNtCs..._my_driver4read+0x42/0x80

# 이 출력을 디맹글링하면:
# BUG: KASAN: slab-use-after-free in my_driver::read
디버깅 도구용도Rust 관련 참고활성화 방법
dmesg커널 로그 확인pr_info!/pr_err! 출력 확인기본 제공
rustfiltRust 심볼 디맹글링cargo install rustfilt사용자 도구
addr2line주소→소스 매핑CONFIG_DEBUG_INFO=y 필요binutils 포함
GDB인터랙티브 디버깅QEMU + -s -S로 원격 디버깅nokaslr 추가 권장
ftrace함수 호출 추적Rust 함수도 추적 가능 (맹글링 이름)CONFIG_FTRACE=y
KASAN메모리 오류 탐지unsafe 블록의 OOB/UAF 탐지CONFIG_KASAN=y
KMSAN초기화되지 않은 메모리unsafe 내 미초기화 접근 탐지CONFIG_KMSAN=y
lockdep잠금 순서 검증Rust Mutex/SpinLock도 lockdep 통합CONFIG_LOCKDEP=y

Rust panic 발생 시 커널 동작

// 커널에서 panic이 발생하는 상황들:

// 1. unwrap() 실패 → 커널 oops
let val = some_option.unwrap();  // None이면 panic!

// 2. 배열 경계 초과 → panic
let arr = [1, 2, 3];
let x = arr[5];  // index out of bounds → panic!

// 올바른 패턴: 항상 Result/?로 에러 전파
fn safe_operation() -> Result {
    let val = some_option.ok_or(code::EINVAL)?;  // panic 대신 에러 반환
    let x = arr.get(5).ok_or(code::EINVAL)?;     // 안전한 경계 검사
    Ok(())
}
💡

Rust panic과 커널: safe Rust에서 panic이 발생하면 커널 oops로 이어집니다. 따라서 커널 Rust에서는 unwrap()/expect() 사용을 최소화하고, 항상 Result와 ? 연산자로 에러를 전파해야 합니다. panic = "abort"가 커널 빌드 기본값입니다.

⚠️

디버그 빌드 주의: CONFIG_DEBUG_INFO=y와 CONFIG_KASAN=y를 동시에 활성화하면 커널 이미지 크기가 수 배로 증가하고 빌드 시간이 크게 늘어납니다. 디버깅이 끝나면 비활성화하세요. 또한 nokaslr 커널 파라미터를 사용하면 GDB 브레이크포인트가 안정적으로 동작합니다.

C vs Rust 커널 코드 비교

동일한 기능을 C와 Rust로 구현했을 때의 차이를 구체적인 코드로 비교합니다. Microsoft와 Google 보안팀의 분석에 따르면 커널 보안 취약점의 약 70%가 메모리 안전 버그에서 기인합니다. 아래 비교는 이 버그들이 왜 C에서 쉽게 발생하고 Rust에서는 컴파일 타임에 차단되는지를 6대 카테고리(메모리 안전·자원 관리·에러 처리·동시성·타입 안전·정수 오버플로)로 나누어 보여줍니다. "Rust가 더 안전하다"는 추상적 주장이 아니라, 어떤 코드 패턴이 어떤 이유로 거부되는지 컴파일러 수준에서 이해하면 safe/unsafe 경계 설계를 더 명확하게 내릴 수 있습니다.

C vs Rust: 6대 안전 카테고리 비교 C vs Rust: 6대 안전 카테고리 비교 동일한 코드 패턴을 C(런타임 위험)와 Rust(컴파일 타임 차단)로 나란히 비교합니다 6대 안전 카테고리 ✗ C 언어 — 런타임 위험 ✓ Rust — 컴파일 타임 차단 메모리 안전 ✗ 수동 해제 → UAF · Double Free kfree() 재접근 · 이중 해제 — 컴파일러가 막지 못함 ✓ 소유권 시스템 UAF/DF는 소유권 위반 → 컴파일 에러 자원 관리 ✗ 수동 lock/unlock → 누수 에러 경로에서 unlock 누락 · 자원 해제 누수 ✓ RAII + Drop MutexGuard가 스코프 종료 시 자동 unlock 에러 처리 ✗ 반환값 무시 가능 copy_from_user() 실패 미체크 → 부분 복사 ✓ Result<T, E> + ? 연산자 에러 무시 불가 — 처리 또는 전파 강제 동시성 ✗ 런타임 데이터 레이스 잠금 순서 · 보호 범위를 수동으로 관리 ✓ Send/Sync 타입 검사 레이스 위험 코드를 컴파일 타임에 차단 타입 안전 ✗ void* · 암시적 캐스팅 타입 불일치를 컴파일러가 묵인 ✓ 강타입 + 트레이트 타입 불일치 → 컴파일 에러 정수 오버플로 ✗ 정수 오버플로 = UB 컴파일러 경고 없이 조용히 오동작 ✓ panic / wrapping (명시적) 오버플로 처리 방식을 명시적으로 선택 결론: 같은 패턴 — C: 런타임 취약점 vs Rust: 컴파일 타임 에러
6대 안전 카테고리(메모리 안전·자원 관리·에러 처리·동시성·타입 안전·정수 오버플로)에서 같은 코드 패턴은 C에서 런타임 취약점이 되지만, Rust에서는 소유권·RAII·Result·Send/Sync·강타입·오버플로 정책으로 컴파일 타임에 차단됩니다.
/* C 커널 코드: 뮤텍스 보호 리소스 접근 */
static DEFINE_MUTEX(my_lock);
static int shared_data = 0;

int my_read(void) {
    int val;
    mutex_lock(&my_lock);
    val = shared_data;
    /* 여기서 에러 반환 시 unlock 빠뜨리기 쉬움! */
    mutex_unlock(&my_lock);
    return val;
}
// Rust 커널 코드: 동일 기능
let shared_data: Mutex<i32> = Mutex::new(0);

fn my_read(data: &Mutex<i32>) -> Result<i32> {
    let guard = data.lock();  // 잠금 획득
    let val = *guard;
    Ok(val)
    // guard가 여기서 Drop → 자동 unlock
    // 에러 경로든 정상 경로든 반드시 unlock됨!
}

에러 경로 비교: 다중 자원 할당

/* C: 에러 경로에서 역순 해제 — "goto cleanup" 패턴 */
int my_probe(struct platform_device *pdev) {
    struct resource *res;
    void __iomem *base;
    int irq, ret;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) return -ENODEV;

    base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(base)) return PTR_ERR(base);

    irq = platform_get_irq(pdev, 0);
    if (irq < 0) return irq;

    ret = devm_request_irq(&pdev->dev, irq, my_handler,
                           0, "mydev", pdev);
    if (ret) return ret;  /* 각 단계마다 에러 체크 필요 */

    return 0;
}
// Rust: ? 연산자로 에러 전파 — 자원은 Drop이 자동 해제
fn probe(pdev: &mut PlatformDevice) -> Result {
    let res = pdev.get_resource(0)?;        // 실패 시 자동 반환
    let base = pdev.ioremap_resource(&res)?; // 실패 시 자동 반환
    let irq = pdev.get_irq(0)?;              // 실패 시 자동 반환
    pdev.request_irq(irq, my_handler)?;     // 실패 시 자동 반환
    Ok(())
    // 에러 시: 이미 생성된 res, base가 Drop으로 자동 해제
    // goto cleanup 패턴이 완전히 불필요!
}

버퍼 오버플로 비교

/* C: 버퍼 오버플로 — 컴파일러가 경고하지 않음 */
char buf[64];
int len = copy_from_user(buf, user_buf, user_len);
/* user_len > 64이면 스택 버퍼 오버플로! */
buf[len] = '\0';  /* len이 음수일 수도 있음 */
// Rust: 슬라이스 범위 검사 + UserSlice 안전 추상화
let mut buf = [0u8; 64];
let len = reader.read_slice(&mut buf)?;  // 최대 64바이트만 읽음
// buf[65] 접근 → 컴파일 타임 에러 (고정 크기 배열)
// reader가 64를 초과하면 Err 반환
버그 유형C에서의 발생 원인Rust의 방지 메커니즘
Use-After-Freekfree(ptr); use(ptr);소유권 이전 후 사용 → 컴파일 에러
Double Freekfree(ptr); kfree(ptr);소유권 이전은 1회만 → 컴파일 에러
버퍼 오버플로배열 범위 체크 누락슬라이스 범위 검사 (런타임 panic 또는 Result)
자원 누수에러 경로 cleanup 누락Drop 트레이트 → RAII 자동 해제
데이터 레이스잠금 없이 공유 데이터 접근Send/Sync + Mutex 타입 시스템 강제
널 포인터NULL 체크 누락Option<T> → None 처리 강제
정수 오버플로(Integer Overflow)부호 있는 정수 UB디버그: panic, 릴리스: wrapping (명시적)
포맷 문자열printk(user_data)타입 안전 매크로 (pr_info!)

C→Rust 점진적 마이그레이션 전략

커널에서 C 코드를 Rust로 마이그레이션하는 것은 전면 교체가 아닌 점진적 공존 전략입니다. 기존 C 서브시스템은 그대로 유지하면서, 새로운 드라이버와 모듈을 Rust로 작성하고, 추상화 계층을 점진적으로 확장합니다.

C→Rust 마이그레이션 로드맵 C → Rust 점진적 마이그레이션 로드맵 인프라 → 추상화 → 드라이버 → 확산 4단계로 점진 도입하며, 기존 C 코드는 유지한 채 새 코드만 선택적으로 작성합니다 1 1 · 인프라 완료 v6.1 (2022) · Kbuild 통합 · bindgen 파이프라인 · alloc 크레이트 (기본 추상화) 2 2 · 추상화 완료 v6.2-6.8 · Mutex·Arc·Vec 래퍼 · Result 에러 처리 · pin_init!/module! 매크로 3 3 · 드라이버 진행 중 v6.8-6.16+ · PHY: ax88796b (6.8) · GPU: Nova (6.16) · 블록: null_block (6.11) 4 4 · 확산 장기 v6.16+ · 진행 지속 · 서브시스템 확대 · 파일시스템 (실험적) · 스케줄러 보조 (장기) 마이그레이션 4대 핵심 원칙 1 · 비강제 교체 새 코드에서만 선택적 사용 2 · 바이너리 공존 C .o + Rust .o 함께 링크 3 · 점진적 확장 C API safe 래퍼 점진 추가 4 · 메인테이너 동의 서브시스템별로 결정
커널 Rust는 ① 인프라(v6.1) → ② 추상화(v6.2~6.8) → ③ 드라이버(v6.8~6.16+) → ④ 확산(장기) 순으로 점진 도입되며, 비강제 교체·바이너리 공존·점진적 확장·메인테이너 동의의 4대 원칙 아래 진행됩니다.

마이그레이션 의사 결정 기준

기준Rust 도입 적합C 유지 적합
코드 상태신규 작성 또는 전면 리팩토링이 필요한 코드안정적으로 작동 중인 기존 코드
메모리 안전버퍼 오버플로/UAF 이력이 있는 서브시스템안전성 이슈가 거의 없는 코드
복잡도동시성/에러 경로가 복잡한 드라이버단순한 레지스터 접근 코드
추상화 가용성필요한 Rust 추상화가 이미 존재추상화 계층을 새로 만들어야 함
메인테이너Rust에 호의적이고 리뷰 가능C만 리뷰하는 메인테이너
성능 민감도안전성이 성능보다 중요한 경로핫 패스 (ns 단위 최적화 필요)

실제 마이그레이션 사례

프로젝트서브시스템상태설명
ax88796bPHY 드라이버머지 완료최초의 프로덕션 Rust 커널 드라이버 (Linux 6.8)
NovaDRM/GPU머지 완료(6.16)NVIDIA GPU 신규 Rust DRM 드라이버 — nova-core(GSP 부트)는 6.15, DRM 수준 드라이버는 6.16에 머지
rust_binderAndroid IPC머지 완료(6.18)Android Binder를 Rust로 재작성 — 6.18에서 rust_binder 드라이버 머지
NVMe블록 장치(Block Device)mainline 밖(오픈소스)NVMe 드라이버 Rust 구현 (안전성 개선 목적) — 아직 mainline 머지 전
Null Block블록 장치머지 완료(6.11)Rust 블록 디바이스 드라이버 API와 함께 null_block 드라이버 도입
r8169 PHY네트워크논의 중Realtek PHY 드라이버 Rust 전환 가능성

새 Rust 드라이버 작성 체크리스트

단계작업확인 사항
1필요한 추상화 확인rust/kernel/에 필요한 C API 래퍼가 있는지 확인
2추상화 추가 (필요 시)rust/kernel/에 새 safe 래퍼 작성 → LKML 패치 제출
3Kconfig 항목 생성config MYDRIVER_RUST + depends on RUST
4Makefile 규칙 추가obj-$(CONFIG_MYDRIVER_RUST) += my_driver.o
5드라이버 구현module! 매크로 + 트레이트 구현
6SAFETY 주석모든 unsafe 블록에 SAFETY 근거 작성
7문서 작성/// 문서 주석 + # Examples, # Errors
8테스트KUnit + rustdoc 테스트 + QEMU 통합 테스트
9Clippy / rustfmtmake LLVM=1 CLIPPY=1 경고 0개 확인
10LKML 제출서브시스템 메인테이너에게 패치 시리즈 전송
💡

마이그레이션 황금 규칙: (1) 기존 C 코드를 강제로 교체하지 않습니다 — 새 드라이버에서 Rust를 선택적으로 사용. (2) C와 Rust가 같은 바이너리에 공존 — 링커가 C .o와 Rust .o를 함께 링크. (3) 추상화 계층을 점진적으로 확장 — 필요한 C API에 대한 safe Rust 래퍼를 점진적으로 추가. (4) 각 서브시스템의 메인테이너가 Rust 도입 여부를 결정.

커널 버전별 Rust 변천사

커널 Rust는 버전마다 API와 지원 범위가 크게 변합니다. Linux 6.1에서 기반 인프라가 들어온 뒤 매 릴리스마다 새 추상화가 추가·변경되었기 때문에, 사용 중인 커널 버전의 rust/kernel/ 디렉터리를 직접 확인하는 습관이 필수입니다 — 이 문서나 외부 블로그의 예제 코드는 버전이 다르면 컴파일되지 않을 수 있습니다. 아래 변천사는 "어느 버전에 무엇이 추가됐는지"를 빠르게 파악해 개발 환경 구축과 최소 커널 버전(MSRV) 결정에 활용하기 위한 요약입니다.

ℹ️

아래 표는 학습용 요약입니다. 세부 지원 범위와 API 변경은 커널 버전별 공식 문서(docs.kernel.org/rust)와 해당 브랜치의 rust/ 디렉터리를 우선 기준으로 확인하세요.

커널 버전Rust 관련 변경사항주요 추가 API / 기능
6.1Rust 기본 인프라 도입, alloc 크레이트, 기본 모듈 지원module! 매크로, pr_info!, kernel::prelude, bindgen 통합, Kbuild 연동
6.2Rust 문서 개선, 빌드 시스템 안정화rustdoc 커널 통합, make rustdoc 지원, 빌드 의존성 정리
6.3Rust 코드 정리 및 인프라 보강kernel::sync 모듈 확장, 타입 안전 래퍼 개선
6.4pin_init! 매크로, 개선된 에러 처리pin_init!, #[pin_data], PinInit 트레이트, Init 트레이트, Opaque<T>
6.5추상화 계층 확장kernel::task, ARef 개선
6.6Rust 1.71.1 툴체인 상향Rust 1.71.1 업그레이드, Mutex/SpinLock lock guard 개선
6.7workqueue 도입, Rust 1.73.0 툴체인 상향kernel::workqueue, Rust 1.73.0 업그레이드, Vec fallible allocation API 개선
6.8첫 실사용 드라이버(ax88796b PHY) 머지kernel::net::phy (PHY 드라이버 프레임워크), ax88796b Rust PHY 드라이버 머지, RCU 추상화 개선
6.9시간 추상화 도입, Rust 문서/테스트 강화kernel::time (Jiffies/Msecs), KUnit Rust 통합 개선, rustdoc 테스트 프레임워크
6.10Rust 1.78.0 툴체인 승격, 할당/시간 API 확장Rust 1.78.0 업그레이드, Ktime 추가, 할당(Allocation) API, Module::as_ptr
6.11디바이스/펌웨어(Firmware) 추상화와 블록 드라이버 API 도입Device & Firmware Rust 추상화, Rust 블록 디바이스 드라이버 API + null_block 드라이버
6.12Rust Binder를 위한 동기화/자료구조 추상화 확충Rust KCFI 지원, 참조 카운트 값용 Rust 연결 리스트(Linked List), Rust Binder용 Red-Black Tree 추상화, CPU_MITIGATIONS/objtool, shadow call stack sanitizer
6.13Rust trace events — 커널 트레이스 이벤트 직접 발행 + Binder 기반 추상화trace_event! 매크로, static branch, PidNamespace, Rust Binder용 File 추상화, seqfile, 제네릭 Allocator, PCI/Platform 디바이스·드라이버 추상화, 커스텀 FFI 정수 타입(6.14에서 전트리 적용)
6.14Device/PCI/Platform 추상화 정식화, 잠금 API 확장platform::Driver/pci::Driver/device::Driver 추상화, Lock::from_raw()/Guard::new(), dwarf modversions로 RUST+MODVERSIONS 동시 지원
6.15Rust hrtimer, ARMv7 지원, nova-core 시작kernel::time::hrtimer, ARMv7 Rust 지원, mm_struct/vm_area_struct/mmap 추상화, pin-init 독립 크레이트 분리, nova-core(NVIDIA GSP 부트) 초기 시리즈
6.16Nova DRM 드라이버 + DRM Rust 추상화 본격 도입DRM Rust 추상화와 drivers/gpu/drm/nova 머지, clk/cpumask/cpufreq/opp 추상화, cpuid, Aux bus, io polling, XArray, mm_struct/vma/mmap
6.17ACPI/디바이스 프로퍼티·VFS 추상화 확장ACPI match table, 디바이스 프로퍼티 읽기 바인딩, vfs Rust 업데이트
6.18rust_binder — 최초의 프로덕션 Rust 드라이버의 본격화rust_binder 드라이버 머지, LKMM atomics, BorrowedPage/PageIter, debugfs 바인딩, sg_table/scatterlist, sync::Refcount, bitmap/ID pool, maple tree 추상화, request_irq, nova-core PCI Class/Vendor, USB 샘플
7.0Rust 동기화 추상화 개편, 공식 지원 안정화Rust 동기화(synchronization) 변경 대폭 반영, 커널 문서상 Rust 1급 언어 명시
7.1GPU/메모리 바인딩과 register! 매크로gem shmem·GPU buddy allocator 바인딩, register! 매크로, C 헬퍼 인라인 옵션, MSRV 1.85.0 승격

Rust 커널 코드 성장 추이

커널 버전rust/ 디렉터리 코드 규모 (약)Rust 커널 모듈/드라이버 수
6.1~12,500줄샘플 모듈만 (samples/rust/)
6.4~18,000줄Pin 초기화 인프라 + 샘플
6.6~22,000줄샘플만 (samples/rust/)
6.8~25,000줄ax88796b Rust PHY 드라이버 (최초 Rust 네트워크 드라이버)
6.12~30,000줄다수 추상화 + PHY 드라이버 (KCFI, linked list, RB-tree)
6.14~38,000줄Device/PCI/Platform 추상화 + PHY + 인프라
6.15~42,000줄+hrtimer, ARMv7, nova-core(GSP) 시작
6.16~50,000줄+Nova DRM 드라이버 + DRM 추상화 머지
6.18~60,000줄+rust_binder 머지 + LKMM atomics + 다수 추상화
ℹ️

수치 주의: 위 rust/ 디렉터리 코드 규모(줄 수)는 커널 버전별로 대략적인 추정치이며, 공식 측정치가 아닙니다. 정확한 규모는 해당 커널 브랜치의 rust/ 디렉터리에서 wc -l 등으로 직접 확인하세요. 다만 드라이버 수와 기능 목록은 kernelnewbies.org 릴리스 노트 기준으로 검증된 내용입니다.

💡

핵심 이정표: Linux 6.1에서 Rust 인프라가 머지된 후, 6.8에서 첫 실사용 드라이버(PHY)가 등장했고, 6.15의 nova-core(GSP 부트)에 이어 6.16에서 복잡한 서브시스템인 DRM에 Rust NVIDIA Nova 드라이버(DRM 수준)가 머지되면서 "Rust는 커널에서 진짜로 쓸 수 있습니다"는 것이 증명되었습니다.

Rust 커널 지원의 미래와 전망

커널 Rust의 미래는 다음과 같은 방향으로 발전하고 있습니다. Linux 재단과 Rust for Linux 팀은 점진적 확장 전략을 채택하여, 검증된 서브시스템부터 단계적으로 Rust 지원을 넓혀가고 있습니다.

Rust 커널 확장 로드맵 Rust 커널 확장 로드맵 인프라 → 드라이버 → 서브시스템 → 안정화 순으로 성장하며, 3대 축(안정화·도구·커뮤니티)이 이를 뒷받침합니다 1 1 · 인프라 구축 완료 v6.1~v6.3 · Kbuild·bindgen 통합 · alloc·core 크레이트 · module!·pr_info! 기본 2 2 · 드라이버 확대 진행 중 v6.4~v6.18 · PHY·GPU: Nova (6.16) · hrtimer·nova-core · rust_binder (6.18) 3 3 · 서브시스템 확장 계획 v6.19+ (예정) · 네트워크 스택 · 블록 레이어·USB · 파일시스템 인터페이스 4 4 · 안정화·성숙 미래 미정 (장기) · stable 컴파일러 전환 · 커널 코어 일부 Rust · Rust-first 서브시스템 성장을 뒷받침하는 3대 축 안정화 과제 · nightly → stable 전환 · ABI 소스 호환성 보장 · rustc 버전 고정 해제 Rust API는 아직 불안정 — 버전마다 변경 가능 도구 생태계 · rust-analyzer 커널 지원 · KernelCI·CI 테스트 통합 · clippy 커널 린트 규칙 KernelCI에서 커널 Rust 테스트 수행 커뮤니티 성장 · 서브시스템 메인테이너 참여 · Rust 멘토링 프로그램 · 기업 스폰서 확대 커뮤니티 참여·기여자가 지속 증가 핵심 원칙: 기존 C 코드를 깨뜨리지 않으면서 점진적으로 안전한 코드 비율을 높입니다 장기 목표: 새로 작성되는 드라이버의 과반이 Rust로 작성되는 생태계
커널 Rust는 ① 인프라 구축(v6.1~v6.3) → ② 드라이버 확대(v6.4~v6.18, Nova 6.16·rust_binder 6.18) → ③ 서브시스템 확장(v6.19+ 계획) → ④ 안정화·성숙(장기) 순으로 성장하며, 안정화 과제·도구 생태계(KernelCI 등)·커뮤니티 성장이 이를 뒷받침합니다.
영역현황전망
드라이버PHY, GPU(Nova 6.16), rust_binder(6.18) 머지 완료USB, 블록, 네트워크 드라이버 확대 (NVMe는 mainline 밖 오픈소스)
파일시스템(Filesystem)Rust 파일시스템 인터페이스 논의 중실험적 FS 구현 가능성
아키텍처x86_64, ARM64, ARMv7 지원RISC-V, 추가 아키텍처 확장
안정화v6.10부터 공식 최소 stable 버전 지정 (1.78.0), 7.0(2026-04)에서 experimental 태그 제거allocator_api, coerce_unsized 등 필요 기능의 stable 승격 완료
도구bindgen, Kbuild 통합 완료rust-analyzer 커널 지원, CI 강화
커뮤니티Rust for Linux 워킹 그룹 활동 중더 많은 서브시스템 메인테이너 참여

주요 기술적 과제

과제현재 상태해결 방향
nightly 의존성 (해결됨)v6.1~v6.9: 기능 필요 기반 최소 버전 상향 (초기 nightly 흔히 사용) / v6.10+: 공식 최소 stable 버전 지정 (1.78.0) / v7.0+: experimental 태그 제거 / v7.1+: 최소 1.85.0allocator_api, coerce_unsized 등 필요 기능의 stable 승격이 완료되어 nightly 의존성 제거. 이제 배포판 패키지 Rust 툴체인으로도 빌드 가능
ABI 안정성Rust는 안정적 ABI를 보장하지 않음커널 내부에서만 사용하므로 ABI는 소스 레벨 호환성으로 대체. repr(C)로 C 측과의 인터페이스 보장
코드 크기Rust 바이너리가 C보다 약간 큼LTO(Link-Time Optimization)와 -Copt-level=z 적용으로 최적화. 제네릭 단형화(monomorphization) 비용 관리
디버깅 경험Rust 심볼 맹글링으로 가독성 저하rustfilt 통합, v0 맹글링 형식 개선, GDB pretty-printer 지원 확대
빌드 시간Rust 컴파일이 C보다 느림증분 컴파일 활용, 크레이트 단위 캐싱, sccache 도입 가능성

Rust가 커널에서 해결할 수 있는 버그 클래스

Microsoft, Google, Android 팀의 보고서에 따르면, 보안 취약점의 약 70%가 메모리 안전 버그입니다. Rust가 컴파일 타임에 방지할 수 있는 대표적 버그 클래스:

버그 클래스C에서의 발생 패턴Rust의 방지 메커니즘CVE 영향도
Use-After-Freekfree(obj); obj->field소유권 시스템: 해제된 메모리 접근 불가높음 (RCE 가능)
Double Freekfree(p); ... kfree(p);소유권 이동: 한 번만 해제 가능높음
Buffer Overflowmemcpy(dst, src, wrong_len)경계 검사, 슬라이스 타입높음 (RCE 가능)
Data Race잠금 없이 공유 데이터 접근Send/Sync + 빌림 규칙중간~높음
Null Derefptr = get_thing(); ptr->fieldOption<T> 강제 체크중간 (DoS)
Uninitialized Memorystruct foo s; use(s.field)초기화 강제, MaybeUninit 명시적 사용중간
ℹ️

Rust ≠ 만병통치약: Rust는 safe Rust 경계 안에서 메모리 안전을 보장합니다. 커널에서는 unsafe 블록과 FFI 경계가 불가피하므로, 이 부분은 여전히 수동 검증이 필요합니다. 또한 논리적 버그, 교착 상태(Deadlock)(Mutex 순서 역전), 자원 누수(non-memory resources) 등은 Rust로도 방지할 수 없습니다.

⚠️

Rust 커널 API는 아직 불안정(unstable)합니다. 버전마다 인터페이스가 크게 변경될 수 있으므로, 항상 해당 커널 버전의 rust/ 디렉터리 소스코드를 참조하세요.

2022년 Linux 6.1에서 실험적(experimental)으로 들어온 Rust 지원은 4년 만에 커널의 정식 일급 언어로 승격되었습니다. 이 섹션은 Linux 6.13~7.0 사이에 일어난 주요 변화와 Rust for Linux 프로젝트의 로드맵을 정리합니다.

한 줄 요약: Linux 6.14에서 FFI 정수 타입과 CoercePointee 마이그레이션이 끝나 "stable Rust만으로 커널 빌드" 목표에 결정적으로 가까워졌고, 2025년 12월 커널 개발자 서밋에서 Rust는 더 이상 실험이 아닌 커널의 핵심(core) 구성 요소로 지위가 바뀌었습니다.

타임라인: 2024~2026

시점이정표의미
2024-05Rust 1.78 최소 버전 지정 (Linux 6.10)"떠다니는 최소 버전" 정책 종료, 정식 MSRV 체계 도입
2025-02Rust 1.85 + 2024 Edition 공개derive(CoercePointee)가 stable — 커널의 Arc/ListArc에 필수
2025-03Linux 6.14 머지 윈도우: Rust 대규모 인프라 업데이트커스텀 FFI 정수 타입(ffi::c_int 등) 마이그레이션 완료(6.13에서 시작), PROCMACROLDFLAGS 도입, macOS 호스트 빌드 개선, dwarf modversions로 RUST+MODVERSIONS 동시 지원
2025-05Linux 6.15: nova-core(GSP 부트) + hrtimer + ARMv7Rust hrtimer API, ARMv7(32-bit) 지원, pin-init 독립 크레이트, mm_struct/mmap 추상화
2025-07Linux 6.16: Nova DRM 드라이버 + DRM Rust 추상화 머지DRM 수준의 본격 드라이버가 Rust로 작성된 최초 사례 (drivers/gpu/drm/nova)
2025-09Linux 6.17: ACPI/디바이스 프로퍼티·VFS 추상화ACPI match table, 디바이스 프로퍼티 읽기, vfs Rust 업데이트
2025-11Linux 6.18: 최초의 프로덕션 등급 rust_binder 드라이버 머지Android Binder를 Rust로 작성 — LKMM atomics, debugfs 바인딩, sg_table/scatterlist 등 대규모 추상화 동반
2025-12Linux Kernel Summit: Rust = core 지위 부여"experimental" 꼬리표 공식 제거, 서브시스템 메인테이너는 Rust 추상화 병합 거부 시 이유를 설명할 의무
2026-04Linux 7.0 공식 Rust 지원 릴리스SPARC/Alpha 신규 코드와 더불어, Rust가 문서상 1급 언어로 명시됨
2026-06Linux 7.1: MSRV 1.85.0 승격 + GPU 바인딩gem shmem/Buddy allocator 바인딩, register! 매크로, C 헬퍼 인라인 옵션
⚠️

검증 노트: "experimental 태그 제거"(7.0) · "공식 1급 언어 명시"(7.0) · "2025-12 커널 서밋 core 지위 부여" 서술은 커널 트리(v6.1~v7.2의 init/Kconfig·Documentation/rust/)와 kernelnewbies 릴리스 노트에서 직접 확인되지 않습니다. docs.kernel.org의 Rust 문서 또는 LWN 등 공식 기록을 통한 출처 확인이 필요합니다.

최소 Rust 버전(MSRV) 정책 — 1.78 → 1.85 전환

Rust for Linux는 커널이 어느 Rust 버전부터 빌드 가능한지를 명확히 규정하는 MSRV(Minimum Supported Rust Version) 정책을 유지합니다. 최소 버전은 커널 트리의 scripts/min-tool-version.sh가 결정하며, 다음과 같이 단계적으로 상향되었습니다.

따라서 어느 커널 버전을 빌드하는지에 따라 요구 rustc가 다르므로, 반드시 해당 커널 트리의 scripts/min-tool-version.sh 출력을 기준으로 툴체인을 고정해야 합니다. 상향 배경은 다음과 같습니다.

# 현재 커널이 요구하는 Rust 최소 버전 확인
grep "^min_tool_version" scripts/min-tool-version.sh

# rustup으로 MSRV 맞춰 고정
rustup toolchain install stable
rustup override set stable

# 커널 Rust 툴체인 상태 진단
make LLVM=1 rustavailable
make LLVM=1 rustreadytest   # Linux 6.14+: stable 기반 빌드 가능 여부 체크

Stable Rust 전용 빌드 목표 — 6.14 이정표

초기 Rust for Linux는 nightly only로 시작했지만, 장기 목표는 stable Rust만으로 커널을 빌드하는 것이었습니다. Linux 6.14에서 두 가지 큰 걸림돌이 동시에 제거되었습니다.

// Linux 6.14+ rust/kernel/sync/arc.rs 발췌
use core::marker::CoercePointee;

#[derive(CoercePointee)]
#[repr(transparent)]
pub struct Arc<#[pointee] T: ?Sized> {
    ptr: NonNull<ArcInner<T>>,
    _p: PhantomData<ArcInner<T>>,
}

// 이 derive가 stable Rust 1.85에 포함되어,
// 커널 Arc를 dyn Trait으로 자동 강제 변환할 수 있게 되었습니다.
let arc: Arc<MyDriver> = Arc::new(driver, GFP_KERNEL)?;
let dyn_arc: Arc<dyn Driver> = arc;   // DispatchFromDyn로 가능

Linux 6.14 전후의 신규 추상화

서브시스템Rust API최소 커널비고
PCIkernel::pci::Driver6.13~6.14Device/PCI/Platform 추상화(6.13~6.14), IRQ·MMIO·DMA 바인딩 확장 중
Platformkernel::platform::Driver6.14Device Tree 매칭 지원 (Device & Firmware 추상화는 6.11부터)
Misc Devicekernel::miscdevice::MiscDevice6.11UserSlice 통합
Workqueuekernel::workqueue::Work6.4delayed work, 취소 안전
DRM/KMS (Nova)kernel::drm::*6.16NVIDIA GSP 기반 GPU 드라이버 — nova-core(6.15) → DRM(6.16) 머지
Filesystemkernel::fs::*6.17 (WIP)vfs Rust 업데이트(6.17), tarfs 프로토타입이 레퍼런스
Networkkernel::net::phy::Driver6.8PHY 드라이버 스캐폴드

툴링 — clippy / rust-analyzer / rustdoc

2025-12 커널 서밋 결정 사항

Maastricht에서 열린 Linux Kernel Developers Summit(2025-12)에서 다음 정책이 공식화되었습니다.

주의: "core 승격"은 모든 Rust 추상화 API가 stable이라는 뜻은 아닙니다. 많은 커널 Rust 래퍼는 여전히 내부(in-tree) API로 취급되며, 버전 간 breaking change가 가능합니다. 외부 트리(out-of-tree) 드라이버를 Rust로 유지하는 경우, 커널 버전 업그레이드마다 rust/kernel/의 시그니처를 재확인해야 합니다.

Rust in Kernel과 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.

사이트 내부 문서

외부 참고 자료

자료URL설명
Rust for Linux 공식Rust for Linux 프로젝트 홈페이지 (rust-for-linux.com)프로젝트 홈페이지, 최신 소식 및 로드맵
GitHub 저장소Rust-for-Linux/linux 소스 코드 (github.com)커널 Rust 통합 개발 트리 및 이슈 트래커
커널 Rust 문서Rust in the Linux Kernel (docs.kernel.org)공식 커널 문서의 Rust 섹션 — 빌드 설정, 코딩 가이드라인
커널 Rust 빠른 시작Rust 빠른 시작 가이드 (docs.kernel.org)환경 구축부터 첫 빌드까지의 단계별 가이드
커널 Rust 코딩 가이드라인Rust 코딩 가이드라인 (docs.kernel.org)커널 Rust 코드의 스타일과 관례
커널 Rust API 문서kernel 크레이트 API 문서 (rust.docs.kernel.org)커널 Rust 크레이트의 자동 생성 API 레퍼런스
LKML RustRust for Linux 메일링 리스트 아카이브 (lore.kernel.org)패치 리뷰, RFC, 커뮤니티 논의 아카이브
커널 Rust 모듈 예제커널 Rust 샘플 모듈 (github.com)rust_minimal, rust_print 등 공식 예제 모듈 모음
RustonomiconThe Rustonomicon — unsafe Rust 심층 가이드 (doc.rust-lang.org)unsafe 코드 패턴, FFI, 메모리 레이아웃, 미정의 동작 규칙
Rust ReferenceThe Rust Reference (doc.rust-lang.org)언어 사양 레퍼런스 — 타입 시스템, ABI, 속성 등
Rust BookThe Rust Programming Language (doc.rust-lang.org)Rust 공식 학습서 — 소유권, 트레이트, 제네릭, 에러 처리
Rust by ExampleRust by Example (doc.rust-lang.org)예제 중심의 Rust 학습 자료
Embedded Rust BookThe Embedded Rust Book (docs.rust-embedded.org)no_std 환경, 베어메탈 Rust 프로그래밍 가이드
bindgen 문서rust-bindgen 사용자 가이드 (rust-lang.github.io)C 헤더에서 Rust FFI 바인딩 자동 생성 도구 문서
Miguel Ojeda — Rust in the Linux Kernel (LPC 2022)Rust in the Linux Kernel 발표 (youtube.com)Rust for Linux 메인테이너의 Linux Plumbers Conference 2022 발표
Miguel Ojeda — Rust for Linux 상태 업데이트 (LPC 2024)Rust for Linux 상태 업데이트 (lpc.events)Linux Plumbers Conference 2024에서의 최신 진행 상황 발표
LWN.net Rust 커버리지Rust 관련 기사 모음 (lwn.net)LWN.net의 커널 Rust 통합 관련 심층 기사 인덱스
Rust for Linux 초기 RFCRust support RFC v1 패치 (lore.kernel.org)2021년 Miguel Ojeda의 Rust 커널 지원 최초 RFC 패치셋
Rust 커널 모듈 튜토리얼 (GitHub)out-of-tree Rust 커널 모듈 템플릿 (github.com)커널 트리 외부에서 Rust 모듈을 빌드하는 템플릿 프로젝트
Coccinelle for RustCoccinelle for Rust (rust-for-linux.com)커널 Rust 코드를 위한 시맨틱 패치 도구 지원
klint — 커널 Rust 정적 분석기klint 정적 분석 도구 (github.com)커널 Rust 코드 전용 lint 및 정적 분석 도구
pinned-init 크레이트pinned-init (github.com)커널 내 pin 초기화 패턴 지원 크레이트