Rust in Kernel (Rust in Kernel)

Linux 커널 Rust 지원, 안전성 보장, 바인딩, Rust 드라이버 작성법.

관련 표준: The Rust Reference (소유권, 수명, unsafe, FFI), C11 (ISO/IEC 9899:2011, C-Rust FFI 경계) — 커널 Rust 지원의 언어 규격 기반입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

Rust in Kernel 개요

Linux 6.1부터 Rust가 커널 개발 언어로 공식 지원됩니다. Rust의 소유권 시스템과 타입 안전성으로 use-after-free, 버퍼 오버플로, 데이터 레이스 등의 메모리 안전 버그를 컴파일 타임에 방지합니다.

왜 커널에 Rust인가

커널 코드의 보안 취약점 중 약 65~70%가 메모리 안전 버그(use-after-free, 버퍼 오버플로, 이중 해제 등)에서 발생합니다(Microsoft, Google Chromium 프로젝트 통계). C 언어는 이러한 버그를 컴파일 타임에 감지할 수 없지만, Rust는 소유권(ownership) 시스템을 통해 대부분을 컴파일 시점에 차단합니다.

Rust 안전성 보장 원리

Rust의 안전성은 세 가지 핵심 원리에 기반합니다:

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

// C에서 흔한 데이터 레이스 — Rust에서는 타입 시스템이 방지
// Send: 스레드 간 소유권 이전 가능
// Sync: 스레드 간 공유 참조 가능
// Mutex<T>는 T: Send이면 Sync → 컴파일러가 동기화 없는 공유 접근을 거부

C-Rust FFI 경계 원리

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

커널 Rust 안전 경계 모델 C 커널 API kmalloc, mutex_lock, ... Rust 추상화 (unsafe) rust/kernel/ 래퍼 Safe Rust 드라이버 unsafe 블록 불필요 bindgen 자동 생성 SAFETY 주석 필수 컴파일러가 안전성 보증 unsafe 코드 비율: 래퍼 ~5%, 드라이버 <1%

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

Rust 커널 개발 환경 설정

# Rust 툴체인 설치
rustup override set $(scripts/min-tool-version.sh rustc)
rustup component add rust-src

# 빌드 의존성
cargo install --locked --version $(scripts/min-tool-version.sh bindgen) bindgen-cli

# Rust 지원 활성화
make LLVM=1 rustavailable  # Rust 사용 가능 여부 확인
make LLVM=1 menuconfig     # General setup → Rust support 활성화

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);
    }
}

커널 Rust 추상화

커널 Rust 코드는 C API를 직접 호출하지 않고, 안전한 Rust 래퍼(abstraction)를 통해 접근합니다:

C 커널 APIRust 추상화안전성 보장
kmalloc/kfreeBox<T>, Vec<T>자동 해제, 범위 검사
mutex_lock/unlockMutex<T>RAII 기반 자동 해제
spinlockSpinLock<T>가드 패턴으로 자동 해제
refcount_tArc<T>참조 카운팅 자동 관리
copy_to/from_userUserSlice범위 검사, 에러 처리

안전성 모델

// 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
    Ok(())
}

// Unsafe Rust: C 함수 호출 시 필요 (safety invariant 문서화)
// SAFETY: ptr은 유효한 할당된 메모리를 가리키며,
//         이 함수의 호출자만 ptr에 접근합니다.
unsafe { bindings::some_c_function(ptr) };
💡

Rust 커널 드라이버 작성 시 unsafe 블록은 최소화하고, 각 unsafe 블록에 // SAFETY: 주석으로 안전성 근거를 명시해야 합니다. 이는 커널 코딩 규칙입니다.

Kbuild 통합과 빌드 과정

Rust 코드는 Kbuild 시스템에 완전히 통합됩니다. Makefile에서 Rust 소스를 지정하면 rustc가 자동으로 호출됩니다.

# 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)

bindgen과 C 바인딩 생성

bindgen은 C 헤더 파일에서 Rust FFI 바인딩을 자동 생성합니다. 커널 빌드 시 rust/bindings/bindings_generated.rs가 생성됩니다.

// 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"));
}

// Rust에서 C 함수 호출
pub use bindings_raw::*;

Pin과 커널 초기화 패턴

커널 Rust에서는 Pin<T>을 활용한 in-place 초기화가 핵심 패턴입니다. 자기 참조 구조체(예: mutex, list_head)는 이동할 수 없으므로 pin_init! 매크로를 사용합니다.

use kernel::sync::{new_mutex, Mutex};
use kernel::init::PinInit;

#[pin_data]
struct SharedState {
    #[pin]
    data: Mutex<Vec<u8>>,
    name: CString,
}

impl SharedState {
    fn new(name: &CStr) -> impl PinInit<Self> {
        pin_init!(Self {
            // Mutex는 Pin으로 초기화해야 함
            data <- new_mutex!(Vec::new(), "SharedState::data"),
            name: CString::try_from_fmt(fmt!("{}", name))?,
        })
    }
}

// Box::pin_init()으로 힙 할당 + pin 초기화
let state = Box::pin_init(SharedState::new(c_str!("example")))?;

에러 처리 (kernel::error)

커널 Rust는 C의 -ENOMEM, -EINVAL 등의 errno 패턴을 Result<T, Error>로 래핑합니다.

use kernel::error::{Error, Result, code};

fn allocate_resource(size: usize) -> Result<Vec<u8>> {
    if size == 0 {
        return Err(code::EINVAL);  // -EINVAL에 대응
    }
    let mut v = Vec::new();
    v.try_reserve(size).map_err(|_| code::ENOMEM)?;  // -ENOMEM
    Ok(v)
}

// C에서 Rust 함수 호출 시 자동 변환
// Ok(()) → 0, Err(EINVAL) → -EINVAL
C errnoRust code::의미
-ENOMEMcode::ENOMEM메모리 부족
-EINVALcode::EINVAL잘못된 인자
-ENOENTcode::ENOENT엔트리 없음
-EBUSYcode::EBUSY리소스 사용중
-EAGAINcode::EAGAIN다시 시도
-EPERMcode::EPERM권한 없음

Rust 디바이스 드라이버 예제

실제 platform 드라이버를 Rust로 작성하는 예제입니다:

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 })
    }
}

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

Rust 커널 지원 변천사

커널 버전Rust 관련 변경사항
6.1Rust 기본 인프라 도입, alloc 크레이트, 기본 모듈 지원
6.2Rust 문서 개선, 빌드 시스템 안정화
6.4pin_init! 매크로, 개선된 에러 처리
6.6네트워크 PHY 드라이버 (첫 실제 Rust 드라이버)
6.8AMBA 드라이버 지원, firmware 모듈
6.10PCI 드라이버 바인딩, 블록 디바이스 추상화
6.12+DRM/GPU 드라이버 (Apple M-series), 파일시스템 추상화