Rust in Kernel (Rust in Kernel)
Linux 커널 Rust 지원, 안전성 보장, 바인딩, 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 경계를 최소화하고, 안전한 추상화 뒤에 숨긴다"는 것입니다:
- bindgen 레이어: C 헤더에서 자동 생성된 raw FFI 바인딩. 모든 함수가
unsafe입니다. - Rust 추상화 레이어 (
rust/kernel/): raw 바인딩을 안전한 Rust 타입으로 래핑합니다.Mutex<T>,Box<T>,UserSlice등이 여기에 해당합니다. 각unsafe블록에는 안전성 근거를 명시하는// SAFETY:주석이 필수입니다. - 드라이버 레이어: 최종 드라이버 코드는 safe Rust만으로 작성됩니다.
unsafe없이 커널 기능을 사용할 수 있습니다.
커널 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 커널 API | Rust 추상화 | 안전성 보장 |
|---|---|---|
kmalloc/kfree | Box<T>, Vec<T> | 자동 해제, 범위 검사 |
mutex_lock/unlock | Mutex<T> | RAII 기반 자동 해제 |
spinlock | SpinLock<T> | 가드 패턴으로 자동 해제 |
refcount_t | Arc<T> | 참조 카운팅 자동 관리 |
copy_to/from_user | UserSlice | 범위 검사, 에러 처리 |
안전성 모델
// 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 errno | Rust code:: | 의미 |
|---|---|---|
-ENOMEM | code::ENOMEM | 메모리 부족 |
-EINVAL | code::EINVAL | 잘못된 인자 |
-ENOENT | code::ENOENT | 엔트리 없음 |
-EBUSY | code::EBUSY | 리소스 사용중 |
-EAGAIN | code::EAGAIN | 다시 시도 |
-EPERM | code::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.1 | Rust 기본 인프라 도입, alloc 크레이트, 기본 모듈 지원 |
| 6.2 | Rust 문서 개선, 빌드 시스템 안정화 |
| 6.4 | pin_init! 매크로, 개선된 에러 처리 |
| 6.6 | 네트워크 PHY 드라이버 (첫 실제 Rust 드라이버) |
| 6.8 | AMBA 드라이버 지원, firmware 모듈 |
| 6.10 | PCI 드라이버 바인딩, 블록 디바이스 추상화 |
| 6.12+ | DRM/GPU 드라이버 (Apple M-series), 파일시스템 추상화 |