開発環境の構築 ~DPDK入門 第9回~
今回はDPDKアプリケーションの開発環境の構築をテーマに説明します。
DPDK入門
- 2019年08月30日公開
はじめに
こんにちは、NTTテクノクロス株式会社の寺尾です。
今回はDPDKアプリケーションの開発環境をテーマに説明します。今まではDPDKに付随しているHello Worldのサンプルプログラム環境でコンパイルをしてきました。今回は環境準備、コンパイル環境などのポイントを説明していきます。
事前準備
開発環境の構築方法は、マシン環境によって変わります。今回は以下の条件で説明します。
- CPU : Intel社の64bit-CPU(x86_64アーキテクチャ)
- OS : Linux (4.15.0-54-generic)
- DPDK : 18.11 (LTS)
それでは、DPDKをコンパイルする前に必要な事前準備について解説します。
最初にBIOS設定についてですが、今まで説明していた機能であれば特別なBIOS設定をしなくとも問題ありません。ただし一部の機能に関しては、BIOS設定が必要がありますので、詳しく知りたい方はDPDKの公式ページ(※1)を参照してください。尚、BIOS設定で「ハイパースレッティングテクノロジ」が有効化(※2)されていれば、DPDKで使用できるCPU(論理プロセッサ)が増えます。
次にDPDKを扱うために必要なパッケージをインストールします。DPDKを動作させるスタンダードなOSはLinuxであり、今まではDebian系のUbuntuを中心に説明をしてきました。その他にもRedhat系は良く利用するかと思いましたので、今回は必要なパッケージはDebian系とRedhat系を取り上げて説明します。それぞれ以下のディストリビューションを例として紹介します。
- Debian系 : Ubuntu 18.04
- Redhat系 : CentOS 7.6
まずはパッケージを最新にします。
次に開発に必要なパッケージをインストールします。Ubuntuならば「build-essentials」を、CentOSならば「Development Tools」をgroupinstallすると、一括でインストールできるため便利です。
コンパイラですが、Linuxは「gcc(バージョン4.9以降)」が推奨されています。clang(※3)やicc(※4)も使えますが必須ではないため、今回は割愛します。
DPDKはigb_uioなどのカーネルモジュールをビルドするため、カーネルモジュールに必要なパッケージをインストールします。この時、使用するLinuxカーネルのバージョンに合わせたパッケージが必要なので、"uname -r"コマンドでカーネルバージョンを指定しています。
DPDKをコンパイルするためにはNUMA関連のパッケージが必要です。また、PCAP-PMDを利用する場合は、PCAPに必要なパッケージをインストールします。NUMAについてはリソース設定(メモリ) ~DPDK入門 第5回~の「NUMAとは」、PCAP-PMDについてはリソース設定(NIC) ~DPDK入門 第6回~の「PCAP-PMDとは」で解説しています。
最後に"dpdk-devbind.py"などのスクリプトを使うために、python(python2系なら2.7以降、python3系なら3.2以降)が必要です。"dpdk-devbind.py"についてはリソース設定(NIC) ~DPDK入門 第6回~の「uio_pci_genericの使い方」で解説しています。
※1 Enabling Additional Functionality
https://doc.dpdk.org/guides-18.11/linux_gsg/enable_func.html
※2 ハイパースレッティングテクノロジの確認方法
以下のコマンド結果で「physical id(物理CPU)とcore id(物理CPU上の物理コア)が一致するprocessor(論理プロセッサ)が複数ある」場合、ハイパースレッティングテクノロジが有効です。以下の場合、物理CPU:0上の物理コア:0がハイパースレッディングテクノロジにより、2つの論理プロセッサ:0と16として扱えるようになっています。
※3 a C language family frontend for LLVM
https://clang.llvm.org/
※4 Intel C++ Compiler
https://software.intel.com/en-us/c-compilers
- CPU : Intel社の64bit-CPU(x86_64アーキテクチャ)
- OS : Linux (4.15.0-54-generic)
- DPDK : 18.11 (LTS)
それでは、DPDKをコンパイルする前に必要な事前準備について解説します。
最初にBIOS設定についてですが、今まで説明していた機能であれば特別なBIOS設定をしなくとも問題ありません。ただし一部の機能に関しては、BIOS設定が必要がありますので、詳しく知りたい方はDPDKの公式ページ(※1)を参照してください。尚、BIOS設定で「ハイパースレッティングテクノロジ」が有効化(※2)されていれば、DPDKで使用できるCPU(論理プロセッサ)が増えます。
次にDPDKを扱うために必要なパッケージをインストールします。DPDKを動作させるスタンダードなOSはLinuxであり、今まではDebian系のUbuntuを中心に説明をしてきました。その他にもRedhat系は良く利用するかと思いましたので、今回は必要なパッケージはDebian系とRedhat系を取り上げて説明します。それぞれ以下のディストリビューションを例として紹介します。
- Debian系 : Ubuntu 18.04
- Redhat系 : CentOS 7.6
まずはパッケージを最新にします。
// Ubuntu 18.04
$ sudo apt update
$ sudo apt upgrade
// CentOS 7.6
$ sudo yum update
$ sudo apt update
$ sudo apt upgrade
// CentOS 7.6
$ sudo yum update
次に開発に必要なパッケージをインストールします。Ubuntuならば「build-essentials」を、CentOSならば「Development Tools」をgroupinstallすると、一括でインストールできるため便利です。
// Ubuntu 18.04
$ sudo apt install build-essentials
// CentOS 7.6
$ sudo yum groupinstall "Development Tools"
$ sudo apt install build-essentials
// CentOS 7.6
$ sudo yum groupinstall "Development Tools"
コンパイラですが、Linuxは「gcc(バージョン4.9以降)」が推奨されています。clang(※3)やicc(※4)も使えますが必須ではないため、今回は割愛します。
DPDKはigb_uioなどのカーネルモジュールをビルドするため、カーネルモジュールに必要なパッケージをインストールします。この時、使用するLinuxカーネルのバージョンに合わせたパッケージが必要なので、"uname -r"コマンドでカーネルバージョンを指定しています。
// Ubuntu 18.04
$ sudo apt install linux-headers-$(uname -r)
// CentOS 7.6
$ sudo yum install kernel-devel-$(uname -r) kernel-headers-$(uname -r)
$ sudo apt install linux-headers-$(uname -r)
// CentOS 7.6
$ sudo yum install kernel-devel-$(uname -r) kernel-headers-$(uname -r)
DPDKをコンパイルするためにはNUMA関連のパッケージが必要です。また、PCAP-PMDを利用する場合は、PCAPに必要なパッケージをインストールします。NUMAについてはリソース設定(メモリ) ~DPDK入門 第5回~の「NUMAとは」、PCAP-PMDについてはリソース設定(NIC) ~DPDK入門 第6回~の「PCAP-PMDとは」で解説しています。
// Ubuntu 18.04
$ sudo apt install libnuma-dev libpcap-dev
// CentOS 7.6
$ sudo yum install numactl-devel libpcap-devel
$ sudo apt install libnuma-dev libpcap-dev
// CentOS 7.6
$ sudo yum install numactl-devel libpcap-devel
最後に"dpdk-devbind.py"などのスクリプトを使うために、python(python2系なら2.7以降、python3系なら3.2以降)が必要です。"dpdk-devbind.py"についてはリソース設定(NIC) ~DPDK入門 第6回~の「uio_pci_genericの使い方」で解説しています。
// Ubuntu 18.04
$ sudo apt install python
// CentOS 7.6
$ sudo yum install python
$ sudo apt install python
// CentOS 7.6
$ sudo yum install python
※1 Enabling Additional Functionality
https://doc.dpdk.org/guides-18.11/linux_gsg/enable_func.html
※2 ハイパースレッティングテクノロジの確認方法
以下のコマンド結果で「physical id(物理CPU)とcore id(物理CPU上の物理コア)が一致するprocessor(論理プロセッサ)が複数ある」場合、ハイパースレッティングテクノロジが有効です。以下の場合、物理CPU:0上の物理コア:0がハイパースレッディングテクノロジにより、2つの論理プロセッサ:0と16として扱えるようになっています。
$ cat /proc/cpuinfo | grep -e "core id" -e "physical id" -e "processor"
processor : 0
physical id : 0
core id : 0
・・・
processor : 16
physical id : 0
core id : 0
・・・
processor : 0
physical id : 0
core id : 0
・・・
processor : 16
physical id : 0
core id : 0
・・・
※3 a C language family frontend for LLVM
https://clang.llvm.org/
※4 Intel C++ Compiler
https://software.intel.com/en-us/c-compilers
コンパイル環境に関して
これまではDPDKのセットアップツールを利用してコンパイルしてきました。今回は細かくコンパイル環境に関して説明します。
コンパイル環境について
DPDKとは? ~DPDK入門 第1回~の「DPDKの環境準備」では、DPDKのセットアップツールで"x86_64-native-linuxapp-gcc"を選択することでコンパイルを行っていました。"x86_64-native-linuxapp-gcc"が「コンパイル環境」となります。なお、コンパイル環境ですが、DPDKの公式ページ(※5)では「DPDK Target」と表記されています。
コンパイル環境を指定することで、DPDKは動作環境に最適化されたコンパイルを行えるようになっています。コンパイル環境の書式は"ARCH-MACHINE-EXECENV-TOOLCHAIN"です。書式の各要素について説明します。
利用可能なコンパイル環境はDPDKの展開先で"make showconfigs"コマンドを実施することで確認できます。環境に合わせたコンパイル環境を使用するようにしてください。
※5 Installation of DPDK Target Environments
https://doc.dpdk.org/guides-18.11/linux_gsg/build_dpdk.html#installation-of-dpdk-target-environments
コンパイル環境を指定することで、DPDKは動作環境に最適化されたコンパイルを行えるようになっています。コンパイル環境の書式は"ARCH-MACHINE-EXECENV-TOOLCHAIN"です。書式の各要素について説明します。
要素 | 説明 |
ARCH | ARCHとMACHINEの組み合わせで使用するCPUを指定します。例えばIntel社の64bit-CPUなら"x86_64-native"、IBM社のPOWER8なら"ppc_64-power8"となります。 |
MACHINE | |
EXECENV | 動作環境(OS)の指定です。Linux(linuxapp)かFreeBSD(bsdapp)の2択です。 |
TOOLCHAIN | コンパイラの指定です。gccやclangなど使用するコンパイラを指定します。 |
利用可能なコンパイル環境はDPDKの展開先で"make showconfigs"コマンドを実施することで確認できます。環境に合わせたコンパイル環境を使用するようにしてください。
$ cd dpdk-<version>
$ make showconfigs
・・・
ppc_64-power8-linuxapp-gcc
x86_64-native-bsdapp-clang
・・・
x86_64-native-linuxapp-gcc
・・・
$ make showconfigs
・・・
ppc_64-power8-linuxapp-gcc
x86_64-native-bsdapp-clang
・・・
x86_64-native-linuxapp-gcc
・・・
※5 Installation of DPDK Target Environments
https://doc.dpdk.org/guides-18.11/linux_gsg/build_dpdk.html#installation-of-dpdk-target-environments
DPDKのコンフィグ
DPDKは必要な機能などを設定するためのconfigファイルが用意されています。場合によっては必要な設定をコンパイル前に実施する必要がありますので、いくつか紹介します。以前リソース設定(NIC) ~DPDK入門 第6回~で、PCAP-PMDを使うために「common_base」というファイルを編集したことを覚えていますでしょうか。
このようにDPDKのconfigファイルで機能の有効化をすることができます。この他にも「DPDKで利用するリソースの上限」も設定ファイルで指定します。以下はDPDKアプリケーションで使用可能なポート数の上限を64に変更する例です。
DPDKのconfigファイルで可能な設定の一部を以下に示します。
DPDKの設定項目(一部)
DPDKで使用する機能を有効化したりリソースの上限値を調節することができます。適宜必要な設定を行い、再コンパイルしてください。
※6 静的ライブラリと共有ライブラリ
ライブラリにはプログラム内に静的に組み込む「静的ライブラリ」と、プログラム実行時に動的に呼び出す「共有ライブラリ」の2種類あります。また、実行ファイルに静的ライブラリを組み込むことを「スタティックリンク」、プログラムを実行する時にライブラリを呼び出すことを「ダイナミックリンク」と呼びます。DPDKライブラリはどちらの形式にも対応できます。違いに関して、以下に示します。
DPDKは設定によってどちらのライブラリでコンパイルするか切り替えられます。DPDKでは明示的に指定しない限り静的ライブラリを組み込み実行ファイルを作成します。
▼ dpdk-version/config/common_base
・・・ #CONFIG_RTE_LIBRTE_PMD_PCAP=n CONFIG_RTE_LIBRTE_PMD_PCAP=y ・・・
このようにDPDKのconfigファイルで機能の有効化をすることができます。この他にも「DPDKで利用するリソースの上限」も設定ファイルで指定します。以下はDPDKアプリケーションで使用可能なポート数の上限を64に変更する例です。
▼ dpdk-version/config/common_base
・・・ #CONFIG_RTE_MAX_ETHPORTS=32 CONFIG_RTE_MAX_ETHPORTS=64 ・・・
DPDKのconfigファイルで可能な設定の一部を以下に示します。
DPDKの設定項目(一部)
設定 | デフォルト値 | 説明 |
CONFIG_RTE_MAX_LCORE | 128 | 使用可能なCPUコア数の上限値 |
CONFIG_RTE_MEMPOOL_CACHE_MAX_SIZE | 512 | DPDKのメモリプールで使用するキャッシュの上限値 |
CONFIG_RTE_MAX_ETHPORTS | 32 | 使用可能なポート数の上限値 |
CONFIG_RTE_MAX_QUEUES_PER_PORT | 1024 | 使用可能なポート毎のキュー数の上限値 |
CONFIG_RTE_LIBRTE_ETHDEV_DEBUG | n | 'y'を設定すると、パケット処理用API内のデバックログを有効化してコンパイルする |
CONFIG_RTE_LIBRTE_IGB_PMD | y | 'n'を設定すると、IGB-PMDを無効化してコンパイルする |
CONFIG_RTE_LIBRTE_IXGBE_PMD | y | 'n'を設定すると、IXGBE-PMDを無効化してコンパイルする |
CONFIG_RTE_LIBRTE_IXGBE_DEBUG_RX | n | 'y'を設定すると、IXGBE-PMDの受信処理内に組み込まれているデバックログを有効化してコンパイルする |
CONFIG_RTE_LIBRTE_IXGBE_DEBUG_TX | n | 'y'を設定すると、IXGBE-PMDの送信処理内に組み込まれているデバックログを有効化してコンパイルする |
CONFIG_RTE_LIBRTE_PMD_PCAP | n | 'y'を設定すると、PCAP-PMDを有効化してコンパイルする |
CONFIG_RTE_LIBRTE_VIRTIO_PMD | y | 'n'を設定すると、VIRTIO-PMDを無効化してコンパイルする |
CONFIG_RTE_BUILD_SHARED_LIB | n | 'n'を設定すると、DPDKを静的ライブラリでコンパイルする。'y'を設定すると、DPDKを共有ライブラリでコンパイルする。(※6) |
DPDKで使用する機能を有効化したりリソースの上限値を調節することができます。適宜必要な設定を行い、再コンパイルしてください。
※6 静的ライブラリと共有ライブラリ
ライブラリにはプログラム内に静的に組み込む「静的ライブラリ」と、プログラム実行時に動的に呼び出す「共有ライブラリ」の2種類あります。また、実行ファイルに静的ライブラリを組み込むことを「スタティックリンク」、プログラムを実行する時にライブラリを呼び出すことを「ダイナミックリンク」と呼びます。DPDKライブラリはどちらの形式にも対応できます。違いに関して、以下に示します。
リンク方法 | メリット | デメリット |
スタティックリンク | ライブラリの呼び出しが早い。 | アプリケーションのサイズが大きい。 |
ダイナミックリンク | アプリケーションのサイズが小さくなるため、プロセス起動が早い。 | 動的にライブラリを呼び出すため、初回のライブラリ呼び出しに時間がかかる。 |
DPDKは設定によってどちらのライブラリでコンパイルするか切り替えられます。DPDKでは明示的に指定しない限り静的ライブラリを組み込み実行ファイルを作成します。
コンパイルの実行
今回はセットアップツールを使わずにmakeコマンドでDPDKライブラリを作成する方法を紹介します。DPDKのconfigファイルの変更を確認するために「静的ライブラリ」と「共有ライブラリ」の2つのパターンで行います。
まず、DPDKを配置したディレクトリまで移動し、環境変数を設定します。これまでの記事でも何度か登場した環境変数"RTE_SDK"と"RTE_TAEGET"の設定が必要です。"RTE_SDK"にはDPDKを配置したディレクトリパスを、"RTE_TAEGET"はコンパイル環境を指定します。
次にDPDKのconfigファイルを設定します。リソース設定(NIC) ~DPDK入門 第6回~では直接「common_base」を編集していましたが、コンパイル環境に合わせたconfigファイルを作成してから編集する方が望ましいです。DPDKのconfigファイルは"make config"コマンドで作成できます。この時、変数:"T"でコンパイル環境を、変数:"O"でDPDKのconfigファイルやライブラリの出力先ディレクトリ名を指定します。今回は"O"と"C"でコンパイル環境名を指定しています。
DPDKのconfigファイルは"make config"で作成されたディレクトリ内の「.config」となります。
まずはconfigファイルの編集を行わないで、デフォルト通り静的ライブラリを作成します。先ほど"make config"コマンドで作られたディレクトリ内にコンパイル用のMakefileが生成されているので、そこまで移動してから"makeコマンド"を実行します。
コンパイル後、DPDKライブラリはlibディレクトリ配下に出力されています。libディレクトリ配下を確認すると、以下のようにDPDKの静的ライブラリ(拡張子が.aのファイル)が作成されていることがわかります。
Hello Worldサンプルアプリケーションをコンパイルし、実行ファイルにDPDKライブラリがどう組み込まれているか確認してみましょう。ここでは、nmコマンド(※7)を利用して、実行ファイルにrte_eal_init()がどう組み込まれているか確認しています。
上記のようにrte_eal_init()にアドレスが割り振られ、実行ファイル内に組み込まれていることがわかります。
次にDPDKのconfigファイルを変更して共有ライブラリを作成してみましょう。一旦作成したDPDKライブラリは"make clean"コマンドで削除できます。
DPDKのconfigファイルを編集して、共有ライブラリを有効化します。以下のように「CONFIG_RTE_BUILD_SHARED_LIB」を"n"から"y"に変更します。
先ほどと同様にmakeします。make完了後、libディレクトリ配下に、今度はDPDKの共有ライブラリ(拡張子が.soのファイル)が作成されていることがわかります。
なお、DPDKを共有ライブラリ化した場合、アプリケーションがダイナミックリンク出来るように設定が必要です。このために"/etc/ld.so.conf.d/"配下にDPDK共有ライブラリへのパスを記載したファイルを作成します。作成するファイル名は拡張子が".conf"である必要があります。今回は「libdpdk.conf」という名前のconfigファイルとしました。
この後、ldconfigコマンド(※8)を実行し、DPDKライブラリを共有ライブラリとして使用できるようにします。
最後に、Hello Worldサンプルアプリケーションをコンパイルし、共有ライブラリとダイナミックリンクが行われるか確認してみましょう。Hello worldサンプルアプリケーションをコンパイル後、lddコマンド(※9)で共有ライブラリとのリンクを確認できます。
また、静的ライブラリ時と同様に、nmコマンドでDPDKライブラリが実行ファイル内にどう組み込まれているか確認します。今度はrte_eal_init()にアドレスが割り振られておらず、未定義扱いになっているため、実行ファイル内にDPDKライブラリが組み込まれていないことがわかります。
※7 nm
https://linuxjm.osdn.jp/html/GNU_binutils/man1/nm.1.html
※8 ldconfig
https://linuxjm.osdn.jp/html/ld.so/man8/ldconfig.8.html
※9 ldd
https://linuxjm.osdn.jp/html/ld.so/man1/ldd.1.html
まず、DPDKを配置したディレクトリまで移動し、環境変数を設定します。これまでの記事でも何度か登場した環境変数"RTE_SDK"と"RTE_TAEGET"の設定が必要です。"RTE_SDK"にはDPDKを配置したディレクトリパスを、"RTE_TAEGET"はコンパイル環境を指定します。
$ cd dpdk-<version>
$ export RTE_SDK=`pwd`
$ export RTE_TARGET=x86_64-native-linuxapp-gcc
$ export RTE_SDK=`pwd`
$ export RTE_TARGET=x86_64-native-linuxapp-gcc
次にDPDKのconfigファイルを設定します。リソース設定(NIC) ~DPDK入門 第6回~では直接「common_base」を編集していましたが、コンパイル環境に合わせたconfigファイルを作成してから編集する方が望ましいです。DPDKのconfigファイルは"make config"コマンドで作成できます。この時、変数:"T"でコンパイル環境を、変数:"O"でDPDKのconfigファイルやライブラリの出力先ディレクトリ名を指定します。今回は"O"と"C"でコンパイル環境名を指定しています。
$ make config O=$RTE_TARGET T=$RTE_TARGET
Configuration done using x86_64-native-linuxapp-gcc
Configuration done using x86_64-native-linuxapp-gcc
DPDKのconfigファイルは"make config"で作成されたディレクトリ内の「.config」となります。
$ ls -la $RTE_TARGET
・・・
-rw-rw-r-- 1 xxx xxx 18935 Jul 8 07:32 .config
・・・
・・・
-rw-rw-r-- 1 xxx xxx 18935 Jul 8 07:32 .config
・・・
まずはconfigファイルの編集を行わないで、デフォルト通り静的ライブラリを作成します。先ほど"make config"コマンドで作られたディレクトリ内にコンパイル用のMakefileが生成されているので、そこまで移動してから"makeコマンド"を実行します。
$ cd $RTE_TARGET
$ make
・・・
Build complete [x86_64-native-linuxapp-gcc]
$ make
・・・
Build complete [x86_64-native-linuxapp-gcc]
コンパイル後、DPDKライブラリはlibディレクトリ配下に出力されています。libディレクトリ配下を確認すると、以下のようにDPDKの静的ライブラリ(拡張子が.aのファイル)が作成されていることがわかります。
$ ls -la lib
・・・
-rw-rw-r-- 1 xxxxx xxxxx 2219 Jul 8 07:46 libdpdk.a
-rw-rw-r-- 1 xxxxx xxxxx 122996 Jul 8 07:41 librte_acl.a
・・・
・・・
-rw-rw-r-- 1 xxxxx xxxxx 2219 Jul 8 07:46 libdpdk.a
-rw-rw-r-- 1 xxxxx xxxxx 122996 Jul 8 07:41 librte_acl.a
・・・
Hello Worldサンプルアプリケーションをコンパイルし、実行ファイルにDPDKライブラリがどう組み込まれているか確認してみましょう。ここでは、nmコマンド(※7)を利用して、実行ファイルにrte_eal_init()がどう組み込まれているか確認しています。
$ cd ../examples/helloworld
$ make
CC main.o
LD helloworld
INSTALL-APP helloworld
INSTALL-MAP helloworld.map
$ nm build/helloworld | grep rte_eal_init
0000000000178de0 T rte_eal_init
$ make
CC main.o
LD helloworld
INSTALL-APP helloworld
INSTALL-MAP helloworld.map
$ nm build/helloworld | grep rte_eal_init
0000000000178de0 T rte_eal_init
上記のようにrte_eal_init()にアドレスが割り振られ、実行ファイル内に組み込まれていることがわかります。
次にDPDKのconfigファイルを変更して共有ライブラリを作成してみましょう。一旦作成したDPDKライブラリは"make clean"コマンドで削除できます。
$ cd $RTE_SDK/$RTE_TARGET
$ make clean
・・・
Clean complete
$ make clean
・・・
Clean complete
DPDKのconfigファイルを編集して、共有ライブラリを有効化します。以下のように「CONFIG_RTE_BUILD_SHARED_LIB」を"n"から"y"に変更します。
▼ $RTE_SDK/$RTE_TARGET/.config
・・・ # Compile to share library #CONFIG_RTE_BUILD_SHARED_LIB=n CONFIG_RTE_BUILD_SHARED_LIB=y
先ほどと同様にmakeします。make完了後、libディレクトリ配下に、今度はDPDKの共有ライブラリ(拡張子が.soのファイル)が作成されていることがわかります。
$ ls -la lib
・・・
-rw-rw-r-- 1 xxxxx xxxxx 2333 Jul 8 08:05 libdpdk.so
lrwxrwxrwx 1 xxxxx xxxxx 17 Jul 8 08:00 librte_acl.so -> librte_acl.so.2.1
-rwxrwxr-x 1 xxxxx xxxxx 110568 Jul 8 08:00 librte_acl.so.2.1
・・・
・・・
-rw-rw-r-- 1 xxxxx xxxxx 2333 Jul 8 08:05 libdpdk.so
lrwxrwxrwx 1 xxxxx xxxxx 17 Jul 8 08:00 librte_acl.so -> librte_acl.so.2.1
-rwxrwxr-x 1 xxxxx xxxxx 110568 Jul 8 08:00 librte_acl.so.2.1
・・・
なお、DPDKを共有ライブラリ化した場合、アプリケーションがダイナミックリンク出来るように設定が必要です。このために"/etc/ld.so.conf.d/"配下にDPDK共有ライブラリへのパスを記載したファイルを作成します。作成するファイル名は拡張子が".conf"である必要があります。今回は「libdpdk.conf」という名前のconfigファイルとしました。
▼ /etc/ld.so.conf.d/libdpdk.conf
# (例)/opt配下にDPDKを展開し、x86_64-native-linuxapp-gcc配下にDPDKライブラリを出力した場合 /opt/dpdk-stable-18.11.2/x86_64-native-linuxapp-gcc/lib
この後、ldconfigコマンド(※8)を実行し、DPDKライブラリを共有ライブラリとして使用できるようにします。
$ sudo ldconfig
最後に、Hello Worldサンプルアプリケーションをコンパイルし、共有ライブラリとダイナミックリンクが行われるか確認してみましょう。Hello worldサンプルアプリケーションをコンパイル後、lddコマンド(※9)で共有ライブラリとのリンクを確認できます。
$ cd ../examples/helloworld
$ make clean
$ make
CC main.o
LD helloworld
INSTALL-APP helloworld
INSTALL-MAP helloworld.map
$ ldd build/helloworld
・・・
librte_eal.so.9.1 => /opt/dpdk-stable-18.11.2/x86_64-native-linuxapp-gcc/lib/librte_eal.so.9.1 (0x00007fe74f98e000)
・・・
$ make clean
$ make
CC main.o
LD helloworld
INSTALL-APP helloworld
INSTALL-MAP helloworld.map
$ ldd build/helloworld
・・・
librte_eal.so.9.1 => /opt/dpdk-stable-18.11.2/x86_64-native-linuxapp-gcc/lib/librte_eal.so.9.1 (0x00007fe74f98e000)
・・・
また、静的ライブラリ時と同様に、nmコマンドでDPDKライブラリが実行ファイル内にどう組み込まれているか確認します。今度はrte_eal_init()にアドレスが割り振られておらず、未定義扱いになっているため、実行ファイル内にDPDKライブラリが組み込まれていないことがわかります。
$ nm build/helloworld | grep rte_eal_init
U rte_eal_init@@DPDK_2.0
U rte_eal_init@@DPDK_2.0
※7 nm
https://linuxjm.osdn.jp/html/GNU_binutils/man1/nm.1.html
※8 ldconfig
https://linuxjm.osdn.jp/html/ld.so/man8/ldconfig.8.html
※9 ldd
https://linuxjm.osdn.jp/html/ld.so/man1/ldd.1.html
DPDKを共有ライブラリ化した場合の注意点について
DPDKを共有ライブラリ化した場合、1点注意が必要になります。共有ライブラリ化対象にはPMDなどのドライバも含められますが、DPDKアプリケーションを実行するまで具体的なNICの種類が分からないため、事前に利用すべきPMDが分からない問題があります。そのため、DPDKにはアプリケーション実行後に読み込む共有ライブラリを指定する方法がありますので解説します。
ここではDPDKを共有ライブラリ化した場合にL2 Forwarding Sampleを起動する方法を記載します。L2 Forwarding Sampleについては、DPDKのパケット処理 ~DPDK入門 第2回~を参照してください。なお、動作環境は先ほどDPDKを共有ライブラリでコンパイルした環境を利用します。読み込む共有ライブラリを指定する方法はいくつかありますが、今回はconfigファイルで設定したディレクトリからドライバを探す方法を紹介します。
DPDKのconfigファイルを編集して「CONFIG_RTE_EAL_PMD_PATH」にドライバを格納しておくディレクトリへのパスを指定します。ここでは"/opt/dpdk-stable-18.11.2/x86_64-native-linuxapp-gcc/drivers"の配下にドライバを格納するように指定しました。
それでは、"drivers"ディレクトリを作成し、DPDKをコンパイルします。
"lib"ディレクトリ配下に共有ライブラリが出力されているので、この中からL2 Forwarding Sampleで使用するドライバを"drivers"へ移します。今回は利用するNICのドライバがIGBであるため"librte_pmd_e1000.so"、メモリプールリングドライバの" librte_mempool_ring.so"を"drivers"配下にコピーします。
なお、余計なドライバを"drivers"配下に格納した場合、アプリケーションの起動に失敗することがあるので、L2 Forwarding Sampleで使用するライブラリだけを格納することを推奨します。
あとは今までの方法でアプリケーションをコンパイルして実行します。
パケットの送受信処理のモニタ画面が表示され、L2 Forwarding Sampleが正常に起動できました。
ここではDPDKを共有ライブラリ化した場合にL2 Forwarding Sampleを起動する方法を記載します。L2 Forwarding Sampleについては、DPDKのパケット処理 ~DPDK入門 第2回~を参照してください。なお、動作環境は先ほどDPDKを共有ライブラリでコンパイルした環境を利用します。読み込む共有ライブラリを指定する方法はいくつかありますが、今回はconfigファイルで設定したディレクトリからドライバを探す方法を紹介します。
DPDKのconfigファイルを編集して「CONFIG_RTE_EAL_PMD_PATH」にドライバを格納しておくディレクトリへのパスを指定します。ここでは"/opt/dpdk-stable-18.11.2/x86_64-native-linuxapp-gcc/drivers"の配下にドライバを格納するように指定しました。
▼ $RTE_SDK/$RTE_TARGET/.config
・・・ # Default driver path (or "" to disable) #CONFIG_RTE_EAL_PMD_PATH="" CONFIG_RTE_EAL_PMD_PATH="/opt/dpdk-stable-18.11.2/x86_64-native-linuxapp-gcc/drivers"
それでは、"drivers"ディレクトリを作成し、DPDKをコンパイルします。
$ cd $RTE_SDK/$RTE_TARGET
$ mkdir drivers
$ make clean
$ make
$ mkdir drivers
$ make clean
$ make
"lib"ディレクトリ配下に共有ライブラリが出力されているので、この中からL2 Forwarding Sampleで使用するドライバを"drivers"へ移します。今回は利用するNICのドライバがIGBであるため"librte_pmd_e1000.so"、メモリプールリングドライバの" librte_mempool_ring.so"を"drivers"配下にコピーします。
$ cp -a lib/librte_pmd_e1000* drivers/
$ cp -a lib/librte_mempool_ring* drivers/
$ ls -la drivers/
・・・
lrwxrwxrwx 1 xxxxx xxxxx 26 Aug 1 05:01 librte_mempool_ring.so -> librte_mempool_ring.so.1.1*
-rwxrwxr-x 1 xxxxx xxxxx 17280 Aug 1 05:01 librte_mempool_ring.so.1.1*
lrwxrwxrwx 1 xxxxx xxxxx 23 Aug 1 05:02 librte_pmd_e1000.so -> librte_pmd_e1000.so.1.1*
-rwxrwxr-x 1 xxxxx xxxxx 490520 Aug 1 05:02 librte_pmd_e1000.so.1.1*
$ cp -a lib/librte_mempool_ring* drivers/
$ ls -la drivers/
・・・
lrwxrwxrwx 1 xxxxx xxxxx 26 Aug 1 05:01 librte_mempool_ring.so -> librte_mempool_ring.so.1.1*
-rwxrwxr-x 1 xxxxx xxxxx 17280 Aug 1 05:01 librte_mempool_ring.so.1.1*
lrwxrwxrwx 1 xxxxx xxxxx 23 Aug 1 05:02 librte_pmd_e1000.so -> librte_pmd_e1000.so.1.1*
-rwxrwxr-x 1 xxxxx xxxxx 490520 Aug 1 05:02 librte_pmd_e1000.so.1.1*
なお、余計なドライバを"drivers"配下に格納した場合、アプリケーションの起動に失敗することがあるので、L2 Forwarding Sampleで使用するライブラリだけを格納することを推奨します。
あとは今までの方法でアプリケーションをコンパイルして実行します。
$ cd $RTE_SDK/examples/l2fwd
$ make
$ sudo ./build/l2fwd -c 0x1 -- -p 0x1
・・・
Port statistics ====================================
Statistics for port 0 ------------------------------
Packets sent: 0
Packets received: 0
Packets dropped: 0
Aggregate statistics ===============================
Total packets sent: 0
Total packets received: 0
Total packets dropped: 0
====================================================
$ make
$ sudo ./build/l2fwd -c 0x1 -- -p 0x1
・・・
Port statistics ====================================
Statistics for port 0 ------------------------------
Packets sent: 0
Packets received: 0
Packets dropped: 0
Aggregate statistics ===============================
Total packets sent: 0
Total packets received: 0
Total packets dropped: 0
====================================================
パケットの送受信処理のモニタ画面が表示され、L2 Forwarding Sampleが正常に起動できました。
Makefile
今まではDPDKアプリケーションをコンパイルする際には、Hello Worldサンプルアプリケーションの環境をそのまま利用していました。実際に開発する場合は、ソースコードのファイルやディレクトリ構成を任意にしたい、バイナリ名は独自に定義したいなどあると思います。そのような場合は開発者が"Makefile"を作成する必要がありますので、その作成方法を説明します。
解説にはDPDK入門 第7回~DPDK入門第8回で作成したDPDKアプリケーションの「初期化処理」と「パケット処理」に分割するように拡張します。
- ソースファイルを初期化処理は「main.c」、パケット処理は「fwd.c」に分割。
- ヘッダファイルとして「fwd.h」を用意し、パケット処理を外部からヘッダファイルをincludeすることで呼び出せるようにする。
- Makefileではこれらのファイルをコンパイル対象に含め、「my_app」というバイナリを生成
- ソースファイル、ヘッダファイル、Makefileは"src"というディレクトリ直下に配置
以上を踏まえた構成イメージを以下に示します。
解説にはDPDK入門 第7回~DPDK入門第8回で作成したDPDKアプリケーションの「初期化処理」と「パケット処理」に分割するように拡張します。
- ソースファイルを初期化処理は「main.c」、パケット処理は「fwd.c」に分割。
- ヘッダファイルとして「fwd.h」を用意し、パケット処理を外部からヘッダファイルをincludeすることで呼び出せるようにする。
- Makefileではこれらのファイルをコンパイル対象に含め、「my_app」というバイナリを生成
- ソースファイル、ヘッダファイル、Makefileは"src"というディレクトリ直下に配置
以上を踏まえた構成イメージを以下に示します。
Makefile作成
DPDKアプリケーションのMakefileは独自に作成することもできますが、依存するライブラリや定義が多く大変です。そこで、DPDKの公式ページで紹介されている方法だと簡易にMakefileが作成できますので、この方法を解説します。
まず、Makefileの先頭で"rte.vars.mk"をインクルードする必要があります。"rte.vars.mk"はDPDKを展開したディレクトリの"mk/"ディレクトリ配下にあります。
DPDKアプリケーションのバイナリ名を変数:"APP"で指定します。今回は"my_app"という名前のバイナリを作成するように指定しました。
コンパイル対象のソースコードは変数:"SRCS-y"で指定します。今回は"main.c"と"fwd.c"を指定します。
コンパイルオプションは通常のMakefileを作成する時と同様に、変数:"CFLAGS"で指定します。
今回指定しているコンパイルオプションについて説明します。
- -O3は、コンパイル時に適用する最適化レベルです。DPDKアプリケーションは大抵の場合、高い処理速度を求められるので、最も強い最適化である"O3"を指定します。最適化について、詳しくはgccのmanページ(※11)を参照してください。
- WERROR_FLAGSは、コンパイラの警告設定です。"rte.vars.mk"をインクルードすることにより使用可能で、コンパイル環境に合わせた適切な警告設定を行います。
また、必要があればCFLAGSの他にも、LDFLAGSやLDLIBSなどMakefileでよく使う定義が可能です。詳しくはDPDKの公式ページ(※10)を参照してください。
最後に、Makefileの終端で"rte.extapp.mk"をインクルードします。このファイルをインクルードすることで、DPDKが用意したMakefileがコンパイルからリンクまで行い、バイナリファイルを作成してくれます。
なお、DPDKアプリケーションをコンパイルする場合は"rte.extapp.mk"をインクルードしますが、DPDKを利用したライブラリを作成したい場合は"rte.extlib.mk"をインクルードします。この他にもDPDKが用意している目的別のMakefileがありますので、DPDKの公式ページ(※12)を参照してください。
※10 Customizing Makefiles
https://doc.dpdk.org/guides/prog_guide/build_app.html
※11 最適化オプション
https://linuxjm.osdn.jp/html/GNU_gcc/man1/gcc.1.html
※12 Makefile Types
https://doc.dpdk.org/guides/prog_guide/dev_kit_build_system.html
まず、Makefileの先頭で"rte.vars.mk"をインクルードする必要があります。"rte.vars.mk"はDPDKを展開したディレクトリの"mk/"ディレクトリ配下にあります。
▼ rte.vars.mkのインクルード
include $(RTE_SDK)/mk/rte.vars.mk
DPDKアプリケーションのバイナリ名を変数:"APP"で指定します。今回は"my_app"という名前のバイナリを作成するように指定しました。
▼ バイナリ名の指定
APP = my_app
コンパイル対象のソースコードは変数:"SRCS-y"で指定します。今回は"main.c"と"fwd.c"を指定します。
▼ ソースコードの指定
SRCS-y := fwd.c main.c
コンパイルオプションは通常のMakefileを作成する時と同様に、変数:"CFLAGS"で指定します。
▼ コンパイルオプションの指定
CFLAGS += -O3 $(WERROR_FLAGS)
今回指定しているコンパイルオプションについて説明します。
- -O3は、コンパイル時に適用する最適化レベルです。DPDKアプリケーションは大抵の場合、高い処理速度を求められるので、最も強い最適化である"O3"を指定します。最適化について、詳しくはgccのmanページ(※11)を参照してください。
- WERROR_FLAGSは、コンパイラの警告設定です。"rte.vars.mk"をインクルードすることにより使用可能で、コンパイル環境に合わせた適切な警告設定を行います。
また、必要があればCFLAGSの他にも、LDFLAGSやLDLIBSなどMakefileでよく使う定義が可能です。詳しくはDPDKの公式ページ(※10)を参照してください。
最後に、Makefileの終端で"rte.extapp.mk"をインクルードします。このファイルをインクルードすることで、DPDKが用意したMakefileがコンパイルからリンクまで行い、バイナリファイルを作成してくれます。
▼ rte.extapp.mkのインクルード
include $(RTE_SDK)/mk/rte.extapp.mk
なお、DPDKアプリケーションをコンパイルする場合は"rte.extapp.mk"をインクルードしますが、DPDKを利用したライブラリを作成したい場合は"rte.extlib.mk"をインクルードします。この他にもDPDKが用意している目的別のMakefileがありますので、DPDKの公式ページ(※12)を参照してください。
※10 Customizing Makefiles
https://doc.dpdk.org/guides/prog_guide/build_app.html
※11 最適化オプション
https://linuxjm.osdn.jp/html/GNU_gcc/man1/gcc.1.html
※12 Makefile Types
https://doc.dpdk.org/guides/prog_guide/dev_kit_build_system.html
動作確認
それでは作成したMakefileを用いてDPDKアプリケーションをコンパイルしてみましょう。ホームディレクトリ配下に"src"ディレクトリを作成し、直下に各ソースコードとMakefileを配置します。
DPDKアプリケーションをコンパイルしましょう。なお、DPDKアプリケーションのコンパイルの際には以下の指定が必須となります。
- 変数:"RTE_SDK"で、DPDKを配置したディレクトリパスを指定
- 変数:"RTE_TARGET"で、コンパイル環境を指定
コンパイルに成功すれば、DPDKアプリケーションのバイナリファイルが"build"ディレクトリ配下に作成されていることがわかります。
なお、以下のようにすることで、バイナリファイルの出力先ディレクトリを変更してコンパイルできます。
$ ls -la ~/src
・・・
-rw-rw-r-- 1 xxxxx xxxxx 1565 Jul 10 02:13 Makefile
-rw-rw-r-- 1 xxxxx xxxxx 1068 Jul 9 02:10 fwd.c
-rw-rw-r-- 1 xxxxx xxxxx 290 Jul 5 08:34 fwd.h
-rw-rw-r-- 1 xxxxx xxxxx 3267 Jul 9 02:02 main.c
・・・
-rw-rw-r-- 1 xxxxx xxxxx 1565 Jul 10 02:13 Makefile
-rw-rw-r-- 1 xxxxx xxxxx 1068 Jul 9 02:10 fwd.c
-rw-rw-r-- 1 xxxxx xxxxx 290 Jul 5 08:34 fwd.h
-rw-rw-r-- 1 xxxxx xxxxx 3267 Jul 9 02:02 main.c
DPDKアプリケーションをコンパイルしましょう。なお、DPDKアプリケーションのコンパイルの際には以下の指定が必須となります。
- 変数:"RTE_SDK"で、DPDKを配置したディレクトリパスを指定
- 変数:"RTE_TARGET"で、コンパイル環境を指定
$ cd dpdk-<version>
$ export RTE_SDK=`pwd`
$ export RTE_TARGET=x86_64-native-linuxapp-gcc
$ cd ~/src
$ make
CC fwd.o
CC main.o
LD my_app
INSTALL-APP my_app
INSTALL-MAP my_app.map
$ export RTE_SDK=`pwd`
$ export RTE_TARGET=x86_64-native-linuxapp-gcc
$ cd ~/src
$ make
CC fwd.o
CC main.o
LD my_app
INSTALL-APP my_app
INSTALL-MAP my_app.map
コンパイルに成功すれば、DPDKアプリケーションのバイナリファイルが"build"ディレクトリ配下に作成されていることがわかります。
$ ls -la build/
・・・
-rwxrwxr-x 1 xxxxx xxxxx 8731400 Jul 10 02:46 my_app
・・・
・・・
-rwxrwxr-x 1 xxxxx xxxxx 8731400 Jul 10 02:46 my_app
・・・
なお、以下のようにすることで、バイナリファイルの出力先ディレクトリを変更してコンパイルできます。
$ make O=<出力先ディレクトリ名>
コンパイル環境の階層化について
開発規模が大きい場合、アプリケーションの機能ごとにソースコードを格納するディレクトリを区切ったり、機能間で共通して参照するヘッダファイルを一つのディレクトリにまとめるなど、ソースコードの階層化が必要になると思います。この場合のMakefile作成方法を説明します。
先ほどのDPDKアプリケーションの構成を変更し、"src"ディレクトリ配下に"include"と"fwd"というサブディレクトリを作成し、"include"ディレクトリに「fwd.h」を、"fwd"ディレクトリに「fwd.c」を配置する構成にするとしましょう。その場合の構成イメージを以下に示します。
この場合、変数:"SRCS-y"で指定するソースコードへのパスを階層化します。今回はfwdディレクトリ配下にfwd.cがあるので、「fwd/fwd.c」と指定しました。
また、各ソースコードから「fwd.h」を共通して参照できるように、コンパイルオプションにインクルードディレクトリを追加します。インクルードディレクトリを指定するコンパイルオプションは"-I"です。"rte.vars.mk"をインクルードしていれば、変数:"RTE_SRCDIR"でソースコードのルートパスが取得できるので、それを利用して"include"へのパスを指定します。
後は構成イメージ通りにソースコードとMakefileを配置し、makeすることでDPDKアプリケーションの作成が出来ますので、試してみてください。
先ほどのDPDKアプリケーションの構成を変更し、"src"ディレクトリ配下に"include"と"fwd"というサブディレクトリを作成し、"include"ディレクトリに「fwd.h」を、"fwd"ディレクトリに「fwd.c」を配置する構成にするとしましょう。その場合の構成イメージを以下に示します。
この場合、変数:"SRCS-y"で指定するソースコードへのパスを階層化します。今回はfwdディレクトリ配下にfwd.cがあるので、「fwd/fwd.c」と指定しました。
▼ ソースコードの指定(階層化されたソースコードを考慮)
・・・ #SRCS-y := fwd.c main.c SRCS-y := fwd/fwd.c main.c ・・・
また、各ソースコードから「fwd.h」を共通して参照できるように、コンパイルオプションにインクルードディレクトリを追加します。インクルードディレクトリを指定するコンパイルオプションは"-I"です。"rte.vars.mk"をインクルードしていれば、変数:"RTE_SRCDIR"でソースコードのルートパスが取得できるので、それを利用して"include"へのパスを指定します。
▼ コンパイルオプション(インクルードディレクトリ追加)
・・・ #CFLAGS += -O3 $(WERROR_FLAGS) CFLAGS += -O3 $(WERROR_FLAGS) -I$(RTE_SRCDIR)/include ・・・
後は構成イメージ通りにソースコードとMakefileを配置し、makeすることでDPDKアプリケーションの作成が出来ますので、試してみてください。
今回作成したコンパイル環境について
最後に、今回作成したDPDKアプリケーションのコンパイル環境を公開します。「DPDKアプリケーションのコンパイル」に記載したように動作しますので、試してみてください。(※13)
コンパイル環境(第9回)
コンパイル環境(階層化)(第9回)
※13 サンプルコードご利用の際の注意事項
本ソースコードおよびMakefileはあくまでサンプルです。本ソースコードおよびMakefileを利用して発生した損害について、当社は一切の責任を負いません。
コンパイル環境(第9回)
コンパイル環境(階層化)(第9回)
※13 サンプルコードご利用の際の注意事項
本ソースコードおよびMakefileはあくまでサンプルです。本ソースコードおよびMakefileを利用して発生した損害について、当社は一切の責任を負いません。
おわりに
今回はDPDKアプリケーションの開発環境の構築方法について解説させていただきました。次回はDPDKアプリケーションを開発する際必要不可欠なログ出力機能などを解説する予定です。ご覧いただきありがとうございました。
連載シリーズ
DPDK入門
あわせて読みたい
著者プロフィール
寺尾 智之