More Related Content Similar to Rust-DPDK (20) More from Masaru Oki (20) Rust-DPDK2. 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
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
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版のステータス
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
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
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でみたとおり)
別のソースから参照されるものは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
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等に似ている)
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
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とは「木箱」という意味。
言語上はextern crate 名前; で引き込む。
モジュール名::サブモジュール名::型などと書いて参照
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
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