2026년 2월 23일 월요일

리눅스 커널 내 Rust 코드 통합 - Part II

래간만에 잠자고 있던 BeaglePlay board를 다시 꺼냈다. 그 이유는 이전 posting에서 열심히 설명했던, Rust를 Embedded Linux 환경에서 돌려보기 위해서이다. 😎

Part I.

Part II. 목차
1. BeaglePlay 상에서 Rust Application 돌려 보기
2. Rust-For-Linux Kernel Build 환경 구성하기
3. QEMU 환경 설정하기
4. Linux Kernel Module in Rust
5. References


"물론 최근에 M$가 C/C++로 개발된 자신들의 code(windows kernel, Azure infrastructure 등) 중 일부를 Rust로 전환하여 효과를 보았다는 얘기가 들리며, Google 역시 Android를 구성하는 C/C++ 코드를 Rust로 전환하여 상당한 효과(C/C++ 대비 메모리 안전 문제를 1,000배 이상 줄였다고 함)를 보고 있는 것도 사실인 것 같다. 또한 Linux kernel에는 Rust로 구현한 코드가 6.1 version(2022년 10월)부터 보이기 시작하더니, 급기야는 작년 말(2025년 12월)에 C, Assembly에 이어 Rust를 공식 언어로 사용하기로 결정했다고 하는 걸 보니, 앞서 불가능하다고 했던 말이 다소 무색해지는 느낌도 든다."  이전 posting 내용 중에서 ... 😋


1. BeaglePlay 상에서 Rust Application 돌려 보기
이번 장에서는 BeaglePlay board 위에서 Rust application code를 돌려보는 방법을 소개해 보고자 한다. BeaglePlay board와 관련해서는 (2년 전에 작성한) 이전 posting 내용을 참조해 주기 바란다.

1.1 Rust 설치하기
먼저 rust를 설치하는 부분부터 시작해 보자. 아래 내용은 Ubuntu 22.04를 기준으로 하였다.

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
info: downloading installer

Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

  /home/chyi/.rustup

This can be modified with the RUSTUP_HOME environment variable.

The Cargo home directory located at:

  /home/chyi/.cargo

This can be modified with the CARGO_HOME environment variable.

The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:

  /home/chyi/.cargo/bin

This path will then be added to your PATH environment variable by
modifying the profile files located at:

  /home/chyi/.profile
  /home/chyi/.bashrc

You can uninstall at any time with rustup self uninstall and
these changes will be reverted.

Current installation options:


   default host triple: x86_64-unknown-linux-gnu
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>1

$ source $HOME/.cargo/env
  -> .bashrc에 이 내용을 넣어두면 편리하다.
$ rustc --version
$ cargo --version

1.2 ARM64용 Rust cross toolchain 설치하기
이제 부터는 cross toolchain을 설치할 차례이다.
$ cargo install cross
    Updating crates.io index
     Ignored package `cross v0.2.5` is already installed, use --force to override

$ rustup target list
  -> 설치 가능한 cross toolchain 목록을 확인한다.
aarch64-apple-darwin
aarch64-apple-ios
aarch64-apple-ios-macabi
aarch64-apple-ios-sim
aarch64-linux-android
aarch64-pc-windows-gnullvm
aarch64-pc-windows-msvc
aarch64-unknown-fuchsia
aarch64-unknown-linux-gnu      <--- 이걸 사용하자.
aarch64-unknown-linux-musl
aarch64-unknown-linux-ohos
aarch64-unknown-none
aarch64-unknown-none-softfloat
aarch64-unknown-uefi
arm-linux-androideabi
arm-unknown-linux-gnueabi
arm-unknown-linux-gnueabihf
arm-unknown-linux-musleabi
arm-unknown-linux-musleabihf
...

$ rustup target add aarch64-unknown-linux-gnu
  -> arm64용 rust toolchain을 설치하도록 한다.
info: downloading component 'rust-std' for 'aarch64-unknown-linux-gnu'
info: installing component 'rust-std' for 'aarch64-unknown-linux-gnu'
 29.9 MiB /  29.9 MiB (100 %)  20.8 MiB/s in  1s  

1.3 Hello world project 생성 후, cross-compile 하기
자, 이제 hello world project를 하나 만들고, beagleplay board에서 돌려 볼 수 있도록 cross compile해 보자.

cargo new hello_beagleplay
     Created binary (application) `hello_beagleplay` package

$ cd hello_beagleplay
$ cross build --target aarch64-unknown-linux-gnu
...
   Compiling hello_beagleplay v0.1.0 (/project)
    Finished dev [unoptimized + debuginfo] target(s) in 0.20s

$ cd target/aarch64-unknown-linux-gnu/debug
  -> debug mode로 동작하는 실행 파일이 생성된다.
$ ls -la
합계 4812
drwxr-xr-x 7 chyi chyi    4096  2월 18 17:10 .
drwxr-xr-x 3 chyi chyi    4096  2월 18 17:10 ..
-rw-r--r-- 1 chyi chyi       0  2월 18 17:10 .cargo-lock
drwxr-xr-x 3 chyi chyi    4096  2월 18 17:10 .fingerprint
drwxr-xr-x 2 chyi chyi    4096  2월 18 17:10 build
drwxr-xr-x 2 chyi chyi    4096  2월 18 17:10 deps
drwxr-xr-x 2 chyi chyi    4096  2월 18 17:10 examples
-rwxr-xr-x 2 chyi chyi 4890952  2월 18 17:10 hello_beagleplay
-rw-r--r-- 1 chyi chyi      79  2월 18 17:10 hello_beagleplay.d
drwxr-xr-x 3 chyi chyi    4096  2월 18 17:10 incremental

$ file hello_beagleplay
hello_beagleplay: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=18f0e0dcafba75c521158f6dc39a362ff22abd38, with debug_info, not stripped

마지막으로, hello_beagleplay binary를 target board로 올려 돌려 보도록 하자(파일을 target board로 올리는 절차는 생략함).

<Target board>
debian@BeagleBone:~/workspace$ uname -a
Linux BeagleBone 6.6.32-ti-arm64-r7 #1 SMP PREEMPT_DYNAMIC Wed Jul  3 19:49:27 UTC 2024 aarch64 GNU/Linux


debian@BeagleBone:~/workspace$ ./hello_beagleplay  
Hello, world!


OK, 문제 없이 잘 동작한다. 😎

[그림 1.2] BeaglePlay 상에서 Rust hello world 프로그램 실행 모습

같은 환경에서, 아래와 같은 다양한 example code를 돌려 보는 것도 좋을 것 같다.


1.4 Rust WireGuard 코드 돌려 보기
Embedded 환경에서 좀 더 복잡한 rust code를 돌려 보기를 원한다면, 이전 posting의 3장을 참조해 주기 바란다.

______________________________________
이어지는 장에서는 Linux kernel에서 동작하는 rust kernel module에 관한 이야기를 해 보도록 하겠다.


2. Rust-For-Linux Kernel Build 환경 구성하기
이번 장에서는 BeaglePlay board 상에서 Rust kernel module을 돌리기 위한 준비 작업(Rust 기능을 사용하도록 linux kernel을 build하는 과정)을 소개해 보고자 한다.

2.1 Linux kernel과 Rust의 결합

지난 35년 동안의 리눅스 커널(Linux Kernel) 개발 역사상 가장 중요한 전환점 중 하나는 Rust가 6.1 버전부터 메인라인에 병합되기 시작했다는 사실이다. 메모리 안전성(Memory Safety)과 Data Race 방지를 컴파일 타임에 보장하는 Rust 언어의 특성은, 오랫동안 C 언어가 지배해 온 커널 공간(Kernel Space)에서의 안정성을 획기적으로 향상시킬 만한 잠재력을 가지고 있다고 말할 수 있다.


[그림 2.1] Rust for Linux - rust 관련 작업 내용(1) [출처 - 참고문헌 14]

[그림 2.2] Rust for Linux - rust 관련 작업 내용(2) [출처 - 참고문헌 16]

Linux는 전통적으로 GCC(GNU Compiler Collection)와 GNU Make 기반의 생태계에 최적화되어 있는반면, Rust-for-Linux는 LLVM/Clang 툴체인, bindgen을 통한 FFI(Foreign Function Interface) 자동 생성, 그리고 Rust 컴파일러(rustc)와 커널 버전 간의 엄격한 버전 동기화를 전제로 설계되었다.

[그림 2.3] LLVM(Low Level Virtual Machine) logo [출처 - https://llvm.org/Logo.html ]

리눅스 커널에서의 Rust 지원은 이제 실험적 단계에서 성숙기로 넘어가는 과정(2025년 12월에 공식으로 사용하기로 결정)에 있는데, 각 커널 버전은 특정 버전의 Rust 컴파일러를 엄격하게 요구하고 있다. 이는 Rust 언어 자체가 아직 C 언어처럼 ABI(Application Binary Interface)가 완전히 고정되지 않았고, 커널 내부에서 사용하는 불안정 기능(Unstable Features)들이 컴파일러 버전에 따라 달라지기 때문이다.

따라서, 리눅스 커널에서 Rust 코드를 돌리기 위해서는 단순 메뉴 설정 과정 이상의 다소 복잡한 setup 과정이 선행되어야 한다.

2.2 LLVM/Clang 기반 빌드 시스템

전통적인 Linux 커널 빌드는 CROSS_COMPILE 변수를 통해 gcc를 사용한다. 그러나 Rust 커널 모듈을 빌드하기 위해서는 LLVM 툴체인이 사실상 필수적이다.

  1. Bindgen과 libclang: 커널의 C 헤더 파일을 Rust 코드가 이해할 수 있는 바인딩으로 변환해주는 도구인 bindgenlibclang에 의존하여 C 코드를 파싱한다. 즉, Clang의 프론트엔드 기능이 호스트 머신에 반드시 존재해야 한다.

  2. LLVM=1 옵션: 커널 커뮤니티는 Rust 지원 커널을 빌드할 때 C 코드와 Rust 코드 모두를 LLVM/Clang으로 빌드하는 것을 "가장 잘 지원되는 설정(Best Supported Setup)"으로 권장하고 있다. GCC를 사용하여 C를 빌드하고 Rust만 rustc로 빌드하는 혼합 방식도 이론적으로 가능하지만, 링커(Linker) 호환성 문제나 LTO(Link Time Optimization) 구성에서 복잡한 문제가 발생할 수 있다.   

  3. 일반적인 Rust 애플리케이션 개발과 달리, 커널은 no_std 환경(운영체제 없는 환경)에서 동작한다. 따라서 커널은 Rust의 표준 라이브러리 중 가장 핵심적인 corealloc 라이브러리를 자신의 빌드 옵션에 맞춰 재컴파일해주어야 한다. 이를 위해서는 호스트 머신에 단순히 rustc 바이너리만 있어서는 안 되며, Rust 표준 라이브러리의 **소스 코드(rust-src)**가 반드시 설치되어 있어야 한다.


[그림 2.4] LLVM(Low Level Virtual Machine)의 개념 [출처 - 참고문헌 9]
📌 바야흐로, GCC의 시대가 저물고, Clang -> LLVM의 시대가 도래한 것 같다. LLVM은 위의 그림에서 보는 것과 같이, clang, rustc 등의 frontend compiler가 생성한 IR code를 토대로 실제 architecture(backend)에 맞는 machine code를 생성해 주는 compiler infrastructure로 이해할 수 있다. 자세한 사항은 참고문헌 [11,12]를 참고하기 바란다.

이러한 내용을 기초로 하여, linux kernel을 준비해 보도록 하자.

2.3 Linux kernel 준비 단계 #1
먼저, kernel을 준비해 보도록 하자(아래 posting 5장 참조).


$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux
$ cd linux

$ git remote add stable https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux
$ git fetch stable

$ git checkout stable/linux-6.6.y

$ ARCH=arm64 make LLVM=1 rustavailable
  -> 이 명령을 통해 rust가 사용 가능한지를 먼저 확인해 본다.
***
*** Rust compiler 'rustc' is too new. This may or may not work.
***   Your version:     1.95.0
***   Expected version: 1.73.0
***
***
*** Rust bindings generator 'bindgen' is too new. This may or may not work.
***   Your version:     0.72.1
***   Expected version: 0.65.1
***
***
*** Please see Documentation/rust/quick-start.rst for details   //이 문서를 읽어 보란다.
*** on how to set up the Rust support.
***
Rust is available!

$ ARCH=arm64 LLVM=1 make bb.org_defconfig
  -> kernel config를 beagleplay용으로 초기화 한다.

$ ARCH=arm64 LLVM=1 make menuconfig
      -> Enable loadable module support
            -> [ ] Module versioning support (비활성화 하기)

      -> General setup 
            -> Rust support가 보여야 하는데, 안 보임.

[그림 2.5] kernel menuconfig - Module versioning support 비활성화하기

[그림 2.6] kernel menuconfig - General setup 하단에서 Rust support 항목 찾기

아무래도, 위의 ARCH=arm64 make LLVM=1 rustavailable 명령 실행 결과가 문제의 원인으로 보이므로, 이를 해결해 보기로 한다.

$ rustup install 1.73.0
  -> rust 1.73.0 version을 설치한다.

$ cargo uninstall bindgen-cli
$ cargo install --locked --version 0.65.1 bindgen-cli
  -> rust bindgen 0.65.1 version을 설치한다.
    Updating crates.io index
  Downloaded bindgen-cli v0.65.1
  Downloaded 1 crate (16.6 KB) in 0.05s
     Ignored package `bindgen-cli v0.65.1` is already installed, use --force to override

$ rustup override set $(scripts/min-tool-version.sh rustc)
  -> 이 명령을 실행하면, 기본 toolchain에는 영향을 주지 않고, working directory가 지정된 버젼의 rustc를 사용하도록 설정된다.
info: override toolchain for '/mnt/hdd/workspace/bootlin/beagleplay/bootlin/embedded-linux-beagleplay-labs/linux' set to '1.73.0-x86_64-unknown-linux-gnu'
📌 scripts/min-tool-version.sh rustc를 실행하면,  rust version 즉, 1.73.0 같은 string이 출력된다. rust update하여 버젼을 upgrade한 후에는 rustup override set 1.93.1 처럼 해 주어야, 실제 rustc version이 변경된다.

$ rustup component add rust-src
  -> rust 1.73.0 version에 해당하는 rust source code를 내려 받는다.
info: downloading component 'rust-src'
info: installing component 'rust-src'

$ ARCH=arm64 make LLVM=1 rustavailable
Rust is available!
  -> OK, 일단 깔금하게 정리가 되었다.

$ ARCH=arm64 LLVM=1 make menuconfig
어라, 그래도 여전히 "Rust support" menu 항목이 보이질 않는다. 😓

<여기서 잠깐!>
많은 사용자가 "General Setup 메뉴에 Rust support가 없다"고 호소(?)하는 것 같다(필자만의 문제는 아닌 것 같다 😂). 이는 커널의 init/Kconfig 파일에 정의된 RUST 옵션의 depends on 조건이 충족되지 않았기 때문인데, 주요 조건은 다음과 같다.
depends on RUST_IS_AVAILABLE
depends on!MODVERSIONS
depends on!GCC_PLUGINS
depends on!RANDSTRUCT

이 조건들을 하나씩 만족시켜야 비로소 메뉴에 Rust support 체크박스가 나타난다고 한다.
depends on HAVE_RUST
--------------------------------------------------

2.4 Linux kernel 준비 단계 #2
아무리 해 보아도, menuconfig에 Rust 항목이 보이질 않는다. 일전에 Buildroot에 있는 beagleplay config로 build했던 기억이 있는데, 다시 확인해 보니 kernel 6.10 버젼을 사용하고 있다. 그렇다면, kernel version을 6.6 -> 6.10으로 올려 보도록 하자(config는 나중에 다시 고려하기로 하자). 

$ git checkout stable/linux-6.10.y

$ git branch
* (HEAD stable/linux-6.10.y 위치에서 분리됨)
  master

$ ARCH=arm64 make LLVM=1 rustavailable
***
*** Rust compiler 'rustc' is too old.
***   Your version:    1.73.0
***   Minimum version: 1.78.0     //이 버젼을 설치해 주어야 한다.
***
***
*** Please see Documentation/rust/quick-start.rst for details  
*** on how to set up the Rust support.
***
make[1]: *** [/mnt/hdd/workspace/bootlin/beagleplay/bootlin/embedded-linux-beagleplay-labs/linux_RUST/linux/Makefile:1727: rustavailable] 오류 1
make: *** [Makefile:240: __sub-make] 오류 2

$ rustup install 1.78.0
  -> rust 1.78.0 version을 다시 설치한다.

$ rustup override set $(scripts/min-tool-version.sh rustc)
or
rustup override set 1.78.0
$ rustc --version
rustc 1.78.0 (9b00956e5 2024-04-29)

$ rustup component add rust-src
  -> rust 1.78.0용 source code도 다시 내려 받는다.

$ ARCH=arm64 make LLVM=1 rustavailable
Rust is available!

$ ARCH=arm64 LLVM=1 make menuconfig
OK, 드디어 Rust가 보인다. 😋
그렇다면, 6.6 kernel 문제였단 말인데, 도무지 정확한 이유를 알 수가 없다. 😓

[그림 2.7] kernel menuconfig - Rust support 항목 활성화하기

본격적으로 kernel build를 하기 전에, 아래 위치에 있는 rust sample code(kernel module)를 살려 두자.

Kernel hacking > Sample kernel code > Rust samples


[그림 2.8] Rust samples 활성화하기

$ ARCH=arm64 LLVM=1 make -j16
  -> firmware/regulatory.db 관련 에러가 발생하는데, 이전 posting에서 설명한 내용을 참조하여 해결한다.

$ cd arch/arm64/boot
  -> OK, kernel image file이 정상적으로 만들어 졌다.
$ ls -al
합계 36936
drwxrwxr-x  3 chyi chyi     4096  2월 17 21:44 .
drwxrwxr-x 14 chyi chyi     4096  2월 17 21:36 ..
-rw-rw-r--  1 chyi chyi      134  2월 17 21:44 .Image.cmd
-rw-rw-r--  1 chyi chyi      106  2월 17 21:44 .Image.gz.cmd
-rw-rw-r--  1 chyi chyi       74  2월 17 21:29 .gitignore
-rwxrwxr-x  1 chyi chyi 26034688  2월 17 21:44 Image
-rw-rw-r--  1 chyi chyi 11749166  2월 17 21:44 Image.gz
-rw-rw-r--  1 chyi chyi     1465  2월 17 21:29 Makefile
drwxrwxr-x 36 chyi chyi     4096  2월 17 21:35 dts
-rwxrwxr-x  1 chyi chyi     1001  2월 17 17:50 install.sh

$ cd samples/rust
$ ls -l *.ko
  -> rust sample code도 정상적으로 build가 되었다.
-rw-rw-r-- 1 chyi chyi 11656  2월 17 21:45 rust_minimal.ko
-rw-rw-r-- 1 chyi chyi 14432  2월 17 21:45 rust_print.ko

2.5 Rust kernel module 구동하기
이전 posting 내용을 참조하여 tftp booting을 시도하도록 하자(자세한 사항은 4장, 5장 참조)


<tftp booting을 위한 준비>
chyi@earth:/workspace/linux/arch/arm64/boot$ cp ./Image.gz /mnt/hdd/tftpboot
  -> kernel image를 교체한다(tftpboot 디렉토리로 복사).

chyi@earth:/workspace/linux/arch/arm64/boot/dts/ti$ cp ./k3-am625-beagleplay.dtb /mnt/hdd/tftpboot
  -> dtb file을 교체한다.

chyi@earth:/workspace/linux/samples/rust$ sudo cp *.ko /mnt/hdd/tftpboot/rootfs/home/root/workspace
  -> rust kernel module을 복사해 둔다(nfs rootfs folder 위치에 복사).

<tftp booting하기>
=> run bootcmd2
  -> 미리 만들어둔 명령어를 통해 tftp booting을 시도한다(자세한 내용은 위의 posting에 나와 있음).


[그림 2.9] BeaglePlay tftp booting 하기

로긴 후, rust kernel module을 구동시켜 보자.

beagleplay login: root

# cd workspace/rust
root@beagleplay:~/workspace/rust# uname -a
Linux beagleplay 6.10.14 #1 SMP PREEMPT_DYNAMIC Tue Feb 17 21:44:38 KST 2026 aarch64 GNU/Linux


root@beagleplay:~/workspace/rust# insmod ./rust_minimal.ko
OK, 올라간다. 😎

[그림 2.10] BeaglePlay 상에서 rust_minimal.ko 모듈 구동하기

(무슨 기능을 하는지는 나중에 살펴 보기로 하고) rust_print.ko 모듈도 구동시켜 보자.

root@beagleplay:~/workspace/rust# insmod ./rust_print.ko
역시 제대로 동작한다. 😎

[그림 2.11] BeaglePlay 상에서 rust_print.ko 모듈 구동하기

______________________________________
지금까지 Rust로 구현한 kernel module을 돌리기 위해 필요한 설정 내용을 자세히 정리해 보았다. 이어지는 장에서는 또 다른 개발 환경인 QEMU를 구축하는 방법을 살펴 보도록 하자.


3. QEMU 환경 설정하기
좀 더 다양하고, 실질적인 rust kernel module을 돌려 보고 싶은데, 몇가지 예제를 테스트해 보니 대부분 kernel 버젼과 일치하지 않아 compile이 안되는 문제가 있다. BeaglePlay board에서 최신 rust kernel module을 모두 돌려 볼 수 있었으면 좋으련만, 어쩔 수 없이 이제 부터는 QEMU(가상 환경)를 사용하여 rust kernel module을 돌려 보기로 하자. 😂

[그림 3.1] QEMU logo (https://www.qemu.org/)

3.1 QEMU build 하기
QEMU binary 버전을 설치할 수도 있겠으나, 여기에서는 source code를 내려 받아 직접 build해 보기로 한다.

$ git clone https://github.com/qemu/qemu
$ cd qemu/
$ mkdir build
$ cd build/
$ ../configure 
$ make
  -> 이후 make install해야 하나, 이 과정 없이 아래 명령(qemu-system-aarch64)을 직접 실행하도록 한다.

$ ls -l qemu-system-* |more
-rwxrwxr-x 1 chyi chyi 116330240  2월 20 14:41 qemu-system-aarch64    <- 이걸 사용하도록 하자.
-rwxrwxr-x 1 chyi chyi  53350184  2월 20 14:41 qemu-system-alpha
-rwxrwxr-x 1 chyi chyi  96381480  2월 20 14:41 qemu-system-arm
-rwxrwxr-x 1 chyi chyi  34598816  2월 20 14:41 qemu-system-avr
-rwxrwxr-x 1 chyi chyi  54173696  2월 20 14:41 qemu-system-hppa
-rwxrwxr-x 1 chyi chyi  81745576  2월 20 14:42 qemu-system-i386
-rwxrwxr-x 1 chyi chyi  63435760  2월 20 14:42 qemu-system-loongarch64
-rwxrwxr-x 1 chyi chyi  41797168  2월 20 14:42 qemu-system-m68k
-rwxrwxr-x 1 chyi chyi  35873192  2월 20 14:42 qemu-system-microblaze
-rwxrwxr-x 1 chyi chyi  35873032  2월 20 14:42 qemu-system-microblazeel
-rwxrwxr-x 1 chyi chyi  61611368  2월 20 14:43 qemu-system-mips
-rwxrwxr-x 1 chyi chyi  62579696  2월 20 14:43 qemu-system-mips64
-rwxrwxr-x 1 chyi chyi  66180616  2월 20 14:43 qemu-system-mips64el
-rwxrwxr-x 1 chyi chyi  61518112  2월 20 14:44 qemu-system-mipsel
-rwxrwxr-x 1 chyi chyi  52853176  2월 20 14:44 qemu-system-or1k
-rwxrwxr-x 1 chyi chyi  66540632  2월 20 14:44 qemu-system-ppc
-rwxrwxr-x 1 chyi chyi  73167520  2월 20 14:45 qemu-system-ppc64
-rwxrwxr-x 1 chyi chyi  67672944  2월 20 14:45 qemu-system-riscv32
-rwxrwxr-x 1 chyi chyi  68232352  2월 20 14:46 qemu-system-riscv64
-rwxrwxr-x 1 chyi chyi  34934800  2월 20 14:46 qemu-system-rx
-rwxrwxr-x 1 chyi chyi  52396440  2월 20 14:46 qemu-system-s390x
-rwxrwxr-x 1 chyi chyi  53192536  2월 20 14:46 qemu-system-sh4
-rwxrwxr-x 1 chyi chyi  53282568  2월 20 14:46 qemu-system-sh4eb
-rwxrwxr-x 1 chyi chyi  37044144  2월 20 14:46 qemu-system-sparc
-rwxrwxr-x 1 chyi chyi  54531720  2월 20 14:47 qemu-system-sparc64
-rwxrwxr-x 1 chyi chyi  35462656  2월 20 14:47 qemu-system-tricore
-rwxrwxr-x 1 chyi chyi  82970032  2월 20 14:47 qemu-system-x86_64
-rwxrwxr-x 1 chyi chyi  62094312  2월 20 14:47 qemu-system-xtensa
-rwxrwxr-x 1 chyi chyi  61921208  2월 20 14:48 qemu-system-xtensaeb
______________________________________

3.2 Linux kernel build 하기
이번에는 가능한 최신 rust kernel module을 돌려보기 위해, 최신 kernel 버젼을 내려 받아 build해 보도록 하자.

git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux
cd linux
git remote add stable https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux
git fetch stable
git checkout stable/linux-6.18.y

git branch
* (HEAD stable/linux-6.18.y 위치에서 분리됨)
  master

ARCH=arm64 make LLVM=1 rustavailable
Rust is available!

ARCH=arm64 LLVM=1 make defconfig   //QEMU 용 config는 이걸 사용한다.
ARCH=arm64 LLVM=1 make menuconfig
  -> Rust 설정을 동일하게 진행한다.
ARCH=arm64 LLVM=1 make clean
ARCH=arm64 LLVM=1 make -j16
cd arch/arm64/boot
$ ls -la
합계 54556
drwxrwxr-x  3 chyi chyi     4096  2월 20 15:11 .
drwxrwxr-x 14 chyi chyi     4096  2월 20 15:06 ..
-rw-rw-r--  1 chyi chyi      134  2월 20 15:11 .Image.cmd
-rw-rw-r--  1 chyi chyi      106  2월 20 15:11 .Image.gz.cmd
-rw-rw-r--  1 chyi chyi       74  2월 19 15:20 .gitignore
-rwxrwxr-x  1 chyi chyi 40905216  2월 20 15:11 Image    //이걸 이용하자.
-rw-rw-r--  1 chyi chyi 14924494  2월 20 15:11 Image.gz
-rw-rw-r--  1 chyi chyi     1538  2월 19 15:20 Makefile
drwxrwxr-x 42 chyi chyi     4096  2월 20 15:04 dts
-rwxrwxr-x  1 chyi chyi     1009  2월 19 15:20 install.sh
____________________________________________________

3.3 initramfs 준비하기
(QEMU로) 정상적인 kernel 부팅을 하기 위해서는 root file system이 필요한데, 여기에서는 initramfs 형태로 light하게 만들어 구동시키는 것이 좋을 듯하다.

[그림 3.2] Linux booting flow w/ initramfs [출처 - 참고문헌 5]
📌 위의 그림과는 달리, QEMU booting 시에는 Root filesystem 부분을 사용하지 않을 것이다.

먼저, busybox source code를 내려 받는다.

$ wget https://github.com/mirror/busybox/archive/refs/tags/1_36_0.tar.gz
$ tar xvzf 1_36_0.tar.gz
$ cd busybox-1_36_0

압축해제한 busybox source code를 arm64용으로 cross compile하도록 한다.

$ export ARCH=arm64
$ export CROSS_COMPILE=aarch64-linux-gnu-
$ make defconfig
$ make menuconfig
  -> busybox를 static binary 형태로 build하도록 option을 선택해 준다.

[그림 3.3] busybox menuconfig - static binary 생성

$ make -j16

$ file busybox
busybox: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=22eba145d2d9670ffefbb6cff2842b13fe24b19e, for GNU/Linux 3.7.0, stripped

$ make install
$ cd _install
$ ls -la
합계 20
drwxrwxr-x  5 chyi chyi 4096  2월 20 15:24 .
drwxrwxr-x 37 chyi chyi 4096  2월 20 15:24 ..
drwxrwxr-x  2 chyi chyi 4096  2월 20 15:24 bin
lrwxrwxrwx  1 chyi chyi   11  2월 20 15:24 linuxrc -> bin/busybox
drwxrwxr-x  2 chyi chyi 4096  2월 20 15:24 sbin
drwxrwxr-x  4 chyi chyi 4096  2월 20 15:24 usr
  -> 이 내용을 initramfs 생성 시 통째로 복사하여 사용한다. .............. (A)

<initramfs 만들기>
mkdir initramfs
mkdir -p initramfs/bin initramfs/sbin initramfs/etc initramfs/proc initramfs/sys initramfs/dev initramfs/usr/bin initramfs/usr/sbin
cp -a busybox-1_36_0/_install/* ./initramfs   ............. (A)

$ cd initramfs
$ vi init
  ->아래의 내용을 담은 init file(shell script)을 하나 만든다. init은 kernel이 부팅을 마치면서 마지막으로 실행해 주는 최초의 application이다.
#!/bin/sh

mount -t devtmpfs devtmpfs /dev
mount -t proc none /proc
mount -t sysfs none /sys
exec /bin/sh
~

$ chmod 755 init

$ find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz
  -> initramfs 디렉토리 내용을 토대로 cpio 형태의 압축 파일을 생성하도록 한다.

3.4 QEMU booting하기
모든 준비(kernel image, initramfs file)가 끝났으니, 아래와 같이 arm64용 qemu를 이용하여 linux kernel을 부팅시켜 보자.

$ ./qemu-system-aarch64 -M virt -cpu cortex-a53 -nographic -kernel /mnt/hdd/workspace/Rust/QEMU/Image -append "console=ttyAMA0" -m 2048 -initrd /mnt/hdd/workspace/Rust/QEMU/initramfs.cpio.gz

 -> OK, 아래와 같이 정상적으로 부팅이 진행된다. 😎


[그림 3.4] QEMU 환경에서의 linux kernel booting

다른 터미널 창에서 qemu process를 확인해 보니, 아래와 같은 모습이 보인다. 마치 docker를 사용하는 듯한 느낌도 드는데, CPU & memory 사용량도 많지 않은 것 같고, 안정적이라는 느낌이 든다. 앞으로 qemu를 좀 더 자주 사용해 보아야겠다. 😍

$ ps aux|grep qemu
chyi      505186  0.0  0.7 3568044 249744 pts/17 Sl+  16:39   0:12 ./qemu-system-aarch64 -M virt -cpu cortex-a53 -nographic -kernel /mnt/hdd/workspace/Rust/QEMU/Image -append console=ttyAMA0 -m 2048 -initrd /mnt/hdd/workspace/Rust/QEMU/initramfs.cpio.gz

정상 부팅을 확인하였으니, 이번에는 rust kernel module을 아래 디렉토리로 복사 후, 다시 부팅을 시도하도록 한다.

$ mkdir initramfs/test
$ cp linux/samples/rust/*.ko initramfs/test
$ cd initramfs
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz

$ ./qemu-system-aarch64 -M virt -cpu cortex-a53 -nographic -kernel /mnt/hdd/workspace/Rust/QEMU/Image -append "console=ttyAMA0" -m 2048 -initrd /mnt/hdd/workspace/Rust/QEMU/initramfs.cpio.gz

$ cd test
/test # ls -la
total 2324
drwxrwxr-x    2 1000     1000           260 Feb 20 07:01 .
drwxrwxr-x   11 1000     1000           280 Feb 20 07:39 ..
-rw-rw-r--    1 1000     1000        241720 Feb 20 07:01 rust_configfs.ko
-rw-rw-r--    1 1000     1000        280352 Feb 20 07:01 rust_debugfs.ko
-rw-rw-r--    1 1000     1000        316336 Feb 20 07:01 rust_debugfs_scoped.ko
-rw-rw-r--    1 1000     1000        251520 Feb 20 07:01 rust_dma.ko
-rw-rw-r--    1 1000     1000        181072 Feb 20 07:01 rust_driver_auxiliary.ko
-rw-rw-r--    1 1000     1000        136344 Feb 20 07:01 rust_driver_faux.ko
-rw-rw-r--    1 1000     1000        214584 Feb 20 07:01 rust_driver_pci.ko
-rw-rw-r--    1 1000     1000        209392 Feb 20 07:01 rust_driver_platform.ko
-rw-rw-r--    1 1000     1000        151840 Feb 20 07:01 rust_minimal.ko
-rw-rw-r--    1 1000     1000        198304 Feb 20 07:01 rust_misc_device.ko
-rw-rw-r--    1 1000     1000        165424 Feb 20 07:01 rust_print.ko


/test # insmod ./rust_minimal.ko  
[  859.812741] rust_minimal: Rust minimal sample (init)
[  859.812971] rust_minimal: Am I built-in? false
/test #  
/test # insmod ./rust_driver_platform.ko
/test # lsmod
rust_driver_platform 12288 0 - Live 0xffffc75c46f8b000
rust_minimal 12288 0 - Live 0xffffc75c46f85000

OK, 모두 정상적으로 구동된다.

[그림 3.5] QEMU booting 후, rust kernel module 구동하기

끝으로, kernel module을 테스트하고자 할 때마다, initramfs.cpio.gz 파일을 생성하는 것이 번거로울 수도 있으니, 아래와 같이 mount하여 사용하는 방법도 소개해 본다.

./qemu-system-aarch64 -M virt -cpu cortex-a53 -nographic -kernel /mnt/hdd/workspace/Rust/QEMU/Image -append "console=ttyAMA0" -m 2048 -initrd /mnt/hdd/workspace/Rust/QEMU/initramfs.cpio.gz -virtfs local,path=<YOUR_DIR_PATH>,security_model=none,mount_tag=<YOUR_MOUNT_TAG>
 -> <YOUR_DIR_PATH> 디렉토리에 필요한 kernel module을 복사해 둔다.
 -> 부팅 후, mount_tag로 지정한 내용(예: modules)을 이용하여 mount하여 사용한다.

<QEMU 기반 linux booting 후>
~ # mount  
rootfs on / type rootfs (rw,size=989308k,nr_inodes=247327)
devtmpfs on /dev type devtmpfs (rw,relatime,size=989308k,nr_inodes=247327,mode=755)
none on /proc type proc (rw,relatime)
none on /sys type sysfs (rw,relatime)

~ # mkdir test2

~ # mount -t 9p -o trans=virtio modules ./test2

~ # df
Filesystem           1K-blocks      Used Available Use% Mounted on
devtmpfs                989308         0    989308   0% /dev
modules              1921724544 1627240448 196792064  89% /test2

 /test2 # mount
rootfs on / type rootfs (rw,size=989308k,nr_inodes=247327)
devtmpfs on /dev type devtmpfs (rw,relatime,size=989308k,nr_inodes=247327,mode=755)
none on /proc type proc (rw,relatime)
none on /sys type sysfs (rw,relatime)
modules on /test2 type 9p (rw,relatime,access=client,trans=virtio)

~ # cd test2/
/test2 # ls -la
total 2328
drwxrwxr-x    2 1000     1000          4096 Feb 21 07:02 .
drwxrwxr-x   12 1000     1000           300 Feb 21 07:03 ..
-rw-rw-r--    1 1000     1000        241720 Feb 21 07:02 rust_configfs.ko
-rw-rw-r--    1 1000     1000        280352 Feb 21 07:02 rust_debugfs.ko
-rw-rw-r--    1 1000     1000        316336 Feb 21 07:02 rust_debugfs_scoped.ko
-rw-rw-r--    1 1000     1000        251520 Feb 21 07:02 rust_dma.ko
-rw-rw-r--    1 1000     1000        181072 Feb 21 07:02 rust_driver_auxiliary.ko
-rw-rw-r--    1 1000     1000        136344 Feb 21 07:02 rust_driver_faux.ko
-rw-rw-r--    1 1000     1000        214584 Feb 21 07:02 rust_driver_pci.ko
-rw-rw-r--    1 1000     1000        209392 Feb 21 07:02 rust_driver_platform.ko
-rw-rw-r--    1 1000     1000        151840 Feb 21 07:02 rust_minimal.ko
-rw-rw-r--    1 1000     1000        198304 Feb 21 07:02 rust_misc_device.ko
-rw-rw-r--    1 1000     1000        165424 Feb 21 07:02 rust_print.ko


______________________________________________
개발 환경이 어느 정도 마무리되었으니, 이어지는 장에서는 Rust kernel module을 만드는 방법을 구체적으로 파악해 보도록 하자.


4. Linux Kernel Module in Rust
이번 장에서는 Rust kernel module(or device driver)을 만드는 방법에 관하여 좀더 깊게 들어가 보도록 하자.

"Kernel Rust has a different flavour to idiomatic userspace Rust (different APIs, different build system, failable allocations, no dependencies, etc)."

4.1 Rust Kernel 아키텍쳐
기존에 C 언어로 linux kernel module을 만들었던 것을 떠올려 보면, Linux kernel code를 작성할 때에는 libc에서 제공하는 다양한 함수(printf, strcmp, exit, malloc, free 등)를 이용할 수도 없으며, thread를 만들기 위해 pthread library를 사용할 수도 없었다. Linux kernel programming을 한다는 것은, kernel에서 자체적으로 구현한 코드(일종의 framework으로 볼 수 있음)을 사용해야만 한다는 뜻이다.

그렇다면, Rust kernel module의 경우는 어떨까 ? Rust로 사용자 영역에서 동작하는 application code를 작성하는 방식과 동일하게 kernel code를 만들면 되는 것일까 ? 대답은 당연히 "아니오"이다.

사설이 좀 길었는데, Rust로 kernel module(or device driver)을 작성하기 위해서는 kernel에 구현되어 있는 rust framework을 이해하고 있어야만 가능하다는 말을 하고 싶었을 뿐이다. 😂

[그림 4.1] /linux/rust 디렉토리 파일 목록

아래 2개의 그림은 kernel에 구현된 rust framework(/linux/rust directory)의 대략적인 architecture를 표현한 것이다. 드라이버나 커널 모듈을 구현하는 일반 사용자는 kernel crate를 사용하여 safe한 방식으로 rust code를 작성할 수 있는데 반하여,  kernel crate 내부적으로는 binding crate를 통해 C code를 unsafe한 방법으로 호출하는 구조로 설계되어 있음을 알 수가 있다.

Linux C code => rust-bindgen(자동 호출) => bindings crate => kernel crate => drivers/kernel module 구현

[그림 4.2] Rust driver 아키텍쳐(1) [출처 - 참고문헌 14]


[그림 4.3] Rust driver 아키텍쳐(2) [출처 - 참고문헌 13]

Linux kernel에서 Rust programming을 한다는 것은 rust를 사용하여 처음부터 새로운 것을 만드는 것이 아니라, 지금까지 오랜 세월 동안 안정적으로 개발되어 온 C code를 재활용하여 동일한 작업(driver or kernel module 구현)을 할 수 있게 하는 것을 의미한다.

Rust kernel 아키텍쳐와 관련한 보다 세부적인 사항에 대해서는 이전 posting을 참조하기 바란다.


4.2 Hello Rust 커널 모듈 돌려보기
이번에 살펴볼 hello rust kernel module은 초기화 시(init 함수 호출), "Hello rust world!\n" 문자열을 출력하고, 종료 시(drop 함수 호출) "Have a nice day!\n" 문자열을 출력하는 초간단 모듈이다.

<hello_rust.rs>
--------------------------------------------------------------------
// SPDX-License-Identifier: GPL-2.0

#![allow(missing_docs)]

use kernel::prelude::*;

module! { //[1] module! 매크로는 C kernel module에서 말하는 MODULE_AUTHOR( ),
//MODULE_DESCRIPTION( ), MODULE_LICENSE( ) 등에 해당하는 코드로 보면 된다.
type: HelloRust,
name: "hello_rust",
authors: ["Slowboot"],
description: "hello rust example",
license: "GPL",
}

struct HelloRust; //[2] HelloRust라는 구조체 변수를 선언한다.

impl kernel::Module for HelloRust { //[3] 여기에서 연관함수 혹은 메서드를 정의한다.
// init( ) 연관 함수는 module init에 해당한다.
fn init(_module: &'static ThisModule) -> Result<Self> {
pr_info!("Hello rust world!\n");

Ok(HelloRust)
}
}

impl Drop for HelloRust { // drop( ) 메서드는 module exit에 해당한다.
fn drop(&mut self) {
pr_info!("Have a nice day!\n");
}
}
--------------------------------------------------------------------

Rust kernel module이 C kernel module과 특별히 다른 점이 있다면, rust kernel module은 무조건 structure 변수를 하나 선언하고, impl { init() and drop() } 내에서 연관함수나 메서드를 구현하는 형태로 코드가 구성되어 있다는 점이다.

<Makefile>
--------------------------------------------------------------------
# SPDX-License-Identifier: GPL-2.0

obj-m := hello_rust.o

#KDIR ?= /lib/modules/$(shell uname -r)/build
KDIR=<YOUR_PATH>/linux
PWD := $(shell pwd)

.PHONY: modules
modules:
$(MAKE) ARCH=arm64 LLVM=1 -C $(KDIR) M=$(PWD) modules

.PHONY: clean
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
--------------------------------------------------------------------

Makefile 안에 ARCH=arm64 LLVM=1 compile option을 직접 설정해 주었으므로, make 명령을 실행하는 것만으로도 kernel module 생성이 가능하다.

$ make
$ ls -l hello_rust.ko 
-rw-rw-r-- 1 chyi chyi 7184  2월 21 15:47 hello_rust.ko

<Target board>
$ insmod ./hello_rust.ko
$ lsmod

[그림 4.4] hello rust 예제 실행 모습

4.3 rust_minimal 커널 모듈 돌려보기
이번에 살펴볼 rust kernel module은 초기화 시 i32 kernel Vector(KVec<i32>)를 하나 만들고, 여기에 숫자 72, 108, 200을 차례로 추가해 주는 단순한 코드이다. 

--------------------------------------------------------------------
// SPDX-License-Identifier: GPL-2.0

//! Rust minimal sample.

use kernel::prelude::*;

module! { //[1] module에 대한 부가 설명
type: RustMinimal,
name: "rust_minimal",
authors: ["Rust for Linux Contributors"],
description: "Rust minimal sample",
license: "GPL",
}

struct RustMinimal { //[2] RustMinimal이라는 구조체 변수 선언
numbers: KVec<i32>,
}

impl kernel::Module for RustMinimal { //[3] RustMinimal 구조체에 대한 kernel::Module 트레잇 관련 코드 구현
fn init(_module: &'static ThisModule) -> Result<Self> { //[3-1] module init
pr_info!("Rust minimal sample (init)\n");
pr_info!("Am I built-in? {}\n", !cfg!(MODULE));

let mut numbers = KVec::new(); //[3-2] kernel vector 생성 및 72, 108, 200 추가
numbers.push(72, GFP_KERNEL)?;
numbers.push(108, GFP_KERNEL)?;
numbers.push(200, GFP_KERNEL)?;

Ok(RustMinimal { numbers }) //[3-3] RustMinimal 구조체 instance return
}
}

impl Drop for RustMinimal { //[4] RustMinimal 구조체에 대한 Drop 트레잇 코드 구현
fn drop(&mut self) { //[4-1] module exit
pr_info!("My numbers are {:?}\n", self.numbers);
pr_info!("Rust minimal sample (exit)\n");
} // block이 끝날때, numbers KVec에 대한 memory(stack & heap) 영역 자동 해제
}
--------------------------------------------------------------------

<Makefile>
--------------------------------------------------------------------
# SPDX-License-Identifier: GPL-2.0

obj-m := rust_minimal.o

KDIR=<YOUR_PATH>/linux
PWD := $(shell pwd)

.PHONY: modules
modules:
$(MAKE) ARCH=arm64 LLVM=1 -C $(KDIR) M=$(PWD) modules

.PHONY: clean
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
--------------------------------------------------------------------

$ make
$ ls -la *.ko
-rw-rw-r-- 1 chyi chyi 160200  2월 21 15:54 rust_minimal.ko

<Target board>
$ insmod ./rust_minimal.ko
$ rmmod rust_minimal

[그림 4.5] rust-minimal kernel module 예제 실행 모습


4.4 rust misc device 커널 모듈 돌려보기
이번에 살펴볼 rust kernel modue은 misc device driver로 /dev/rust-misc-device 파일을 통해 사용자 application과 통신(open, ioctl, close)하는 역할을 한다.

<rust_misc_device.rs> - kernel module
--------------------------------------------------------------------
use core::pin::Pin;

use kernel::{
c_str,
device::Device,
fs::{File, Kiocb},
ioctl::{_IO, _IOC_SIZE, _IOR, _IOW},
iov::{IovIterDest, IovIterSource},
miscdevice::{MiscDevice, MiscDeviceOptions, MiscDeviceRegistration},
new_mutex,
prelude::*,
sync::{aref::ARef, Mutex},
uaccess::{UserSlice, UserSliceReader, UserSliceWriter},
};

const RUST_MISC_DEV_HELLO: u32 = _IO('|' as u32, 0x80);
const RUST_MISC_DEV_GET_VALUE: u32 = _IOR::<i32>('|' as u32, 0x81);
const RUST_MISC_DEV_SET_VALUE: u32 = _IOW::<i32>('|' as u32, 0x82);

module! { //[1] module에 대한 부가 설명
type: RustMiscDeviceModule,
name: "rust_misc_device",
authors: ["Lee Jones"],
description: "Rust misc device sample",
license: "GPL",
}

#[pin_data]
struct RustMiscDeviceModule { //[2] RustMiscDeviceModule이라는 구조체 변수 선언
#[pin]
_miscdev: MiscDeviceRegistration<RustMiscDevice>,
}

impl kernel::InPlaceModule for RustMiscDeviceModule { //[3] RustMiscDeviceModule 구조체에 대한
//kernel::InPlaceModule 트레잇 관련 코드 구현
fn init(_module: &'static ThisModule) -> impl PinInit<Self, Error> { //[3-1] module_init
pr_info!("Initialising Rust Misc Device Sample\n");

let options = MiscDeviceOptions {
name: c_str!("rust-misc-device"),
};

try_pin_init!(Self {
_miscdev <- MiscDeviceRegistration::register(options),
})
}
}

struct Inner {
value: i32,
buffer: KVVec<u8>,
}

#[pin_data(PinnedDrop)]
struct RustMiscDevice { //[4] RustMiscDevice이라는 구조체 변수 선언
#[pin]
inner: Mutex<Inner>,
dev: ARef<Device>,
}

#[vtable]
impl MiscDevice for RustMiscDevice { //[5] RustMiscDevice 구조체에 대하여 MiscDevice 트레잇 관련 코드 구현
//open(), read_ter(), write_iter(), ioctl() 연관 함수 구현
type Ptr = Pin<KBox<Self>>;

fn open(_file: &File, misc: &MiscDeviceRegistration<Self>) -> Result<Pin<KBox<Self>>> {
let dev = ARef::from(misc.device());

dev_info!(dev, "Opening Rust Misc Device Sample\n");

KBox::try_pin_init(
try_pin_init! {
RustMiscDevice {
inner <- new_mutex!(Inner {
value: 0_i32,
buffer: KVVec::new(),
}),
dev: dev,
}
},
GFP_KERNEL,
)
}

fn read_iter(mut kiocb: Kiocb<'_, Self::Ptr>, iov: &mut IovIterDest<'_>) -> Result<usize> {
let me = kiocb.file();
dev_info!(me.dev, "Reading from Rust Misc Device Sample\n");

let inner = me.inner.lock();
// Read the buffer contents, taking the file position into account.
let read = iov.simple_read_from_buffer(kiocb.ki_pos_mut(), &inner.buffer)?;

Ok(read)
}

fn write_iter(mut kiocb: Kiocb<'_, Self::Ptr>, iov: &mut IovIterSource<'_>) -> Result<usize> {
let me = kiocb.file();
dev_info!(me.dev, "Writing to Rust Misc Device Sample\n");

let mut inner = me.inner.lock();

// Replace buffer contents.
inner.buffer.clear();
let len = iov.copy_from_iter_vec(&mut inner.buffer, GFP_KERNEL)?;

// Set position to zero so that future `read` calls will see the new contents.
*kiocb.ki_pos_mut() = 0;

Ok(len)
}

fn ioctl(me: Pin<&RustMiscDevice>, _file: &File, cmd: u32, arg: usize) -> Result<isize> {
dev_info!(me.dev, "IOCTLing Rust Misc Device Sample\n");

// Treat the ioctl argument as a user pointer.
let arg = UserPtr::from_addr(arg);
let size = _IOC_SIZE(cmd);

match cmd {
RUST_MISC_DEV_GET_VALUE => me.get_value(UserSlice::new(arg, size).writer())?,
RUST_MISC_DEV_SET_VALUE => me.set_value(UserSlice::new(arg, size).reader())?,
RUST_MISC_DEV_HELLO => me.hello()?,
_ => {
dev_err!(me.dev, "-> IOCTL not recognised: {}\n", cmd);
return Err(ENOTTY);
}
};

Ok(0)
}
}

#[pinned_drop]
impl PinnedDrop for RustMiscDevice { //[6] RustMiscDevice 구조체에 대한 PinnedDrop 트레잇 코드 구현
fn drop(self: Pin<&mut Self>) {
dev_info!(self.dev, "Exiting the Rust Misc Device Sample\n");
}
}

impl RustMiscDevice { //[7] RustMiscDevice 구조체에 대한 나머지 method(set_value, get_value, hello) 구현
fn set_value(&self, mut reader: UserSliceReader) -> Result<isize> {
let new_value = reader.read::<i32>()?;
let mut guard = self.inner.lock();

dev_info!(
self.dev,
"-> Copying data from userspace (value: {})\n",
new_value
);

guard.value = new_value;
Ok(0)
}

fn get_value(&self, mut writer: UserSliceWriter) -> Result<isize> {
let guard = self.inner.lock();
let value = guard.value;

// Free-up the lock and use our locally cached instance from here
drop(guard);

dev_info!(
self.dev,
"-> Copying data to userspace (value: {})\n",
&value
);

writer.write::<i32>(&value)?;
Ok(0)
}

fn hello(&self) -> Result<isize> {
dev_info!(self.dev, "-> Hello from the Rust Misc Device\n");

Ok(0)
}
}
--------------------------------------------------------------------

<misc_dev.c> - userspace application
/dev/rust-misc-device 파일을 open한 후, ioctl로 driver를 제어하는 간단한 routine이다.
--------------------------------------------------------------------
// SPDX-License-Identifier: GPL-2.0

// Copyright (C) 2024 Google LLC.

// Rust misc device sample.
//
// Below is an example userspace C program that exercises this sample's functionality.
//

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define RUST_MISC_DEV_FAIL _IO('|', 0)
#define RUST_MISC_DEV_HELLO _IO('|', 0x80)
#define RUST_MISC_DEV_GET_VALUE _IOR('|', 0x81, int)
#define RUST_MISC_DEV_SET_VALUE _IOW('|', 0x82, int)

int main() {
int value, new_value;
int fd, ret;

// Open the device file
printf("Opening /dev/rust-misc-device for reading and writing\n");
fd = open("/dev/rust-misc-device", O_RDWR);
if (fd < 0) {
perror("open");
return errno;
}

// Make call into driver to say "hello"
printf("Calling Hello\n");
ret = ioctl(fd, RUST_MISC_DEV_HELLO, NULL);
if (ret < 0) {
perror("ioctl: Failed to call into Hello");
close(fd);
return errno;
}

// Get initial value
printf("Fetching initial value\n");
ret = ioctl(fd, RUST_MISC_DEV_GET_VALUE, &value);
if (ret < 0) {
perror("ioctl: Failed to fetch the initial value");
close(fd);
return errno;
}

value++;

// Set value to something different
printf("Submitting new value (%d)\n", value);
ret = ioctl(fd, RUST_MISC_DEV_SET_VALUE, &value);
if (ret < 0) {
perror("ioctl: Failed to submit new value");
close(fd);
return errno;
}

// Ensure new value was applied
printf("Fetching new value\n");
ret = ioctl(fd, RUST_MISC_DEV_GET_VALUE, &new_value);
if (ret < 0) {
perror("ioctl: Failed to fetch the new value");
close(fd);
return errno;
}

if (value != new_value) {
printf("Failed: Committed and retrieved values are different (%d - %d)\n", value, new_value);
close(fd);
return -1;
}

// Call the unsuccessful ioctl
printf("Attempting to call in to an non-existent IOCTL\n");
ret = ioctl(fd, RUST_MISC_DEV_FAIL, NULL);
if (ret < 0) {
perror("ioctl: Succeeded to fail - this was expected");
} else {
printf("ioctl: Failed to fail\n");
close(fd);
return -1;
}

// Close the device file
printf("Closing /dev/rust-misc-device\n");
close(fd);

printf("Success\n");
return 0;
}
--------------------------------------------------------------------

위의 kernel module과 사용자 application을 순차적으로 실행해 보면 다음과 같다.

[그림 4.6] rust_misc_device kernel module 예제 실행 모습

[그림 4.7] misc_dev_app  사용자 영역 application 예제 실행 모습

4.5 그 밖의 커널 모듈(or 디바이스 드라이버)에 관하여
지금까지는 아주 기초적인 rust 기반 kernel module에 대해 살펴 보았다. 하지만, 실제 환경에서는 이러한 내용 보다는 platform driver, i2c driver, spi driver, usb driver, pci driver, network driver 등 다양한 device driver를 구현해야 하는 상황에 직면하게 된다. 다행히도 이들과 관련한 sample code가 linux 최신 커널에 일부 포함되어 있으니, 1차적으로 이들을 참조하면 될 것으로 보인다. 😂

________________________________
지금까지 2개의 posting을 통해서 Rust-For-Linux에 관하여 대략적으로 살펴 보았다. (가능하다면) 이어지는 posting에서는 Zephyr RTOS 상에서 Rust programming을 하는 방법을 소개하는 시간을 가져 보도록 하겠다. 😎

To be continued...


5. References
[1] embedded-linux-beagleplay-labs.pdf, bootlin
[2] linux-kernel-beagleplay-labs.pdf, bootlin
[3] Embedded Linux Essential Handbook, Mohammed Billoo, Packt
[4] 실전! 러스트로 배우는 리눅스 커널 프로그래밍, 김백기, 우충기, 위키북스
[5] embedded-linux-slides.pdf
  => Embeddedd Linux

[6] https://joone.net/2023/01/19/49-llvm-%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8-1-2/?ref=pangyoalto.com
[7] https://joone.net/2023/01/19/50-llvm-%ed%94%84%eb%a1%9c%ec%a0%9d%ed%8a%b8-2-2/
[8] https://www.praj.in/posts/2020/graalvms-secret-llvm-backend/
[9] https://pangyoalto.com/clang-and-optimization/
[10] https://github.com/torvalds/linux/blob/master/Documentation/rust/quick-start.rst
  => LLVM에 관하여

[11] https://rust-lang.github.io/ctcft/slides/2021-11-22_-_Rust_CTCFT_-_Rust_for_Linux.pdf
[12] Rust in the Linux Kernel: Analyzing Rust Implementations of Device Drivers, Fabian Garber, BSc
[13] https://www.usenix.org/system/files/atc24-li-hongyu.pdf
[14] https://archive.fosdem.org/2025/events/attachments/fosdem-2025-6507-rust-for-linux/slides/237976/2025-02-0_iwSaMYM.pdf
[15] https://events.linuxfoundation.org/wp-content/uploads/2022/10/Wedson-Almeida-Filho-LF-Writing-Linux-Kernel-Modules-in-Rust.pdf
[16] https://events.linuxfoundation.org/wp-content/uploads/2022/04/2022-04-20-Linux-Foundation-LF-Live-Mentorship-Series-Rust-for-Linux-Code-Documentation-Tests.pdf
  => Rust for Linux에 관하여

[17] https://www.redox-os.org/ko/
  => Redox OS
[18] https://github.com/zephyrproject-rtos/zephyr-lang-rust
  => Rust for Zephyr RTOS

[19] And, Google and Gemini~


Slowboot