Rustのプロジェクトの中でCを使う

この記事ではRustのビルドシステムであるCargoを使って、Cのコードをビルドし、それをRustのコードから呼び出す方法について紹介します。
※記事作成時のRustのバージョンはrustc 1.33.0です。

この記事で取り扱うRustの知識とクレートは次の通りです。


また、この記事に載っているソースコードは全て次のリポジトリに置いてあります。
ryochack/rust-ffi-c-tutorial

事前準備

cargo-editというCargo.tomlへのクレートの追加や削除を楽にしてくれるコマンドをこの記事では使用しています。
必須ではありませんが、とても便利なコマンドなので是非インストールしてみてください。

$ cargo install cargo-edit

目次

次の順番で説明を進めます。

  1. Cのコードを書く
  2. ccクレートを使ってCのコードをCargoからビルドする
  3. bindgenクレートを使ってRustバインディングを生成する
  4. bindgenで生成されたコードをよりRustらしいコードでラップする

1. Cのコードを書く

Cのコードは何でもいいのですが、今回はフィボナッチ数列を返すモジュールを用意します。

fibonacci.h

#include <stdint.h>

typedef void* Fibonacci_t;

/* Create fibonacci instance */
Fibonacci_t* fibo_new();
/* Get latest fibonacci value */
uint32_t fibo_next(Fibonacci_t* handle);

fibonacci.c

#include "fibonacci.h"

struct Fibonacci {
    int32_t  n;
    uint32_t prev;
    uint32_t cur;
};

Fibonacci_t* fibo_new() {
    struct Fibonacci *self = calloc(1, sizeof(struct Fibonacci));
    return (void*)self;
}

uint32_t fibo_next(Fibonacci_t* handle) {
    if (!handle) {
        return 0;
    }

    struct Fibonacci *self = (struct Fibonacci*)handle;
    self->n++;
    switch (self->n) {
        case 1:
            return 0;
        case 2:
            self->cur = 1;
            return 1;
        default:
            ;
            uint32_t next = self->prev + self->cur;
            self->prev = self->cur;
            self->cur  = next;
            return next;
    }
}

動作確認のために、fibonacciモジュールを呼び出すmain.cを実装します。

main.c

#include <stdio.h>
#include <stdlib.h>
#include "fibonacci.h"

int main() {
    Fibonacci_t *p = fibo_new();
    for (int i=0; i<20; i++) {
        printf("%d, ", fibo_next(p));
    }
    free(p);
    return 0;
}

実行してみます。

$ cc fibonacci.c main.c
$ ./a.out
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181

良さそうですね。 ここまでは単純にCの話でした。

2. ccクレートを使ってCのコードをCargoからビルドする

ここからRustのプロジェクトを作って、その中でCをビルドする仕組みを入れていきます。
まずは、Cargoでクレートパッケージを作ります。 パッケージ名は fibonacci-sys とします。

$ cargo new --lib fibonacci-sys

先ほど書いたCのコードを fibonacci-sys/src/c の下に置きます。(Rustのパッケージの中にCのコードを置く場合の置き場所については、特に決まりはないようです。)

$ mkdir fibonacci-sys/src/c
$ cp fibonacci.[ch] fibonacci-sys/src/c

CargoからCのコードをビルドするために、ccクレートをCargo.tomlの build-dependencies に追加します。 build-dependencies 指定を使うことで、ビルド時にのみ必要なクレートを記述することができます。
Specifying Dependencies - The Cargo Book

$ cd fibonacci-sys
$ cargo add cc --build

ここでCargo.tomlは次のようになるはずです。 build-dependencies にccクレートが追加されているのがわかります。

[package]
name = "fibonacci-sys"
version = "0.1.0"
edition = "2018"

[dependencies]

[build-dependencies]
cc = "1.0.35"

build.rsというビルドスクリプトで、ビルド時にCのコードの src/c/fibonacci.c もコンパイルするようにしてみます。
(build.rs については、Build Scripts - The Cargo Book で詳しい説明がされています。)

build.rs

fn main() {
    // Cのコードをコンパイルします。
    //    ccクレートはcargoにコンパイルのリンクオプションを自動的に追加します。
    //    これの意味することは、本来Cのコードをビルドした後にRustのコードにリンクするために必要な
    //    以下の記述をbuild.rsから省略できるということです。
    //     println!("cargo:rustc-link-search=native={}", env::var("OUT_DIR").unwrap());
    //     println!("cargo:rustc-link-lib=fibonacci");
    cc::Build::new()
        .warnings(true)
        .flag("-Wall")
        .flag("-Wextra")
        .file("src/c/fibonacci.c")
        .include("src/c")
        .compile("libfibonacci.a");
}

今のディレクトリ構造は次のようになっています。

$ tree
fibonacci-sys
├── build.rs
├── Cargo.toml
└── src
    ├── c
    │   ├── fibonacci.c
    │   └── fibonacci.h
    └── lib.rs

それではビルドしてみます。

$ cargo build
    Updating crates.io index
   Compiling cc v1.0.35
   Compiling fibonacci-sys v0.1.0 (/home/ryo/code/sandbox/rust/ffi-fibonacci/fibonacci-sys)
    Finished dev [unoptimized + debuginfo] target(s) in 5.17s

ビルドが無事通ったようです。fibonacci.cからライブラリが生成されているはずなので探してみます。

$ find . -name "libfibonacci.a"
./target/debug/build/fibonacci-sys-147411519e93d7f0/out/libfibonacci.a

見つかりました。これでCargoからCのコードがビルドできていることが確認できました。

3. bindgenクレートを使ってRustバインディングを生成する

CargoからCのコードをビルドできることは確認できましたが、RustからCのコードを呼び出すためにはFFIを使ってバインディングを実装する必要があります。
自分で実装する方法もありますが、ここではCのヘッダファイルからRustのバインディングを自動生成してくれるbindgenクレートを使います。

bindgenクレートを Cargo.toml に追加します。

$ cargo add bindgen --build

bindgenクレート追加後の Cargo.toml は次のようになります。

[package]
name = "fibonacci-sys"
version = "0.1.0"
edition = "2018"

[dependencies]

[build-dependencies]
cc = "1.0.35"
bindgen = "0.49.0"

ビルド時にbindgenを実行するように builld.rs を修正します。

use std::env;
use std::path::PathBuf;

fn main() {
    // Cのコードをコンパイルします。
    //    ccクレートはcargoにコンパイルのリンクオプションを自動的に追加します。
    //    これの意味することは、本来Cのコードをビルドした後にRustのコードにリンクするために必要な
    //    以下の記述をbuild.rsから省略できるということです。
    //     println!("cargo:rustc-link-search=native={}", env::var("OUT_DIR").unwrap());
    //     println!("cargo:rustc-link-lib=fibonacci");
    cc::Build::new()
        .warnings(true)
        .flag("-Wall")
        .flag("-Wextra")
        .file("src/c/fibonacci.c")
        .include("src/c")
        .compile("libfibonacci.a");

    // bindgenにfibonacci.hの場所を伝えます。
    let bindings = bindgen::Builder::default()
        .header("src/c/fibonacci.h")
        .generate()
        .expect("Unable to generate bindings!");

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    // bindgenによってbindings.rsという名前でRustのバインディングコードが生成されます。
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}

ビルドしてbindgenが bindings.rs を生成してくれるかを確認してみます。

$ cargo build

$ find . -name "bindings.rs"
./target/debug/build/fibonacci-sys-5c43ba6fb0edf06f/out/bindings.rs

$ cat ./target/debug/build/fibonacci-sys-5c43ba6fb0edf06f/out/bindings.rs
...
pub type Fibonacci_t = *mut ::std::os::raw::c_void;
extern "C" {
    pub fn fibo_new() -> *mut Fibonacci_t;
}
extern "C" {
    pub fn fibo_next(handle: *mut Fibonacci_t) -> u32;
}

うまくbindgenがコードを生成してくれているようです。

4. bindgenで生成されたコードをよりRustらしいコードでラップする

bindgenはCのヘッダからRustのコードを生成してくれる非常に便利なツールです。しかし、bindgenが生成するコードはアンセーフであり、またCライクなコードになっているためRustらしいコードではありません。 そのため、Rustのコードから使いやすくするためにラッパーライブラリを作る必要があります。

この章で目指すディレクトリ構造は次の通りです。

$ tree
.
├── Cargo.toml
├── fibonacci
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
└── fibonacci-sys
    ├── build.rs
    ├── Cargo.lock
    ├── Cargo.toml
    └── src
        ├── c
        │   ├── fibonacci.c
        │   └── fibonacci.h
        └── lib.rs

今までの作業は fibonacci-sys パッケージ下で行ってきました。 この章では、 fibonacci-sys パッケージと同じ階層に fibonacci パッケージを作り、 fibonacci-sys パッケージと fibonacci パッケージを親ディレクトリの Cargo.toml でワークスペースとしてまとめるという方針で進めます。
パッケージの命名ルールについては「補足: bindgenで生成したコードのクレートの分け方と名前付けについて」で説明しています。

fibonacci-sys のbindgenが生成したコードを外部に公開するために、 fibonacci-sys/src/lib.rs を次のように実装します。

#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

fibonacci-sys にtarget/が残るのは気持ちが悪いので、cargo cleanできれいにしておきましょう。

$ # @fibonacci-sys directory
$ cargo clean

1つ上の親ディレクトリに移動して、fibonacci パッケージの生成と Cargo.toml でのワークスペースを定義を行います。
Cargo Workspaces - The Rust Programming Language

ワークスペースを定義することで、 fibonacci-sys パッケージと fibonacci パッケージを同じワークスペースとしてまとめることができます。

$ cd ..
$ cargo new --lib fibonacci
$ cat << eof >> Cargo.toml
[workspace]
members = ["fibonacci-sys", "fibonacci"]
eof

それでは、たった今作ったばかりの fibonacci パッケージにRustのラッパーを実装していきます。
fibonacci パッケージの Cargo.toml に依存パッケージを追加していきます。

$ cd fibonacci
$ cargo add fibonacci-sys --path ../fibonacci-sys
$ cargo add libc

fibonacciパッケージの Cargo.toml は次のようになります。

[package]
name = "fibonacci"
version = "0.1.0"
edition = "2018"

[dependencies]
fibonacci-sys = { path = "../fibonacci-sys" }
libc = "0.2.51"

これが最後の仕上げです。
fibonacci-sys のbindgen生成コードをラップするライブラリを fibonacci/src/lib.rs に実装します。

use fibonacci_sys as fibo;
use libc;

pub struct Fibonacci {
    handle: *mut fibo::Fibonacci_t,
}

impl Fibonacci {
    pub fn new() -> Self {
        Self {
            handle: unsafe { fibo::fibo_new() }
        }
    }

    pub fn next(&mut self) -> u32 {
        unsafe { fibo::fibo_next(self.handle) }
    }
}

impl Drop for Fibonacci {
    fn drop(&mut self) {
        unsafe {
            libc::free(self.handle as *mut core::ffi::c_void);
        }
        println!("free()");
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn fibonacci_test() {
        let expects: [u32; 10] = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34];
        let mut fibo = Fibonacci::new();
        for &e in expects.iter() {
            assert_eq!(fibo.next(), e);
        }
    }
}

最後にテストを実行してみましょう。
このテストが通れば、 ラッパーライブラリの fibonacci クレートから fibonacci-sys クレートのバインディングライブラリを通してCのコードが正常に実行できていることになります。

$ cargo test fibonacci_test
    Finished dev [unoptimized + debuginfo] target(s) in 0.09s
     Running /home/ryo/code/sandbox/rust/ffi-fibonacci/target/debug/deps/fibonacci-53505d427ab47c43

running 1 test
test tests::fibonacci_test ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests fibonacci

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

意図通り動いているようです。

若干の手間はありますが、ccやbindgenといったクレートのおかげで簡単にCのモジュールをRustのプロジェクトの中で使うことができました。

補足: bindgenで生成したコードのクレートの分け方と名前付けについて

この記事では、bindgenで生成したコードを含むクレートパッケージをfibonacci-sys、fibonacci-sysをラップしてRustらしいコードを提供するクレートパッケージをfibonacciとして分離しています。
これは次の記事を参考にしたやり方です。おそらくこれがbindgenを扱う時の流儀なのでしょう。

CircuitJS1をElectronアプリ化する

電子回路のシミュレーションツールを探していたところ、ブラウザ上で動的にシミュレートするサービス CircuitJS1 を見つけました。

Circuit Simulator Applet

ちょうどQucsという他の電子回路シミュレータを触り始めたところだったのですが、Qucsでは回路に変更を加えるたびにシミュレーションコマンドを意図的に実行しなくてはならないため、少し煩わしく感じていました。
以前に、iCircuitという動的に電子回路のシミュレーションができるiOSアプリを使っていたこともあり、似たような操作感のシミュレータは無いかと探していたところ、このCircuitJS1を見つけました。

CircuitJS1の操作感については、リンク先で実際に触ってみてもらうのがわかりやすいです。 CircuitJS1では、回路に追加した抵抗などによる変化を即座にシミュレーションに反映してくれるだけではなく、作成した回路図のURLリンクなども生成できるため、Markdownなどのテキストメモとの相性もいいと思います。

そんな素晴らしいCircuitJS1なのですが、使っているうちにデスクトップアプリとしてオフラインでも使用したい欲求が湧き上がってきました。

そこで今回、CircuitJS1をElectronアプリ化してみましたので、その手順をまとめてみました。

まず、CircuitJS1のGitHubリポジトリはこちらになります。
pfalstad/circuitjs1: Electronic Circuit Simulator in the Browser

READMEを読むと、Electronアプリとしてビルドするための方法が記載してありますので、それに沿って進めていきます。 おおまかには以下の手順になります。

  1. 必要なツールのインストール
  2. EclipseからGWTコンパイル(JavaのコードをJavaScriptに変換)
  3. Electron用のディレクトリ構成を構築
  4. Electronアプリとして起動

それでは進めていきましょう。

必要なツールのインストール

以下のツールをそれぞれインストールします。

手元の環境ではArchLinuxとパッケージマネージャにyayを使っているので、以下のコマンドでインストールしました。

$ yay -S jdk8-openjdk eclipse-java electron

合わせて、CircuitJS1のコードも適当な場所にgit cloneしておきましょう。

$ git clone https://github.com/pfalstad/circuitjs1.git

EclipseからGWTコンパイル(JavaのコードをJavaScriptに変換)

Electronアプリとして動かすためには、Javaで書かれているコードをJavaScriptに変換しなくてはなりません。(GWT/Electronに詳しくないため、間違った理解をしているかもしれません)
そのためにGWTGoogle Web Toolkit )を使って変換を試みます。

GWTプラグインのインストール

  1. インストールしたEclipseを起動して、適当なワークスペースを設定します。
  2. Help > Eclipse Marketplace から"GWT"で検索して、GWT Eclipse Pluginをインストールします。

Javaのバージョン確認

GWTJava 8を使ってコンパイルするのがいいようです。 (僕は最初にJava 11を使ってコンパイルしたのですが、以下のエラーが出てGWTコンパイルができませんでした。)

Compiling module com.lushprojects.circuitjs1.circuitjs1 [ERROR] Hint: Check that your module inherits 'com.google.gwt.core.Core' either directly or indirectly (most often by inheriting module 'com.google.gwt.user.User')

Eclipseが使用するJavaのバージョンは、 Window > Preferences > Java > Installed JREs で確認できます。 ここでJava 8が設定されていないようなら、設定し直す必要があります。

参考までに、手元の環境で設定されている値は以下になります。

JRE home: /usr/lib/jvm/java-8-openjdk
JRE name: java-8-openjdk

CircuitJS1プロジェクトのインポートとGWTでのコンパイル

  1. Welcome画面から、"Import existing projects"をクリックします。
  2. Select root directory > Browse... でさきほどcloneしたcircuitjs1/ディレクトリを指定します。
  3. ツールバーにあるGWTボタンから GWT Compile Project... をクリックし、circuitjs1プロジェクトを選択してコンパイルを実行します。
  4. コンパイルに成功すると、 war/ディレクトリ下にWEB-INF/とcircuitjs1/が生成されます。 
$ cd circuitjs1
$ ls war
circuitjs1/     # <- generated
circuitjs.html
customfunction.html
customlogic.html
iframe.html
shortrelay.php
WEB-INF/        # <- generated

Electron用のディレクトリ構成を構築

Electronアプリとして起動させるために、Electron用のディレクトリ構成を作ります。
Application Distribution | Electron

(ここでは例としてWindows/Linux用のディレクトリ構成にします。macOSではディレクトリ構成が異なってきますのでご注意ください。)

$ cd circuitjs1
$ mkdir -p electron/resources
$ cp -r app electron/resources/
$ cp -r war electron/resources/app/
$ tree -F -L 2 electron/resources/app/
electron/resources/app/
├── index.html
├── main.js
├── package.json
├── preload.js
├── renderer.js
└── war/
    ├── circuitjs1/
    ├── circuitjs.html
    ├── customfunction.html
    ├── customlogic.html
    ├── iframe.html
    ├── shortrelay.php
    └── WEB-INF/

Electronアプリとして起動

全ての準備が整いました。さっそくCircuitJS1をElectronアプリとして起動してみます。

$ cd circuitjs1
$ electron electron/resources/app/

f:id:ryochack:20190104012015p:plain

これでCircuitJS1がElectronアプリとして起動するようになり、オフラインでも気軽に電子回路シミュレータが使えるようになりました。

Rustでプラットフォーム依存の処理を書く

#[cfg(...)] Attribute

Rustでは #[cfg(...)] アトリビュートを使うことによって、OSやCPUに応じた条件コンパイルを行うことができる。

例えば、次のように書くことでコンパイルターゲットのOSに応じたhello()をビルドし、呼び出すことができる。

#[cfg(target_os = "windows")]
fn hello() { println!("Hello, I'm Windows!"); }

#[cfg(target_os = "linux")]
fn hello() { println!("Hello, I'm Linux!"); }

#[cfg(target_os = "macos")]
fn hello() { println!("hello, I'm MacOS."); }

fn main() {
    hello();
}

モジュールを分ける

各複数プラットフォーム向けの処理が小さい時には、前述のように1つのファイルの中にベタ書きでも問題ないが、コードが膨らんできた場合は各プラットフォーム毎にファイルを分けた方がいいだろう。

そこで、次のようなモジュール構成を考てみる。 nameモジュールを用意し、OS依存部分はnameモジュールの中に隠蔽することを目的とする。

Cargo.toml
src/
├── lib.rs
└── name
    ├── linux.rs
    ├── macos.rs
    ├── mod.rs
    └── windows.rs

name/${os_dependent}.rs

OS依存の処理を name/以下の linux.rs, macos.rs, windows.rsにそれぞれ書いていく。 各モジュールはOSの名前をStringで返すだけのname()関数を実装する。

name/linux.rs

pub fn name() -> String {
    "Linux".to_owned()
}

name/macos.rs

pub fn name() -> String {
    "MacOS".to_owned()
}

name/windows.rs

pub fn name() -> String {
    "Windows".to_owned()
}

name/mod.rs

各OS依存の処理を他のモジュールから扱えるようにするために name/mod.rs を記述する。

name/mod.rs

#[cfg(target_os = "linux")]
pub mod linux;
#[cfg(target_os = "linux")]
pub use self::linux::*;

#[cfg(target_os = "macos")]
pub mod macos;
#[cfg(target_os = "macos")]
pub use self::macos::*;

#[cfg(target_os = "windows")]
pub mod windows;
#[cfg(target_os = "windows")]
pub use self::windows::*;

ここでのmod.rsの役割は、コンパイル対象とするモジュールの切り替えと、他のモジュールへの再エキスポートだ。

コンパイル対象とするモジュールの切り替え

これは前述のように #[cfg(target_os = "linux")] を使ってコンパイル対象のモジュールを指定する。

#[cfg(target_os = "linux")]
pub mod linux;

他のモジュールへの再エクスポート

pub useによって再エクスポートすることによって、他のモジュールからnameのOS依存モジュールを意識せずに使えるようにしている。

#[cfg(target_os = "linux")]
pub use self::linux::*;

この記述がないと、nameモジュールのOS依存処理を使う時は常にモジュール名を明確に指定しなくてはならない。 これでは以下のように他のモジュールが常にOS依存を気にしたコードを書かなくてはならず、非常によろしくない。

mod name;
let os_name = name::linux::name();

pub useすることによって、OS依存モジュール名を省略して使用することができるようになり、他のモジュールからOS依存を気にせずに使用できるようになる。

// mod.rsでpub use self::linux::*;
mod name;
let os_name = name::name();

lib.rs

最後に、今まで定義したnameモジュールを使ったlib.rsを次に記述する。

lib.rs

mod name;
pub fn greeting() {
    println!("Hello, I'm {}.", name::name());
}
// -> Hello, I'm ${YOUR_OS}.

#[cfg(test)]
mod tests {
    use super::*;

    #[cfg(target_os = "linux")]
    #[test]
    fn test_linux() {
        assert_eq!("Linux", name::name());
    }

    #[cfg(target_os = "macos")]
    #[test]
    fn test_linux() {
        assert_eq!("MacOS", name::name());
    }

    #[cfg(target_os = "windows")]
    #[test]
    fn test_linux() {
        assert_eq!("Windows", name::name());
    }
}

Linux上でテストを実行すると、name()は"Linux"の文字列を返してくれているのが確認できる。

$ cargo test
   Compiling hello v0.1.0 (/home/ryo/code/sandbox/rust/scraps/target_os)
    Finished dev [unoptimized + debuginfo] target(s) in 0.75s
     Running target/debug/deps/hello-69d1a57a65d70c5b

running 1 test
test tests::test_linux ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests hello

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

参考

RustのRange指定

RustのRange .. は[start..end)の半開区間だが、 ..= を使うことで、[start..=end]の閉区間にすることができる。

for i in 0..5 {
    print!("{},", i);
}
// => 0,1,2,3,4
for i in 0..=5 {
    print!("{},", i);
}
// => 0,1,2,3,4,5

また、matchに使われる ... は閉区間

Linux kernel moduleビルドのメモ

ArchLinux(4.17.10)上でのkernel moduleビルドを試したメモ。

Linux kernelのソースコードを入手する

prepare_kernel_source.sh

#!/usr/bin/env sh
set -eu
KERNEL_VERSION=`uname -r | sed -r 's/([0-9]+\.[0-9]+\.[0-9]+).*$/\1/'`
LINUX_VERSION=linux-${KERNEL_VERSION}

if [ -e $LINUX_VERSION ]; then
    echo "$LINUX_VERSION is already exist."
    exit 1
fi

wget https://www.kernel.org/pub/linux/kernel/v4.x/${LINUX_VERSION}.tar.xz
tar -xvJf ${LINUX_VERSION}.tar.xz
ln -s ${LINUX_VERSION} kernel-source
cd ${LINUX_VERSION}
make clean && make mrproper

# prepare kernel
zcat /proc/config.gz > .config
make oldconfig
make prepare && make scripts
$ cd $HOME
$ sh prepare_kernel_source.sh

kernel moduleのソースコードMakefileを書く

hello.c

#include <linux/module.h>
#include <linux/init.h>

MODULE_LICENSE("MIT");

static int hello_init(void)
{
    printk(KERN_INFO "Hello, World!\n");
    return 0;
}

static void hello_exit(void)
{
    printk(KERN_INFO "Goodbye...\n");
}

module_init(hello_init);
module_exit(hello_exit);

Makefile

obj-m := hello.o

KERNEL_SRC_DIR=${HOME}/kernel-source
PWD:=$(shell pwd)
BUILD_DIR=$(PWD)
VERBOSE=0

all:
    make -C ${KERNEL_SRC_DIR} M=${BUILD_DIR} KBUILD_VERBOSE=${VERBOSE} modules

clean:
    make -C ${KERNEL_SRC_DIR} M=${BUILD_DIR} clean
$ make
-> hello.ko

libelf-dev でのコンパイルエラーが起こったら

コンパイルエラー

$ make
make -C /root/kernel-source M=/root/workspace/hello KBUILD_VERBOSE=0 modules
make[1]: Entering directory '/root/kernel-source'
Makefile:970: *** "Cannot generate ORC metadata for CONFIG_UNWINDER_ORC=y, please install libelf-dev, libelf-devel or elfutils-libelf-devel".  Stop.
make[1]: Leaving directory '/root/kernel-source'
Makefile:8: recipe for target 'all' failed
make: *** [all] Error 2

解決方法

$ apt-get install libelf-dev

Install/Remove kernel module

$ sudo insmod hello.ko
$ lsmod
-> Module                  Size  Used by
   hello                  16384  0
$ dmesg
-> [11869.696026] Hello, World!
$ sudo rmmod hello
$ lsmod
$ dmesg
-> [11900.796428] Goodbye...

参考