Rust組込みLinux開発:2026年ベアメタルからデバイスドライバまでの完全実践ガイド

编程语言

組込み開発のペインポイント、いくつ当てはまりましたか?

C言語で組込み開発をすると、メモリリークとセグメンテーションフォールトは日常茶飯事——本番デバイスが動いているうちにクラッシュし、リモートデバッグは基本的に推測頼みです。クロスコンパイル環境の構築だけで半日かかり、ターゲットボードを変えるとやり直しです。暗黙の未定義動作は、テスト環境では問題なくても本番で間欠的にクラッシュし、再現できません。

Rustのゼロコスト抽象化と所有権システムにより、組込み開発はついに安全かつ効率的になります。2026年、Rust組込みエコシステムは十分に成熟しており、Cからの移行を真剣に検討すべき時です。


Rust組込みコア概念

概念 説明 C言語との比較
no_std 標準ライブラリ不使用、ベアメタル・カーネル開発に適する Cのfreestandingモード
所有権システム コンパイル時メモリ安全性保証、GC不要 Cは手動管理、エラー発生しやすい
クロスコンパイル ネイティブマルチターゲットクロスコンパイル対応 クロスツールチェーンが必要
Embedded HAL ハードウェア抽象レイヤ、統一ペリフェラルインタフェース CのHALに似ているが型安全
defmt リソース制限環境向け高効率ログフレームワーク printfの軽量代替

主要crateエコシステム:

crate 用途 スター数
cortex-m ARM Cortex-M低レベルサポート 1.2k
embedded-hal ハードウェア抽象レイヤtrait 1.8k
linux-embedded-hal LinuxプラットフォームHAL実装 350+
sysfs-gpio Linux sysfs GPIO操作 200+
tokio-serial 非同期シリアル通信 500+

問題の深掘り:なぜ組込みにRustが必要なのか?

従来のC言語組込み開発が直面するコア問題:

  1. メモリ安全性:バッファオーバーフローが組込み脆弱性の60%以上を占める
  2. 並行安全性:割り込みとメインループのデータ競合は検出困難
  3. ツールチェーンの断片化:異なるベンダのクロスコンパイルツールチェーンが互換性なし

Rustのソリューション比較:

問題 C言語ソリューション Rustソリューション 利点
バッファオーバーフロー コードレビュー+静的解析 コンパイル時境界チェック ゼロランタイムオーバーヘッド
データ競合 volatile+割り込み無効化 Send/Sync trait コンパイル時保証
ツールチェーン断片化 ベンダ固有ツールチェーン rustup統一管理 1ツールチェーンでマルチターゲット
未定義動作 仕様による制約 unsafeブロックの明示的マーク 安全境界が明確

ステップバイステップ:ベアメタルからLinuxドライバまで

ステップ1:クロスコンパイル環境の構築

# Rustとクロスコンパイルターゲットのインストール
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add armv7-unknown-linux-gnueabihf
rustup target add aarch64-unknown-linux-gnu
rustup target add thumbv7em-none-eabihf

# クロスリンカのインストール
sudo apt install gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu

# cargoクロスコンパイルの設定
mkdir -p .cargo
cat > .cargo/config.toml << 'EOF'
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"

[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
EOF

ステップ2:no_stdベアメタルプログラム

#![no_main]
#![no_std]

use cortex_m_rt::entry;
use embedded_hal::digital::OutputPin;
use panic_halt as _;

#[entry]
fn main() -> ! {
    let dp = pac::Peripherals::take().unwrap();
    let cp = cortex_m::Peripherals::take().unwrap();
    let mut rcc = dp.RCC.constrain();
    let mut gpioa = dp.GPIOA.split(&mut rcc.ahb);
    let mut led = gpioa.pa5.into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper);
    let mut delay = cp.SYST.delay(&mut rcc.clocks);

    loop {
        led.set_high().ok();
        delay.delay_ms(500u32);
        led.set_low().ok();
        delay.delay_ms(500u32);
    }
}

ステップ3:Linuxユーザースペースデバイスドライバ

use std::fs;
use std::io::{self, Read, Write};
use std::path::Path;
use std::time::Duration;

pub struct GpioPin {
    pin_num: u32,
    base_path: String,
}

impl GpioPin {
    pub fn new(pin_num: u32) -> io::Result<Self> {
        let base_path = format!("/sys/class/gpio/gpio{}", pin_num);
        if !Path::new(&base_path).exists() {
            fs::write("/sys/class/gpio/export", pin_num.to_string())?;
        }
        std::thread::sleep(Duration::from_millis(100));
        Ok(Self { pin_num, base_path })
    }

    pub fn set_direction(&self, direction: &str) -> io::Result<()> {
        fs::write(format!("{}/direction", self.base_path), direction)
    }

    pub fn set_value(&self, value: u8) -> io::Result<()> {
        fs::write(format!("{}/value", self.base_path), value.to_string())
    }

    pub fn get_value(&self) -> io::Result<u8> {
        fs::read_to_string(format!("{}/value", self.base_path))?
            .trim().parse::<u8>()
            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
    }
}

impl Drop for GpioPin {
    fn drop(&mut self) {
        let _ = fs::write("/sys/class/gpio/unexport", self.pin_num.to_string());
    }
}

fn main() -> io::Result<()> {
    let led = GpioPin::new(17)?;
    led.set_direction("out")?;
    for i in 0..10 {
        led.set_value(1)?;
        std::thread::sleep(Duration::from_millis(200));
        led.set_value(0)?;
        std::thread::sleep(Duration::from_millis(200));
        println!("Blink #{}", i + 1);
    }
    Ok(())
}

ステップ4:Cargo.toml設定

[package]
name = "embedded-linux-driver"
version = "0.1.0"
edition = "2021"

[dependencies]
embedded-hal = "1.0"
linux-embedded-hal = "0.4"
nix = { version = "0.29", features = ["ioctl", "fs"] }

[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
strip = true
panic = "abort"

ステップ5:IoTデバイスMQTT通信

use std::time::Duration;
use std::thread;

struct SensorReading {
    sensor_id: String,
    temperature: f32,
    humidity: f32,
    timestamp: u64,
}

impl SensorReading {
    fn to_json(&self) -> String {
        format!(
            r#"{{"sensorId":"{}","temperature":{:.2},"humidity":{:.2},"timestamp":{}}}"#,
            self.sensor_id, self.temperature, self.humidity, self.timestamp
        )
    }
}

fn main() {
    loop {
        let reading = SensorReading {
            sensor_id: "SENSOR-001".to_string(),
            temperature: 23.5,
            humidity: 55.0,
            timestamp: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(),
        };
        println!("Publishing: {}", reading.to_json());
        thread::sleep(Duration::from_secs(5));
    }
}

完全コード:組込みLinuxデバイスモニターデーモン

use std::fs;
use std::io::{self, Write};
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};

static RUNNING: AtomicBool = AtomicBool::new(true);

struct DeviceMonitor {
    gpio_pins: Vec<u32>,
    i2c_bus: u8,
    watch_dir: PathBuf,
    report_interval: Duration,
}

impl DeviceMonitor {
    fn new(gpio_pins: Vec<u32>, i2c_bus: u8, watch_dir: &str) -> Self {
        Self { gpio_pins, i2c_bus, watch_dir: PathBuf::from(watch_dir), report_interval: Duration::from_secs(10) }
    }

    fn read_gpio(&self, pin: u32) -> io::Result<u8> {
        fs::read_to_string(format!("/sys/class/gpio/gpio{}/value", pin))?
            .trim().parse::<u8>().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
    }

    fn read_cpu_temp(&self) -> io::Result<f32> {
        let raw = fs::read_to_string("/sys/class/thermal/thermal_zone0/temp")?;
        Ok(raw.trim().parse::<f32>().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? / 1000.0)
    }

    fn run(&self) -> io::Result<()> {
        let start = Instant::now();
        while RUNNING.load(Ordering::Relaxed) {
            let temp = self.read_cpu_temp().unwrap_or(0.0);
            println!("[{:?}] CPU: {:.1}°C", start.elapsed(), temp);
            std::thread::sleep(self.report_interval);
        }
        Ok(())
    }
}

fn main() -> io::Result<()> {
    ctrlc::set_handler(|| RUNNING.store(false, Ordering::Relaxed))
        .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
    DeviceMonitor::new(vec![17, 27, 22], 1, "/var/log/device-monitor").run()
}

よくある落とし穴ガイド

落とし穴1:クロスコンパイルリンカが見つからない

linker 'arm-linux-gnueabihf-gcc' not found が最も一般的なエラー。

解決策.cargo/config.tomlでリンカパスを明示的に指定、gcc-arm-linux-gnueabihfがインストール済みであることを確認。

落とし穴2:no_stdでprintln!が使用不可

no_std環境には標準ライブラリがないため、println!マクロは使用できません。

解決策defmt::info!またはcortex_m_semihosting::hprintln!を使用。

落とし穴3:GPIO権限不足

/sys/class/gpioの操作にroot権限またはgpioグループが必要。

解決策sudo usermod -aG gpio $USER、udevルールを設定。

落とし穴4:I2C/SPIデバイスオープン失敗

/dev/i2c-1が存在しない場合、カーネルモジュールが未有効。

解決策sudo modprobe i2c-devsudo dtparam=i2c_arm=on

落とし穴5:リリースビルドが大きすぎる

デフォルトリリースビルドは数MBになる可能性があります。

解決策opt-level = "z"lto = truestrip = truepanic = "abort"を設定。


エラートラブルシューティング

# エラーメッセージ 原因 解決方法
1 linker 'arm-linux-gnueabihf-gcc' not found クロスコンパイルツールチェーン未インストール sudo apt install gcc-arm-linux-gnueabihf
2 could not find native static library 'm' 数学ライブラリ不足 アーキテクチャ別libc-devをインストール
3 can't find crate for std ターゲット未インストール rustup target add <target>
4 panic handler not found no_stdでpanicハンドラ不足 panic-haltを追加
5 Permission denied: /sys/class/gpio/export GPIO操作権限なし gpioグループに追加
6 No such file: /dev/i2c-1 I2Cカーネルモジュール未有効 sudo modprobe i2c-dev
7 SPI transfer failed: Invalid argument SPI設定エラー SPIパラメータを確認
8 stack overflow in thread main スタックサイズ不足 スタックサイズを増加
9 undefined symbol: __aeabi_uidiv ソフト浮動小数点ターゲット ハード浮動小数点ターゲットを使用
10 failed to run custom command build.rsの外部ツール不在 依存関係をインストール

高度な最適化

1. ゼロアロケーションログ:defmt

#![no_std]
use defmt_rtt as _;
use panic_probe as _;

fn process_sensor(value: f32) {
    defmt::info!("Sensor: {:.2}", value);
}

2. 非同期IoT通信

use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;

async fn send_telemetry(host: &str, port: u16, data: &[u8]) -> std::io::Result<()> {
    let mut stream = TcpStream::connect((host, port)).await?;
    stream.write_all(data).await?;
    Ok(())
}

3. ファームウェアOTAアップデート

use std::io::Read;
use sha2::{Sha256, Digest};

fn verify_checksum(path: &str, expected: &str) -> std::io::Result<bool> {
    let mut file = std::fs::File::open(path)?;
    let mut hasher = Sha256::new();
    let mut buf = [0u8; 8192];
    loop {
        let n = file.read(&mut buf)?;
        if n == 0 { break; }
        hasher.update(&buf[..n]);
    }
    Ok(format!("{:x}", hasher.finalize()) == expected)
}

比較分析

次元 Rust組込み C組込み Zig組込み MicroPython
メモリ安全性 コンパイル時保証 手動管理 部分保証 ランタイムGC
ランタイムオーバーヘッド ゼロコスト なし 極小 大きい
クロスコンパイル rustup統一 ベンダツールチェーン 内蔵 インタープリタ
エコシステム成熟度 非常に成熟 初期
学習曲線
デバッグサポート probe-run+defmt GDB/OpenOCD 内蔵 REPL
バイナリサイズ 最小
リアルタイム性 保証可能 保証可能 保証可能 保証不可

まとめ:Rustは組込み開発にコンパイル時メモリ安全性とゼロコスト抽象化のユニークな組み合わせをもたらします。ベアメタルno_std開発からLinuxユーザースペースドライバまで、Rustの型システムはほとんどのメモリエラーと並行問題をコンパイル時に捕捉します。2026年のRust組込みエコシステムはCほど成熟していませんが、HAL抽象レイヤとクロスコンパイルツールチェーンは本番使用に十分です。Linuxユーザースペースドライバから始め、徐々にno_stdベアメタル開発に深入りすることで、安全性と開発効率の最適なバランスを見つけられます。


オンラインツールおすすめ

  • JSONフォーマッター:/ja/json/format — IoTデバイスデータとセンサー読取値のフォーマット
  • Base64エンコード/デコード:/ja/encode/base64 — ファームウェアアップデートパッケージとデバイス証明書の処理
  • Curl to Code:/ja/dev/curl-to-code — APIデバッグcurlコマンドをRust HTTPクライアントコードに変換

ブラウザローカルツールを無料で試す →

#Rust#嵌入式#Linux#no_std#交叉编译#设备驱动#IoT#固件开发