Rust-DPDK
Internet Week 2017 Software Router/Switch BoF
Nov 29, 2017
Masaru OKI
DPDK
 Data Plane Development Kit
 http://dpdk.org/
 ユーザ空間で高速パケット処理を実現するライブラリ。BSD License.
 C言語のAPIとして提供される。
 基本的にLinux向け。(一部機能が未提供だが)FreeBSDでも動作する。
 バージョン番号は年.月。(1.8.0や2.2.0は旧命名規則。かなり古い)
 16.11.3がLTS(Long Term Support)
 17.05 → 17.08 → 17.11 → と続いていく。
2
Kernel moduleはGPL
DPDKが使われているところ
 高速なパケット転送を必要としている部分に採用されている。
 基本的に、どれもC言語で実装されている。
 Pktgen-dpdk
パケットジェネレータ。
 Soft patch panel (spp)
ソフトウェアパッチパネル。
 ovs-dpdk
Open vSwitch。DPDK対応版としてビルドすると使える。
 Lagopus vswitch
OpenFlow 1.3対応ソフトウェアスイッチ。
 FD.io VPP
Vector Packet Processing platform. L2/L3処理エンジン。
3
モチベーション
 DPDKアプリケーションの大半はC言語で書かれている。
 C言語はシンプルだが困った点も多い。
 現代のモダンな言語と比較すると素朴すぎ。(e.g.メモリ管理は自己責任)
 ライブラリが管理されていない、更新など全部手でやる。
 C++で書くことも可能だが、C++は複雑すぎる。
 他のモダンな言語で書けばメンテが楽になりそう。
 Lagopus routerではGo言語が使われている。
 他のプログラミング言語でDPDKを扱えないか?
4
検討した言語(いわゆるbetter C)
 C# (ゲームエンジンのUnityで使われている)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/
 Go (GoBGPだとかZebra2.0やLagopus routerがGo言語で書かれている)
 https://golang.org/
 Swift (Objective-Cの代替、iOSアプリが書ける)
 https://swift.org/
 Kotlin (Javaの代替、Androidアプリが書ける)
 https://kotlinlang.org/
 Rust (初期はMozillaによって開発された)
 https://www.rust-lang.org/
5
Rust?
 https://www.rust-lang.org/
 Rustは速度、安全性、並行性の3つのゴールにフォーカスしたシステム
プログラミング言語。
 スクリプト言語ではなく、ネイティブコードにコンパイルする。
 コンパイラのバックエンドはLLVM。
 いろいろなOS(Linux, *BSD, Windows, macOS, …)に対応。
 多様なCPU(x86_64, i386, ARM, ARM64, MIPS, MIPS64, PPC, PPC64, …)
に対応。
 Rustで書かれたOSが存在する: RedoxOS, intermezzOS, Tock
6
Rustのhello, world
 拡張子は .rs
 % cat hello.rs
fn main() {
println!(”hello, world.”);
}
% rustc hello.rs
% ./hello
hello, world.
%
rustc –emit asm hello.rs
とすればasm出力が得られる。
7
既存のDPDKのRustバインディング
 すでにいくつか実装があった
 https://github.com/libpnet/rust-dpdk
 https://github.com/flier/rust-dpdk
 https://github.com/ANLAB-KAIST/rust-dpdk
対応状況 libpnet/rust-dpdk flier/rust-dpdk ANLAB-KAIST/rust-dpdk
対応DPDK ver. 1.8.0 不明 不明
最終更新 2015年1月 2016年6月 2017年4月
ビルド方法 make cargo build cargo build
サポート状況 bindgenでの変換のみ、
inline未対応、サンプルプロ
グラムなし(実質動かない)
サンプルプログラム多数。
l2fwdの各coreで動かす関数
がC言語
サンプルプログラムなし
8
既存の実装をあてにできず、自作する
 ベースはlibpnet/rust-dpdk
 Rustの安定版でまだ提供されていない機能を使う。
 C言語のThread Local Storageへのアクセス: nightly buildなら対応。
 Bindingは、bindgenというツールで自動生成。
 ツールを別途インストールする必要はなし。
 Rust標準のcargoコマンドでビルドできる。
 RustらしくするAPIについては手で追加コードを用意。
 https://github.com/iMasaruOki/rust-dpdk
9
DPDKを使って各coreでhello(C言語)
 int
hello(void *arg) {
printf(“hello lcore %d¥n”, rte_lcore_id());
return 0;
}
int
main(int argc, char *argv[]) {
rte_eal_init(argc, argv);
rte_eal_mp_remote_launch(hello, NULL, CALL_MASTER);
rte_eal_mp_wait_lcore();
return 0;
}
10
各コアで動くスレッド
DPDKの初期化
各コアでhello()を実行
RustでDPDK hello (iMasaruOki版)
 extern crate dpdk;
use std::os::raw::c_void;
unsafe extern "C" fn hello_thread(_arg: *mut c_void)
-> i32 {
println!("Hello! lcore {}", dpdk::lcore::id());
0
}
fn main() {
unsafe {
let _ = dpdk::eal::init(std::env::args());
let callback_arg: *mut c_void = std::mem::zeroed();
dpdk::eal::mp_remote_launch(hello_thread,
callback_arg, true);
dpdk::eal::mp_wait_lcore();
}
}
11
各コアで動くスレッド。
DPDKから呼ばれるのでextern ”C”が必要。
DPDKの初期化
各コアでhello_thread()を実行
外部のコードを取り込む
RustでDPDK hello ビルドと実行
 $ pwd
/home/masaru/src/rust-dpdk/examples/dpdk-hello
$ cargo +nightly build
(ビルドログは省略)
$ sudo ./target/debug/dpdk-hello -cf -n2
EAL: Detected 8 lcore(s)
EAL: No free hugepages reported in hugepages-1048576kB
EAL: Probing VFIO support...
Hello! lcore 1
Hello! lcore 0
Hello! lcore 2
Hello! lcore 3
$
12
DPDKアプリはsudo必須
Rust-DPDK iMasaruOki版のステータス
 DPDK 17.11対応
 wrapperを作成したAPIはまだごく一部
 マルチコア動作を確認
 l2fwd相当のサンプルを作成、パケット転送できることを確認
netns: ns1
114.0.0.1/24
netns: ns2
114.0.0.2/24
netns: ns3
114.0.0.3/24
dpdk-l2fwd
tap i/f
13
dpdk-l2fwd デモ
14
今後に向けて
 DPDKのrte_flowやCrypto APIなど対応させていきたい
 Lagopus vSwitchのサブセットが作れないか検討中
 使えそうなcrate(Pythonのpipのように外部ライブラリを管理できる)
 rust_ofp: OpenFlow 1.0 プロトコルを提供
 pnet: Rust向けパケット処理フレームワーク
 grpc: gRPCサポート
 toml: TOMLサポート
 Tom's Obvious, Minimal Language
 いわゆるINIファイルのような記述で設定を表現する
15
おまけ: 他言語でのDPDK対応
 https://github.com/libmoon/libmoon
LuaJIT
 https://github.com/feiskyer/dpdk-go
Go言語(forkもありました)
 https://github.com/intel-go/yanff
Go言語で抽象化APIを提供
 https://github.com/FlowForwarding/edpdk
erlangでDPDK PMDを利用
 https://software.intel.com/en-us/articles/minimize-nodejs-io-
bottlenecks
LKLを使ってJavaScriptからDPDKを呼ぶという記事
 https://github.com/P4ELTE/t4p4s
bindingではないが、 DPDKを使うバイナリを出力するP4言語コンパイラ
16
予備
17
おまけ2: Rustの解説
 詳細は公式ドキュメントを参照。
 https://www.rust-lang.org/ja-JP/documentation.html
 書き方の例を見てまねるにはRust by Exampleが有用。
 https://rustbyexample.com/
 記法のおおまかな紹介
 コメントは // … か /* … */
 行志向ではなく、Cと同じくセミコロンで区切る。
 スレッドサポートはライブラリで。スレッド間IPCも、同期処理系もライブラリ。
 マクロはあるけどCプリプロセッサではない。
 extern ”C” 宣言すればC言語のライブラリを呼び出すことができる。
18
Rustのインストールと更新
 curl https://sh.rustup.rs –sSf | sh
 $HOME/.cargo/binにインストールされる。
 rustc: コンパイラ
 cargo: パッケージマネージャ
 rustup: ツールチェインのインストーラ/アップデータ
 必要な環境変数は$HOME/.cargo/envの実行で設定される。
 sudoしない。ユーザーごと。
 Windowsの場合はインストーラを実行。
 コンパイラを最新版にするときはrustup update
19
ビルドには(makeではなく)cargoを使う
 % pwd
/tmp/hello
% mkdir src
% mv hello.rs src/main.rs
% cargo init
Created library project
% cargo run
Compiling hello v0.1.0 (file:///tmp/hello)
Finished dev [unoptimized + debuginfo] target(s) in 0.xx secs
Running `target/debug/hello`
hello, world.
%
シンプルなCargo.tomlを生成。
必要に応じて手直しする。
コマンド 意味
cargo run コンパイルして即実行
cargo build ビルドのみ
cargo test テストコードを実行
cargo doc ドキュメントを生成
cargo clean target/を削除
20
簡単な紹介
 ファンクションはfnで定義する。(hello.rsでみたとおり)
 別のソースから参照されるものはpub fnと書く。
 変数や値の型はu8, u16, u32, i8, i16, i32など。後ろに書く。
 fn testfunc(arg: u32) -> u32 {
let a: u32 = 1;
let b = a + 1;
let a = b + arg;
let mut c = a + b;
c = c * 2;
a + b + c
}
Cでいうところの
uint32_t testfunc(uint32_t arg)
型宣言して初期値を代入、変更不可能
右辺の型がわかるので省略(型推論)
新しいaを宣言(以前のaは参照できない)
cはmutable(変更できる)
最後に式を書くとリターン値になる
Cのようにreturn a + b + c;
と書いてもよい
21
型いろいろ
 u8, u16, u32, u64: 符号なし整数
 i8, i16, i32, i64: 符号あり整数
 bool: 真偽値 (true or false)
 usize: 符号なしでサイズをあらわす(配列の添え字はこれ)
 isize: 符号ありサイズ(ポインタのオフセットはこれ)
 f32, f64: 浮動小数
 char: Unicode文字
 String, Vector, HashMapが標準ライブラリとして用意される
 これらは特別な宣言なしにビルトイン型と同じように使える
 type your_type = u64; などと書けばC言語でいうtypedefが可能
22
型キャスト
 式 as 型 でキャストできる。
 符号なし整数でもサイズが違うと別の型。
 違う型の値どうしの演算をする場合キャストが必要。
 let shortval: u16 = 3;
let longval: u32 = 5;
let ans = shortval + longval;
shortval as u32 + longval 型が違うのでこの記述は
エラーになりビルドできない
キャストして
型を合わせて演算する
23
条件分岐
 if a == b {
println!(”equal”);
} else {
println!(”a == {} but b == {}”, a, b);
}
match a {
1 => println!(”one”),
2 => println!(”two”),
_ => println!(”other”),
}
Cのswitch caseでの
defaultに相当
24
ループ
 forはあるが、Cとだいぶ異なる記法。 (Python等に似ている)
let values: [u32; 5] = [ 3, 5, 7, 9, 11];
for value in values.iter() {
…
}
 for i in 0..10 { … } // 0~9 (10ではない)
 whileはCなどと同様。
while !done { // done != trueと同じ
…
}
 loop { … } は while true { … } と同じ。永久ループ。
 break; や continue; はCと同様。ラベルを付けてそこに飛ぶことも可能。
 ’label1: while !done {
loop {
if a == b { continue ’label1; }
}
}
25
配列、ポインタ、参照
 ポインタは通常使わ(れ)ない。
 Cでいう構造体ポインタからのメンバ参照foo->barの->は存在しない。
 (*foo).barと書く。
 C言語ライブラリとつなぐ際はポインタを多用する(せざるを得ない)。
 配列
let array: [u32; 3] = [ 1, 2, 3];
 配列先頭のポインタ
let ptr = array.as_ptr(); // Cではuint32_t *ptr = array;
let mut_ptr: *mut u32; // 型推論しない場合は例えばこう
 ポインタから配列の中身を参照
let val = *ptr.offset(1); // Cでは*(ptr + 1)で、値は2
 Rust言語では通常変数のアクセスを追跡して安全を保障する。
 ポインタの参照は安全ではないためunsafeという指定が必要。
 unsafe fn foo () { … } など関数単位の指定が多いようだ。
26
structとimpl (trait)
 structを定義し、それに対してfnを定義することができる。
 traitと呼ばれる。C++のclassに対するメンバ関数のようなもの。
 struct Foo {
v: u32,
}
impl Foo {
fn add(&mut self, val: u32) {
self.v = self.v + val;
}
}
fn main() {
let mut testfoo = Foo { v: 10 };
testfoo.add(2);
println!(”{}”, testfoo.v); // 12
}
27
Generic functions
 C++のテンプレート相当。
 テンプレートのように foo<T>などと定義し、foo<u32>など指定して使う。
 定義(traitを使う)
impl <T> Vec<T> {
pub fn new() -> Vec<T> {
…
}
…
}
 使用例
let mut vec: Vec<i32> = Vec::new();
28
Rustのライブラリ、crate
 crateとは「木箱」という意味。
 言語上はextern crate 名前; で引き込む。
 モジュール名::サブモジュール名::型などと書いて参照
 use mod::submod::type; などとすれば単にtypeで参照できる
 たとえばHTTP実装 Hyper https://hyper.rs/
 stdから始まるものは標準で引き込まれるのでextern crate不要。
 std::thread, std::option, std::syncなど
29
crateの引き込み方
 パッケージシステム上はCargo.tomlに記述を加える。
 例
[dependencies]
libc = ”0.2”
my_crate = { git = ”https://github.com/...”, rev = ”8ef654d…” }
 https://crates.io/ から取得する。
 有用な定番ものから開発途上のものまで1万以上が登録されている。
 git = https://github.com/... と書けばgithubから取得できる。
 rev = ”1075f4e…”など書けばcommit指定ができる。rev指定がない場合はmasterを取得する。
 cargo buildあるいはcargo run実行でソースを取得しビルドする。
 一度取得したら以後はローカルのものを使用。
 通常は静的リンクになるので出来上がるバイナリは大きくなる。
 取得元の更新内容と同期(再取得)するにはcargo updateを実行する。
30
C言語ライブラリの呼び出し方
 extern ”C”をつけてRust風にfn宣言すれば呼び出せる。
 structもRust風に定義する。
 例
extern ”C” pub fn your_c_func(arg: u32) -> u32;
 ポインタにアクセスするものはだいたいunsafe
use ::std::os::raw::c_void;
unsafe extern “C” pub fn unsafe_func(arg: c_void);
 Foeign Function Interface (FFI)と呼んでいる。
 リンクするには、 Cargo.tomlと同じ場所にbuild.rsを用意する。
 build.rsの例
fn main() {
// libfooをリンク
println!(”cargo:rustc-link-lib=foo”);
}
31
Cライブラリのbindingを自動生成
 bindgenというツールで自動生成できる。
 https://rust-lang-nursery.github.io/rust-bindgen/
 コマンドラインツールとして使う場合は、cargo install bindgenでインストール。
 C言語のヘッダを読ませ、Rustソース(extern ”C”の塊)を出力する。
 シェル等からbindgenコマンドを呼び出すのでもいいが、
crateになってるのでbuild.rsに書くのがおすすめ。(Cargo.tomlへの追加も忘れずに)
 例
extern crate bindgen;
use std::path::PathBuf;
fn main() {
let bindings = bindgen::Builder::default()
.header(”foo.h”)
.generate();
bindings.write_to_file(PathBuf::from(”src/foo.rs”));
}
32
bindgenでの生成例
C言語
 ulimit.h (一部)
 enum
{
UL_GETFSIZE = 1,
UL_SETFSIZE,
__UL_GETMAXBRK,
__UL_GETOPENMAX
};
extern long int ulimit (int __cmd, …);
Rust (bindgenで生成)
 /* automatically generated by rust-bindgen */
pub const UL_GETFSIZE: _bindgen_ty_1 = _bindgen_ty_1::UL_GETFSIZE;
pub const UL_SETFSIZE: _bindgen_ty_1 = _bindgen_ty_1::UL_SETFSIZE;
pub const __UL_GETMAXBRK: _bindgen_ty_1 = _bindgen_ty_1::__UL_GETMAXBRK;
pub const __UL_GETOPENMAX: _bindgen_ty_1 = _bindgen_ty_1::__UL_GETOPENMAX;
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum _bindgen_ty_1 {
UL_GETFSIZE = 1,
UL_SETFSIZE = 2,
__UL_GETMAXBRK = 3,
__UL_GETOPENMAX = 4,
}
extern "C" {
pub fn ulimit(__cmd: ::std::os::raw::c_int, ...)
-> ::std::os::raw::c_long;
}
33
Rustは使いやすいか?
 注: 私見です
 C++と違い、明示的に書いたコードだけが動くので追いかけやすい
 Goと違いスレッドの扱いやスレッド間通信が言語に隠蔽されない
 Goと違いgitのcommitの指定が標準で可能
 cgoみたいなコメントにゴリゴリ書く汚いスタイルではなくextern ”C”
 型チェックが強力で、雑に書けるCより面倒だが安心感がある
 エラー発生時にもしかして?と表示してくれるのは初心者には助かる
 GCはない
 言語とライブラリの境界がはっきりしている
 かなりC言語に近い感覚で書けるので、Cがわかるならとっつきやすい
 シンプルさに重きを置いたbetter C
34
おまけ3: DPDK APIをRustから呼ぶ方法
 bindgenを使えば楽勝?
→実はbindgenで対応できないものがある
 ヘッダファイルで定義されるinline functionに未対応
 extern ”C”を吐き出すだけで実体を定義しないので、現状しかたない
 回避方法
 -fno-inlineでライブラリを再コンパイル(実際には意味がない)
 自前でRustで書き直す
 inline functionを呼び出すwrapperをC言語で書き、同時にリンクする
35

Rust-DPDK

  • 1.
    Rust-DPDK Internet Week 2017Software Router/Switch BoF Nov 29, 2017 Masaru OKI
  • 2.
    DPDK  Data PlaneDevelopment Kit  http://dpdk.org/  ユーザ空間で高速パケット処理を実現するライブラリ。BSD License.  C言語のAPIとして提供される。  基本的にLinux向け。(一部機能が未提供だが)FreeBSDでも動作する。  バージョン番号は年.月。(1.8.0や2.2.0は旧命名規則。かなり古い)  16.11.3がLTS(Long Term Support)  17.05 → 17.08 → 17.11 → と続いていく。 2 Kernel moduleはGPL
  • 3.
    DPDKが使われているところ  高速なパケット転送を必要としている部分に採用されている。  基本的に、どれもC言語で実装されている。 Pktgen-dpdk パケットジェネレータ。  Soft patch panel (spp) ソフトウェアパッチパネル。  ovs-dpdk Open vSwitch。DPDK対応版としてビルドすると使える。  Lagopus vswitch OpenFlow 1.3対応ソフトウェアスイッチ。  FD.io VPP Vector Packet Processing platform. L2/L3処理エンジン。 3
  • 4.
    モチベーション  DPDKアプリケーションの大半はC言語で書かれている。  C言語はシンプルだが困った点も多い。 現代のモダンな言語と比較すると素朴すぎ。(e.g.メモリ管理は自己責任)  ライブラリが管理されていない、更新など全部手でやる。  C++で書くことも可能だが、C++は複雑すぎる。  他のモダンな言語で書けばメンテが楽になりそう。  Lagopus routerではGo言語が使われている。  他のプログラミング言語でDPDKを扱えないか? 4
  • 5.
    検討した言語(いわゆるbetter C)  C#(ゲームエンジンのUnityで使われている)  https://docs.microsoft.com/ja-jp/dotnet/csharp/  Go (GoBGPだとかZebra2.0やLagopus routerがGo言語で書かれている)  https://golang.org/  Swift (Objective-Cの代替、iOSアプリが書ける)  https://swift.org/  Kotlin (Javaの代替、Androidアプリが書ける)  https://kotlinlang.org/  Rust (初期はMozillaによって開発された)  https://www.rust-lang.org/ 5
  • 6.
    Rust?  https://www.rust-lang.org/  Rustは速度、安全性、並行性の3つのゴールにフォーカスしたシステム プログラミング言語。 スクリプト言語ではなく、ネイティブコードにコンパイルする。  コンパイラのバックエンドはLLVM。  いろいろなOS(Linux, *BSD, Windows, macOS, …)に対応。  多様なCPU(x86_64, i386, ARM, ARM64, MIPS, MIPS64, PPC, PPC64, …) に対応。  Rustで書かれたOSが存在する: RedoxOS, intermezzOS, Tock 6
  • 7.
    Rustのhello, world  拡張子は.rs  % cat hello.rs fn main() { println!(”hello, world.”); } % rustc hello.rs % ./hello hello, world. % rustc –emit asm hello.rs とすればasm出力が得られる。 7
  • 8.
    既存のDPDKのRustバインディング  すでにいくつか実装があった  https://github.com/libpnet/rust-dpdk https://github.com/flier/rust-dpdk  https://github.com/ANLAB-KAIST/rust-dpdk 対応状況 libpnet/rust-dpdk flier/rust-dpdk ANLAB-KAIST/rust-dpdk 対応DPDK ver. 1.8.0 不明 不明 最終更新 2015年1月 2016年6月 2017年4月 ビルド方法 make cargo build cargo build サポート状況 bindgenでの変換のみ、 inline未対応、サンプルプロ グラムなし(実質動かない) サンプルプログラム多数。 l2fwdの各coreで動かす関数 がC言語 サンプルプログラムなし 8
  • 9.
    既存の実装をあてにできず、自作する  ベースはlibpnet/rust-dpdk  Rustの安定版でまだ提供されていない機能を使う。 C言語のThread Local Storageへのアクセス: nightly buildなら対応。  Bindingは、bindgenというツールで自動生成。  ツールを別途インストールする必要はなし。  Rust標準のcargoコマンドでビルドできる。  RustらしくするAPIについては手で追加コードを用意。  https://github.com/iMasaruOki/rust-dpdk 9
  • 10.
    DPDKを使って各coreでhello(C言語)  int hello(void *arg){ printf(“hello lcore %d¥n”, rte_lcore_id()); return 0; } int main(int argc, char *argv[]) { rte_eal_init(argc, argv); rte_eal_mp_remote_launch(hello, NULL, CALL_MASTER); rte_eal_mp_wait_lcore(); return 0; } 10 各コアで動くスレッド DPDKの初期化 各コアでhello()を実行
  • 11.
    RustでDPDK hello (iMasaruOki版) extern crate dpdk; use std::os::raw::c_void; unsafe extern "C" fn hello_thread(_arg: *mut c_void) -> i32 { println!("Hello! lcore {}", dpdk::lcore::id()); 0 } fn main() { unsafe { let _ = dpdk::eal::init(std::env::args()); let callback_arg: *mut c_void = std::mem::zeroed(); dpdk::eal::mp_remote_launch(hello_thread, callback_arg, true); dpdk::eal::mp_wait_lcore(); } } 11 各コアで動くスレッド。 DPDKから呼ばれるのでextern ”C”が必要。 DPDKの初期化 各コアでhello_thread()を実行 外部のコードを取り込む
  • 12.
    RustでDPDK hello ビルドと実行 $ pwd /home/masaru/src/rust-dpdk/examples/dpdk-hello $ cargo +nightly build (ビルドログは省略) $ sudo ./target/debug/dpdk-hello -cf -n2 EAL: Detected 8 lcore(s) EAL: No free hugepages reported in hugepages-1048576kB EAL: Probing VFIO support... Hello! lcore 1 Hello! lcore 0 Hello! lcore 2 Hello! lcore 3 $ 12 DPDKアプリはsudo必須
  • 13.
    Rust-DPDK iMasaruOki版のステータス  DPDK17.11対応  wrapperを作成したAPIはまだごく一部  マルチコア動作を確認  l2fwd相当のサンプルを作成、パケット転送できることを確認 netns: ns1 114.0.0.1/24 netns: ns2 114.0.0.2/24 netns: ns3 114.0.0.3/24 dpdk-l2fwd tap i/f 13
  • 14.
  • 15.
    今後に向けて  DPDKのrte_flowやCrypto APIなど対応させていきたい Lagopus vSwitchのサブセットが作れないか検討中  使えそうなcrate(Pythonのpipのように外部ライブラリを管理できる)  rust_ofp: OpenFlow 1.0 プロトコルを提供  pnet: Rust向けパケット処理フレームワーク  grpc: gRPCサポート  toml: TOMLサポート  Tom's Obvious, Minimal Language  いわゆるINIファイルのような記述で設定を表現する 15
  • 16.
    おまけ: 他言語でのDPDK対応  https://github.com/libmoon/libmoon LuaJIT https://github.com/feiskyer/dpdk-go Go言語(forkもありました)  https://github.com/intel-go/yanff Go言語で抽象化APIを提供  https://github.com/FlowForwarding/edpdk erlangでDPDK PMDを利用  https://software.intel.com/en-us/articles/minimize-nodejs-io- bottlenecks LKLを使ってJavaScriptからDPDKを呼ぶという記事  https://github.com/P4ELTE/t4p4s bindingではないが、 DPDKを使うバイナリを出力するP4言語コンパイラ 16
  • 17.
  • 18.
    おまけ2: Rustの解説  詳細は公式ドキュメントを参照。 https://www.rust-lang.org/ja-JP/documentation.html  書き方の例を見てまねるにはRust by Exampleが有用。  https://rustbyexample.com/  記法のおおまかな紹介  コメントは // … か /* … */  行志向ではなく、Cと同じくセミコロンで区切る。  スレッドサポートはライブラリで。スレッド間IPCも、同期処理系もライブラリ。  マクロはあるけどCプリプロセッサではない。  extern ”C” 宣言すればC言語のライブラリを呼び出すことができる。 18
  • 19.
    Rustのインストールと更新  curl https://sh.rustup.rs–sSf | sh  $HOME/.cargo/binにインストールされる。  rustc: コンパイラ  cargo: パッケージマネージャ  rustup: ツールチェインのインストーラ/アップデータ  必要な環境変数は$HOME/.cargo/envの実行で設定される。  sudoしない。ユーザーごと。  Windowsの場合はインストーラを実行。  コンパイラを最新版にするときはrustup update 19
  • 20.
    ビルドには(makeではなく)cargoを使う  % pwd /tmp/hello %mkdir src % mv hello.rs src/main.rs % cargo init Created library project % cargo run Compiling hello v0.1.0 (file:///tmp/hello) Finished dev [unoptimized + debuginfo] target(s) in 0.xx secs Running `target/debug/hello` hello, world. % シンプルなCargo.tomlを生成。 必要に応じて手直しする。 コマンド 意味 cargo run コンパイルして即実行 cargo build ビルドのみ cargo test テストコードを実行 cargo doc ドキュメントを生成 cargo clean target/を削除 20
  • 21.
    簡単な紹介  ファンクションはfnで定義する。(hello.rsでみたとおり)  別のソースから参照されるものはpubfnと書く。  変数や値の型はu8, u16, u32, i8, i16, i32など。後ろに書く。  fn testfunc(arg: u32) -> u32 { let a: u32 = 1; let b = a + 1; let a = b + arg; let mut c = a + b; c = c * 2; a + b + c } Cでいうところの uint32_t testfunc(uint32_t arg) 型宣言して初期値を代入、変更不可能 右辺の型がわかるので省略(型推論) 新しいaを宣言(以前のaは参照できない) cはmutable(変更できる) 最後に式を書くとリターン値になる Cのようにreturn a + b + c; と書いてもよい 21
  • 22.
    型いろいろ  u8, u16,u32, u64: 符号なし整数  i8, i16, i32, i64: 符号あり整数  bool: 真偽値 (true or false)  usize: 符号なしでサイズをあらわす(配列の添え字はこれ)  isize: 符号ありサイズ(ポインタのオフセットはこれ)  f32, f64: 浮動小数  char: Unicode文字  String, Vector, HashMapが標準ライブラリとして用意される  これらは特別な宣言なしにビルトイン型と同じように使える  type your_type = u64; などと書けばC言語でいうtypedefが可能 22
  • 23.
    型キャスト  式 as型 でキャストできる。  符号なし整数でもサイズが違うと別の型。  違う型の値どうしの演算をする場合キャストが必要。  let shortval: u16 = 3; let longval: u32 = 5; let ans = shortval + longval; shortval as u32 + longval 型が違うのでこの記述は エラーになりビルドできない キャストして 型を合わせて演算する 23
  • 24.
    条件分岐  if a== b { println!(”equal”); } else { println!(”a == {} but b == {}”, a, b); } match a { 1 => println!(”one”), 2 => println!(”two”), _ => println!(”other”), } Cのswitch caseでの defaultに相当 24
  • 25.
    ループ  forはあるが、Cとだいぶ異なる記法。 (Python等に似ている) letvalues: [u32; 5] = [ 3, 5, 7, 9, 11]; for value in values.iter() { … }  for i in 0..10 { … } // 0~9 (10ではない)  whileはCなどと同様。 while !done { // done != trueと同じ … }  loop { … } は while true { … } と同じ。永久ループ。  break; や continue; はCと同様。ラベルを付けてそこに飛ぶことも可能。  ’label1: while !done { loop { if a == b { continue ’label1; } } } 25
  • 26.
    配列、ポインタ、参照  ポインタは通常使わ(れ)ない。  Cでいう構造体ポインタからのメンバ参照foo->barの->は存在しない。 (*foo).barと書く。  C言語ライブラリとつなぐ際はポインタを多用する(せざるを得ない)。  配列 let array: [u32; 3] = [ 1, 2, 3];  配列先頭のポインタ let ptr = array.as_ptr(); // Cではuint32_t *ptr = array; let mut_ptr: *mut u32; // 型推論しない場合は例えばこう  ポインタから配列の中身を参照 let val = *ptr.offset(1); // Cでは*(ptr + 1)で、値は2  Rust言語では通常変数のアクセスを追跡して安全を保障する。  ポインタの参照は安全ではないためunsafeという指定が必要。  unsafe fn foo () { … } など関数単位の指定が多いようだ。 26
  • 27.
    structとimpl (trait)  structを定義し、それに対してfnを定義することができる。 traitと呼ばれる。C++のclassに対するメンバ関数のようなもの。  struct Foo { v: u32, } impl Foo { fn add(&mut self, val: u32) { self.v = self.v + val; } } fn main() { let mut testfoo = Foo { v: 10 }; testfoo.add(2); println!(”{}”, testfoo.v); // 12 } 27
  • 28.
    Generic functions  C++のテンプレート相当。 テンプレートのように foo<T>などと定義し、foo<u32>など指定して使う。  定義(traitを使う) impl <T> Vec<T> { pub fn new() -> Vec<T> { … } … }  使用例 let mut vec: Vec<i32> = Vec::new(); 28
  • 29.
    Rustのライブラリ、crate  crateとは「木箱」という意味。  言語上はexterncrate 名前; で引き込む。  モジュール名::サブモジュール名::型などと書いて参照  use mod::submod::type; などとすれば単にtypeで参照できる  たとえばHTTP実装 Hyper https://hyper.rs/  stdから始まるものは標準で引き込まれるのでextern crate不要。  std::thread, std::option, std::syncなど 29
  • 30.
    crateの引き込み方  パッケージシステム上はCargo.tomlに記述を加える。  例 [dependencies] libc= ”0.2” my_crate = { git = ”https://github.com/...”, rev = ”8ef654d…” }  https://crates.io/ から取得する。  有用な定番ものから開発途上のものまで1万以上が登録されている。  git = https://github.com/... と書けばgithubから取得できる。  rev = ”1075f4e…”など書けばcommit指定ができる。rev指定がない場合はmasterを取得する。  cargo buildあるいはcargo run実行でソースを取得しビルドする。  一度取得したら以後はローカルのものを使用。  通常は静的リンクになるので出来上がるバイナリは大きくなる。  取得元の更新内容と同期(再取得)するにはcargo updateを実行する。 30
  • 31.
    C言語ライブラリの呼び出し方  extern ”C”をつけてRust風にfn宣言すれば呼び出せる。 structもRust風に定義する。  例 extern ”C” pub fn your_c_func(arg: u32) -> u32;  ポインタにアクセスするものはだいたいunsafe use ::std::os::raw::c_void; unsafe extern “C” pub fn unsafe_func(arg: c_void);  Foeign Function Interface (FFI)と呼んでいる。  リンクするには、 Cargo.tomlと同じ場所にbuild.rsを用意する。  build.rsの例 fn main() { // libfooをリンク println!(”cargo:rustc-link-lib=foo”); } 31
  • 32.
    Cライブラリのbindingを自動生成  bindgenというツールで自動生成できる。  https://rust-lang-nursery.github.io/rust-bindgen/ コマンドラインツールとして使う場合は、cargo install bindgenでインストール。  C言語のヘッダを読ませ、Rustソース(extern ”C”の塊)を出力する。  シェル等からbindgenコマンドを呼び出すのでもいいが、 crateになってるのでbuild.rsに書くのがおすすめ。(Cargo.tomlへの追加も忘れずに)  例 extern crate bindgen; use std::path::PathBuf; fn main() { let bindings = bindgen::Builder::default() .header(”foo.h”) .generate(); bindings.write_to_file(PathBuf::from(”src/foo.rs”)); } 32
  • 33.
    bindgenでの生成例 C言語  ulimit.h (一部) enum { UL_GETFSIZE = 1, UL_SETFSIZE, __UL_GETMAXBRK, __UL_GETOPENMAX }; extern long int ulimit (int __cmd, …); Rust (bindgenで生成)  /* automatically generated by rust-bindgen */ pub const UL_GETFSIZE: _bindgen_ty_1 = _bindgen_ty_1::UL_GETFSIZE; pub const UL_SETFSIZE: _bindgen_ty_1 = _bindgen_ty_1::UL_SETFSIZE; pub const __UL_GETMAXBRK: _bindgen_ty_1 = _bindgen_ty_1::__UL_GETMAXBRK; pub const __UL_GETOPENMAX: _bindgen_ty_1 = _bindgen_ty_1::__UL_GETOPENMAX; #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum _bindgen_ty_1 { UL_GETFSIZE = 1, UL_SETFSIZE = 2, __UL_GETMAXBRK = 3, __UL_GETOPENMAX = 4, } extern "C" { pub fn ulimit(__cmd: ::std::os::raw::c_int, ...) -> ::std::os::raw::c_long; } 33
  • 34.
    Rustは使いやすいか?  注: 私見です C++と違い、明示的に書いたコードだけが動くので追いかけやすい  Goと違いスレッドの扱いやスレッド間通信が言語に隠蔽されない  Goと違いgitのcommitの指定が標準で可能  cgoみたいなコメントにゴリゴリ書く汚いスタイルではなくextern ”C”  型チェックが強力で、雑に書けるCより面倒だが安心感がある  エラー発生時にもしかして?と表示してくれるのは初心者には助かる  GCはない  言語とライブラリの境界がはっきりしている  かなりC言語に近い感覚で書けるので、Cがわかるならとっつきやすい  シンプルさに重きを置いたbetter C 34
  • 35.
    おまけ3: DPDK APIをRustから呼ぶ方法 bindgenを使えば楽勝? →実はbindgenで対応できないものがある  ヘッダファイルで定義されるinline functionに未対応  extern ”C”を吐き出すだけで実体を定義しないので、現状しかたない  回避方法  -fno-inlineでライブラリを再コンパイル(実際には意味がない)  自前でRustで書き直す  inline functionを呼び出すwrapperをC言語で書き、同時にリンクする 35