러스트를 이용한 커널 개발

Sijin·2025년 11월 6일

Rust for linux

https://github.com/rust-for-linux

  • 현재 리눅스 커널 코드 일부를 러스트로 전환하는 작업이 진행 중이다
  • 그 중, 주로 커널 모듈 구현에 초점을 맞추고 있다
  • 러스트로 작성된 커널 모듈은 기존 C로 개발된 리눅스 커널과 상호작용이 필요하다
    • 이를 위해 Rust for linux에서는 다양한 크레이트를 개발해 제공하고 있다
  • Rust for linux의 핵심은 커널 함수와 상호작용을 쉽고 안전하게 만드는 것이다
  • 이를 통해, 개발자들은 C 바인딩에 직접 접근하지 않고도 러스트를 사용해 드라이버를 만들 수 있다

커널 개발을 돕는 크레이트

커널 크레이트

https://rust-for-linux.github.io/docs/kernel/

  • 커널 크레이트를 사용하면 리눅스 커널 기능을 사용할 수 있다

할당 크레이트

https://rust-for-linux.github.io/docs/alloc/index.html

  • 할당 크레이트는 동적 메모리 할당 과정을 단순화, 최적화하여 제공한다
  • 할당 크레이트는 std 크레이트에 종석되지 않는다, 즉 std 크레이트가 없는 #![no_std] 환경에서 동작이 가능하다

코어 크레이트

https://rust-for-linux.github.io/docs/core/index.html

  • #![no_std]를 사용하는 환경은 표준 라이브러리를 완전히 불러오지 않아 메모리 사용을 최소화 할 수 있다
    • 임베디드나 기타 특별한 환경에 적합하다
  • 코어 크레이트는 표준 라이브러리의 근간을 이루는 컴포넌트로 #![no_std] 환경에서 활용 가능한 기능들을 제공한다

간단한 커널 모듈

// SPDX-License-Identifier: GPL-2.0
//! this is document

use kernel::prelude::*;

module! {
    type: RustSample,
    name: "rust_sample",
    authors: ["Rust for Linux Contributors"],
    description: "Minimal in-tree Rust sample driver",
    license: "GPL",
}

struct RustSample;

impl kernel::Module for RustSample {
    fn init(_module: &'static ThisModule) -> Result<Self> {
        pr_info!("rust_sample: init\n");
        Ok(RustSample)
    }
}

impl Drop for RustSample {
    fn drop(&mut self) {
        pr_info!("rust_sample: exit\n");
    }
}
  • rust for linux 프로젝트가 제공하는 커널 크레이트의 prelude 모듈을 사용한다
    • prelude 모듈은 커널 모듈 개발에 필요한 핵심 traits, type, macro들을 제공한다
  • module! 매크로에서는 커널 모듈에 대한 정보를 한곳에서 정의한다
  • RustSample 구조체를 정의하고, RustSample 구조체가 kernel::Module trait을 구현한다
  • kernel::Module 트레잇은 커널 모듈을 위해 필요한 함수들을 정의한다
    • init 함수는 커널 모듈이 로드 되는 시점 호출되며, module! 매크로를 통해 정의된 구조체를 매개변수로 받는다
  • 커널은 각 모듈마다 struct module이라는 C 구조체로 상태를 관리하는데, ThisModule은 그 구조체의 포인터를 감싼 rust 래퍼이다
  • 모듈이 언로드될 때 호출되는 Drop trait을 구현한다

rust analyzer 설정

  • 커널 빌드 시스템에서 제공하는 make 명령어를 이용해 rust-project.json 파일을 자동 생성할 수 있다
  • 이 파일은 cargo를 사용하지 않는 커널 프로젝트의 구조를 rust-analyzer에 알려주는 역할을 한다
make M=drivers/rust_sample rust-analyzer

빌드

  • 위 파일은 linux/drivers/rust_sample/sample.rs로 추가했다
  • linux/drivers/rust_sample/Makefile을 작성하자
# Build a Rust kernel module
obj-$(CONFIG_RUST_SAMPLE) += sample.o
  • 이어서 linux/drivers/rust_sample/Kconfig 작성하자
config RUST_SAMPLE
    tristate "Rust Sample Kernel Module"
    depends on RUST
    default n
    help
      Example Rust kernel module
  • 이후 linux/drivers/Makefile에 아래 내용을 추가한다
obj-$(CONFIG_RUST_HELLO) += rust_sample/
  • 마지막으로 linux/drivers/Kconfig에 아래 내용을 추가한다
source "drivers/rust_sample/Kconfig"
  • 이후 make menuconfig를 하면, Device Drivers / Rust Hello Kernel Module를 M으로 체크한다

  • 마지막으로 아래 명령어로 빌드할 수 있다

    • Rust for Linux 지원이 켜져있으면 .rs 파일들은 같은 이름의 .o 파일이 생성된다고 한다
make M=drivers/rust_sample modules

/dev/random 구현하기

misc device 등록

  • 리눅스에서 캐릭터 디바이스는 cdev와 misc 중 하나로 등록할 수 있다
    • cdev는 가장 기본적인 캐릭터 디바이스 등록 방식이다, 주 번호 부 번호를 수동으로 관리한다
    • misc는 cdev를 쉽게 사용하기 위한 단순화된 래퍼로, 주번호 10을 예약해 사용한다
#[pin_data(PinnedDrop)]
struct RustSample {
    #[pin]
    dev: MiscDeviceRegistration<SampleDevice>,
}

struct SampleDevice;

#[vtable]
impl MiscDevice for SampleDevice {
    type Ptr = ();

    fn open(_file: &File, _misc: &MiscDeviceRegistration<Self>) -> Result<Self::Ptr> {
        pr_info!("rust_sample: device opened\n");
        Ok(())
    }
}

impl kernel::InPlaceModule for RustSample {
    fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
        pr_info!("rust_sample: init\n");

        try_pin_init!(Self {
            dev <- MiscDeviceRegistration::register(MiscDeviceOptions {
                name: c_str!("rust-sample"),
            }),
        })
    }
}

#[pinned_drop]
impl PinnedDrop for RustSample {
    fn drop(self: Pin<&mut Self>) {
        pr_info!("rust_sample: exit\n");
    }
}
  • RustSample 구조체에 dev를 pin 상태로 보관한다
    • 한번 특정 메모리 위치에 두면 더 이상 이동시키지 않는다는 규약이다
  • RustSample 구조체는 InPlaceModule을 구현해서 insmod시 init 함수가 호출되어 로그를 찍고 /dev/rust-sample를 등록한다
  • RustDevice 구조체는 MiscDevice를 구현해서 open 시 로그를 찍는다
  • RustSample 구조체는 PinnedDrop을 구현해서 rmmod 시 drop 함수가 호출되어 로그를 찍는다

랜덤값 제공

// SPDX-License-Identifier: GPL-2.0

//! 최소 구조만 갖춘 Rust 모듈 예제.

use core::ffi::c_void;
use kernel::{
    bindings,
    c_str,
    fs::{File, Kiocb},
    iov::IovIterDest,
    miscdevice::{MiscDevice, MiscDeviceOptions, MiscDeviceRegistration},
    prelude::*,
};

module! {
    type: RustSample,
    name: "rust_sample",
    authors: ["Rust for Linux Contributors"],
    description: "Minimal Rust sample driver",
    license: "GPL",
}

#[pin_data(PinnedDrop)]
struct RustSample {
    #[pin]
    dev: MiscDeviceRegistration<SampleDevice>,
}

struct SampleDevice;

#[vtable]
impl MiscDevice for SampleDevice {
    type Ptr = ();

    fn open(_file: &File, _misc: &MiscDeviceRegistration<Self>) -> Result<Self::Ptr> {
        pr_info!("rust_sample: device opened\n");
        Ok(())
    }

    fn read_iter(_kiocb: Kiocb<'_, Self::Ptr>, dest: &mut IovIterDest<'_>) -> Result<usize> {
        const CHUNK: usize = 256;
        let mut buf = [0u8; CHUNK];
        let mut total = 0usize;

        while !dest.is_empty() {
            let len = buf.len().min(dest.len());
            fill_random(&mut buf[..len]);
            let written = dest.copy_to_iter(&buf[..len]);
            total += written;

            if written != len {
                break;
            }
        }

        Ok(total)
    }
}

impl kernel::InPlaceModule for RustSample {
    fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> {
        pr_info!("rust_sample: init\n");

        try_pin_init!(Self {
            dev <- MiscDeviceRegistration::register(MiscDeviceOptions {
                name: c_str!("rust-sample"),
            }),
        })
    }
}

#[pinned_drop]
impl PinnedDrop for RustSample {
    fn drop(self: Pin<&mut Self>) {
        pr_info!("rust_sample: exit\n");
    }
}

fn fill_random(slice: &mut [u8]) {
    if slice.is_empty() {
        return;
    }

    unsafe {
        bindings::get_random_bytes(
            slice.as_mut_ptr().cast::<c_void>(),
            slice.len(),
        );
    }
}
  • 이전 단계에서 MiscDevice의 read_iter 함수의 구현이 추가되었다
  • read_iter는 사용자가 /dev/rust-sample을 읽을 때 호출되는 callback이다
  • read_iter는 아래와 같이 동작한다
    • 우선 buf에 u8 type의 0으로 CHUNK길이의 배열을 초기화하고
    • total은 usize type의 0으로 초기화
    • read(fd, buf, 0) 호출처럼 buf의 길이가 0, 즉 is_empty = true인 경우에만 반복문 진입
    • len은 buf나 dest의 길이 중 작은 것으로 초기화
    • buf에 len만큼 random 값을 채우고
    • dest에 buf를 복사하면서 실제로 몇바이트 썼는지 copy_to_iter의 반환값을 written에 대입
    • 원래 쓰려고한 바이트 수인 len과 write가 다른 경우는 사용자 버퍼가 가득 차거나 에러가 난 경우로 이런 경우에는 반복문 탈출
    • dest가 CHUNK보다 큰 경우에는 아직 dest에 남은 공간이 있어 is_empty가 아니므로 반복

0개의 댓글