自作VMM の virtio-blk 対応

はじめに gokvm開発 1 2 3 4 5 の続き。 前回の virtio-net 対応に引き続いて、virtio-blk に対応した。 virt queueのデータ構造や挙動はそのまま流用できる。 この辺り Virtio はうまく設計されているなと感動する。 7389ff59 カーネルコンパイルオプションの調整 ゲストカーネルからファイルシステムを経由してブロックIOを実現するにあたって、以下のオプションを有効にした。 CONFIG_VIRTIO_BLK=y CONFIG_XFS_FS=y CONFIG_EXT3_FS=y CONFIG_EXT4_FS=y 4f4bbb78 virtio-blkの実装 さて、それでは本題である virtio-blk の実装に移っていく。 virtio-blk の挙動は virtio-net のものとほとんど同じなので、もし前回のブログを読んでいなければ、そちらを先に読むことをお勧めする。 差分はキュー数とdescripterテーブルのエントリが指す先のデータ構造だけである。 virtio-net では送受信のため2つのキューを必要としたが、virtio-blk の場合には 1つのキューで読み書きを実現する。 これはディスクへの読み書きはどちらもOS側からの発行となるため、外部割り込みを受ける必要がないためである。 descripterテーブルエントリが指すデータ構造は、以下のように3つのエントリがLinked Listの要領で繋がっている 6 。 1つ目のエントリが指すデータ構造は blkReq であり、typeフィールドが1なら書き込み、0なら読み込みを意味する。 sectorフィールドがディスクの先頭からのオフセットを意味する。 1セクタは512バイトなので、仮想ディスク用ファイルの sector x 512 バイト目から読み書きすることを意味する。 type blkReq struct { typ uint32 _ uint32 sector uint64 } 2つ目のエントリが実データを指す。ここに実際に読み書きしたいデータをバイナリで格納する。 3つ目のエントリがステータスである。エラーが発生した場合には0以外の数値を書き込む。...

April 12, 2022

自作VMM の virtio-net 対応

はじめに gokvm開発 1 2 3 4 の続き。 最近の一連の開発によって、gokvm 上のVMに virtio-net によって仮想NICを提供することができた。 ネットワーキングのサポートは当初の目標の一つだったので、達成感がある。 この対応によって gokvm 上のVMはホスト(あるいはソフトウェアスイッチを経由して外部)との間で通信できるようになった。 WEBサーバを提供したり、SSHでログインできたり、と出来ることの幅が広がる大きな変更だと思う。 例によって、重要なコミットを抜き出して振り返りたい。 c5217550 Virt Queue データ構造の追加 そもそも Virt Queue とは何なのか。 Virt Queue は ゲスト・ホスト間におけるデータのやり取りに使うリング構造のキューを意味する。 例えば送受信でそれぞれ1つのキューを使うナイーブな virtio-net の場合には、 送受信それぞれ1つの Virt Queue (全体で合わせて2つのVirt Queue)が必要になる。 もちろん、マルチキューをサポートする場合やコントロールキューをサポートする場合には、さらに Virt Queue が必要になる。 1つの Virt Queue は Descripter Table、Avail Ring、Used Ring から構成される。 取り扱いたいデータのアドレスと長さを1つのディスクリプタとしてまとめ、それをテーブル状に並べたものが Descripter Table である。 Avail Ring と Used Ring は似ていて、どちらもディスクリプタのIDをゲスト・ホスト間で 伝え合うために利用される。 方向も決まっていて、Avail Ring がゲストからホスト宛、Used Ring がホストからゲスト宛 となる。 ちなみに virtio 仕様 5 の中では、ゲストをdriver、ホストをdevice と表現している。...

March 18, 2022

自作VMMの PCI デバイス対応

はじめに gokvm開発 1 2 3 の続き。 gokvm 上のVMからPCIデバイスを取り扱えるよう開発を進めてきた。 道のりは長いだろうが、最終的には virtio-net を経由して、VMと外部の間でIP疎通を取りたい。 現時点では virtio-net デバイスをゲストカーネルのネットワークインターフェイスとして認識させることができたので、 ひとまずそこまでのログを残しておく。 やったことを大きく分けると、(1) ゲストのLinuxカーネルに対してvirtio-netデバイスをPCIデバイスとして認識させ、 (2) virtio-netデバイス初期化を完了させることでネットワークインターフェイスとして登録させることの2点。 virt queue上の操作やパケットのやり取りについては、この記事には含まれない。 例によって、コミット単位で実装の経過を残しておく。 fc02176d lspciコマンドの追加 busyboxにはlspciコマンドが同梱されているが、pci.ids ファイル 4 が存在しない。 pci.ids はベンダIDやデバイスIDなどの数値と、それに対応する文字列が組になっているようなファイルである。 このファイルがあれば、人間に読みやすいフォーマットで出力できる。 後々のデバッグをスムーズに進めたいので、対応させておいた。 e126392e PCI Config空間に対するIOエミュレーション カーネルがPCIデバイスを認識するための重要なフェーズ。 PCI Config 空間を読む方法はいくつかあるようだが、ここではタイプ1 5 と呼ばれる方法でアクセスした。 ここで使われるIOポートのアドレスは以下の通り。 0xcf8:アドレスレジスタに対応する。バス番号、デバイス番号、Function番号、PCI Config 空間内のオフセットに対応する。 0xcfc ~ 0xcff:データに対応する。 アドレスレジスタは32bit幅で以下のように解釈される。 位置 内容 Bit 31 Enable Bit Bit 30-24 Reserved Bit 23-16 Bus Number Bit 15-11 Device Number Bit 10-8 Function Number Bit 7-0 Register Offset ざっくり PCI Config 空間のあるオフセットにあるデータを読みたいときは次のような手続きになる。...

January 24, 2022

KVMを使った自作VMMのSMP対応

はじめに gokvm開発 1 2 の近況報告。 これまでは1つの仮想CPUにしか対応していなかった。 マルチCPUのためSMP(Symmetric Multiprocessing)対応させたいと思い立ってから2~3週間くらい試行錯誤し、無事実装することができた。 自分の知る限り KVM でVMMを作ってみたという取り組みを探す中で、 具体的にSMP対応とはどのような実装なのか解説されている資料がなかなか見つからなかった。 稚拙な記事ではあるけれど、今後自作VMMに挑戦する方にこの記事が役に立てば嬉しい。 例によってコミット単位に開発の経過を紹介していく。もちろん実際にはもっともっと泥臭い実装から始めていて、 何度もgit rebaseを繰り返しながら、最終的に解説できるよう粒度を調整したので、コミットのタイムスタンプはあてに出来ない。 #34 vCPUスレッドを複数生成 プルリクエストをいただいた。まずはioctl(fd,KVM_CREATE_VCPU,...)でvCPUを複数生成できるよう変更する。 その後vCPUごとに個別のスレッドを生成して各vCPUごとに独立してioctl(fd,KVM_RUN,...)を発行する。 vCPUはライフタイム全体を通して、同一のスレッドからioctlを発行する必要がある。 Go言語の場合にはスレッドの代わりにgoroutineを使うことが多いので、 runtime.LockOSThread()を呼び出してgoroutineとスレッドを静的に関連づけた。 ce22a91 struct mpf_intel の実装 vCPUがカーネルに認識されるためにはIntel MultiProcessor Specification 3 に従ったデータ構造を認識させる必要がある。 このデータ構造はLinuxカーネルの中で struct mpf_intel のPhysPtrが指す先 struct mpc_table に対応する。 コード 4 を読むと、チェックサム・バーション・マジックナンバーを読み取ることができたので、 仕様書は斜め読みしかしていない。 このデータ構造はどこに配置すれば良いのか。仕様書を読むとExtended BIOS Data Area (EBDA)の最初の1KB以内とあるのでそこに配置することにした。 EBDAは典型的に 0x0009FC00 に置かれる 5 ようなので、それに倣った。 a. In the first kilobyte of Extended BIOS Data Area (EBDA), or b. Within the last kilobyte of system base memory (e....

November 25, 2021

Understanding Linux Network Internal 1~2部 読書メモ

このブログではネットワークに関する比較的新しい技術について触れてきたが、たまには古きを温めるのも良いだろうということで読んでみた。Linuxカーネルは今後も長きにわたって使われるはずで、カンペキな理解でなくとも、取っ掛かりだけでも掴んでいる意味は大きいと思う。この本は1,000頁超えで、1~7部から構成されているので、一気に読むのはモチベーション維持が難しいと思う。この記事ではとりあえず現時点で読んだところまでをまとめたい。カーネルバージョン 2.6.39 のソースコードを手元に置いて、読み進めていった。ビルド方法などは前回の記事 1 のとおり。 1部 ネットワークに関する重要なデータ構造として struct sk_buff と struct net_device がある。まずはこの2つのデータ構造を掴むことが肝要だと思う。struct sk_buff は(フラグメンテーション云々の話を抜きにすると)1つのパケットに対応する。しばしばそのインスタンスは skb という名前が付けられる。skb->data が処理を担当しているネットワークレイヤのヘッダを指している。例えば、L2の処理を行っている際にはskb->data はL2ヘッダ の先頭を指している。処理の進行に伴って、このポインタは移動していく。実データの前後に余白が設けられている。 +------------+ skb->mac skb->nh | | | | | head-----------> +------------+ | | | | | headroom | v v | data-----------> +------------+ +---------+---------+---------+--- | | | | | L2 | L3 | L4 | | tail | | Data | | header | header | header | ... | | | | | +---------+---------+---------+--- | end | | | | ^ ^ | | +----------> +------------+ | | | | | | tailroom | | | | +-------------> +------------+ +---------+ | | skb->data +------------+ struct sk_buff この構造体にどんなメンバがいるか見ていく。users が参照カウンタに対応していて、sk_getやkfree_skbで操作できる。mac_header など各レイヤに対応するポインタもある。cbはコントロールバッファの略で、48バイトの領域を各レイヤの中でプライベート(他のレイヤを意識せず)に使える。struct sk_buffは双方向リストで管理されていて、リスト全体は struct sk_buff_head に対応する。デバッガを使って、中身を見ていく。送信を担当する関数にアタッチしてみると、struct sk_buff内部に保持されたIPヘッダ の中身を見ることができる。...

August 26, 2021

BusyboxベースのミニマルなLinux環境を作りQEMUで起動

すでに多くの方が似たような取り組みを行っていてブログ記事 1 2 3 として丁寧にまとめられているように、 やはりこういった環境を手元にさっと作れることの意味は大きいと思う。 ここではざっくりとした仕組みを記録しておく。 成果物をスクリプトとしてまとめGithubにあげている。 特徴 CentOS6、CentOS7、Ubuntu20.04などメジャーなディストリビューション向けカーネルのビルドに対応しているので、実務よりの応用ができる Busyboxを使ってユーザランドをメモリ上に展開するので、起動のたびにピュアでミニマルな環境を作れる SSHログインや外部ネットワーク疎通が可能なので、他システムとの連携が絡む動作を検証しやすい GDBを使ったデバッグによってカーネル内部のデータを参照できる 現時点ではx86/64のみに対応している カーネルのビルド カーネルのビルドは端的に言えば、ビルド設定を.config ファイルに記述し、makeコマンドを叩くことに対応する。.config はテキストファイルなので適用なエディタでも編集できるが、専用のコマンド(make oldconfig、make defconfig、make menuconfig など)が用意されているので、それを使うことが多い。カーネルはアップストリームのものと各ディストリビューションが手を加えたものがあるがここでは以下の全てのカーネルをビルドできるよう環境を整えた。 upstream (kernel v2.6.39) centos6 (kernel v2.6.32-754.35.1.el6) centos7 (kernel v3.10.0-1160.13.1.el7) ubuntu20.04 (kernel v5.4.0-65.73) 最近のGCCで古いカーネルをコンパイルするのは難儀なので、カーネルバージョンごとにビルド専用Dockerイメージを用意した。例えばカーネルv2.6.39はCentOS6のビルド環境を使ってビルドすることにした。 FROMghcr.io/buddying-inc/centos68:latestRUN sed -i "s|#baseurl=|baseurl=|g" /etc/yum.repos.d/CentOS-Base.repo \ && sed -i "s|mirrorlist=|#mirrorlist=|g" /etc/yum.repos.d/CentOS-Base.repo \ && sed -i "s|http://mirror\.centos\.org/centos/\$releasever|https://vault\.centos\.org/6.10|g" /etc/yum.repos.d/CentOS-Base.repoRUN yum install -y gcc perl glibc-static kernel kernel-devel \ autoconf zlib-devel zlib-static openssl-static openssl-devel上のDockerfileをもとに buildenv-v2....

July 7, 2021

MininetでFRR(BGP Unnumbered)を動かす

https://github.com/bobuhiro11/mininetlab の紹介。 Mininet を使うと単一のマシン上でいくつかのスイッチとホストを動作させることができる。 これを使って仮想的にホストを2つ立ち上げ、その間をFRRパッケージに含まれるBGP(Unnumbered)で接続してみる。 Mininetでは以下のようにPythonでトポロジや各ホストにおけるコマンド実行について記述できる。 一見複雑にみえるFRRもdaemons、vtysh.conf、frr.confの3つのファイルを正しく配置しておけば、 frrinit.sh startで簡単に起動できる。 ホストごとにnetnsは分割されているが、mountns は分割されていない(要確認)ため、 /etc/frrや/var/run/frrが2つのホスト間で衝突してしまいFRRが正常に起動できなかった。 これはprivateDirs = ['/etc/frr', '/var/run/frr'] によって回避できる。 #!/usr/bin/env python from mininet.net import Mininet from mininet.log import setLogLevel import time frr_conf = ''' hostname {name}password zebra ! router bgp {asnum}bgp router-id {router_id}bgp bestpath as-path multipath-relax neighbor h1-eth0 interface remote-as external neighbor h2-eth0 interface remote-as external address-family ipv4 unicast network {router_id}/32 network {network}exit-address-family ! line vty ! end ''' vtysh_conf = ''' service integrated-vtysh-config ''' daemons = ''' bgpd=yes vtysh_enable=yes zebra_options=" -A 127....

May 8, 2021

KVMを使ったVMMを自作してLinuxを起動するまでの記録 2

2021/2/24 WSL2 サポート 4f6b785 WSL2(Windows Subsystem for Linux 2)のUbuntu 20.04で gokvm を実行すると、 IOポート 0x64 への出力が無限に繰り返され、Initプロセスの起動まで到達しなかった。 どうやら PS/2 キーボード周りの挙動が原因のようだ。 kvmtool では in (0x61) に対して 0x20 を返している 1 のでそれを踏襲する形で対応した。 IOポート 0x61は NMI (Non-Maskable Interrupt)のステータスとコントロールレジスタとして使われているようだ 2。 このステータスレジスタの内容を調べると、bit 5はmirrors timer 2 output condition を意味するが、 これ以上は解釈できず。 理解できていない部分はあるが、結果として WSL2 での ゲストVMの起動もできるようになった。 0061 r KB controller port B control register (ISA, EISA) system control port for compatibility with 8255 bit 7 parity check occurred bit 6 channel check occurred bit 5 mirrors timer 2 output condition bit 4 toggles with each refresh request bit 3 channel check status bit 2 parity check status bit 1 speaker data status bit 0 timer 2 gate to speaker status 出典:XT, AT and PS/2 I/O port addresses...

March 3, 2021

KVMを使ったVMMを自作してLinuxを起動するまでの記録

はじめに KVMを利用したナイーブで実験的なVMMを作ってみた。 ioctl で /dev/kvm を叩いて仮想マシンを作成し、その上でLinux Kernelとユーザプロセスを起動できる。 Kernelのデバイスドライバから認識できる程度の非常に簡素なシリアルコンソールのエミュレーションも実装したので、 ログインシェルから操作ができる。 一方で、ネットワーキングやディスクについては現時点ではまだサポートしていない。 最近はKVMを従来のような仮想マシンとしての使い方だけでなく、 マルチテナントなクラウド環境において分離レベルを強化するために、 Google gVisor 1 や Kata Containers 2、 Amazon Firecracker 3 をはじめとした コンテナやマイクロVMでの使い方が登場してきた。 今回作ったgokvmは標準ライブラリのみを使いGo言語で実装したもので、 全体で1,500行程度(ブログ記事作成時点)なので、 自分と同じようにKVMやLinuxのブートプロセスに興味のある方にはとっかかりとして役立つかなと思う。 コミットログを見ながら、何をどう実装したのかについて振り返ってみる。 2021/1/30 プロジェクト始動 632c6e0 最初のコミット。README.md、.gitignore、LICENSEファイルを配置しただけで、 特に特筆することはない。似たようなプロジェクト 4 5 や LWN.net の記事 6 を調べていた。 ミニマムな実装で Linux ユーザランドまでブートさせるようなものは見当たらなかった。 ざっと調べただけなので調査漏れがあるかも。 もともとはkvmtool 4 がその立ち位置だったのかもしれないが、 ちょっとコードが巨大に感じた。kvm-host.c 5 は250行程度のCのコードでkernelのブートができるが、 ユーザランドまでは到達できていないようだ。 2021/2/4 bzImage・initrdのビルドとKVMのラッパー実装 69e3ebb 動作確認用のbzImageとinitrdを make コマンドから生成できるようにした。 bzImageはLinux Kernel本体、initrd はメモリ上の一時的なファイルシステムに対応する。 Linux Kernel バージョンはプロジェクト開始時点で最新の 5.10 を使った。 make tinyconfig を実施したのち、make menuconfig を使って追加で必要なconfigを有効にした。 initrdは、Busyboxをベースとした。 Linux KernelとBusyboxの ....

February 18, 2021

SRv6のLinux Kernel実装

SRv6とは SRv6はIPv6拡張の一つでSource Routingを実現するもの。Source Routingは、データ送信者がその宛先だけでなく、経路についても指定することを意味する。 経由するノードをSID(Segment Identifier)によって識別し、そのリストをパケットヘッダに含めることで、経路を自由に制御できる。SRv6では、IPv6アドレスがSIDに対応する。 SRv6は、EITF(Internet Engineering Task Force)を中心に仕様の策定が進められている 1 。 2020年3月にはRFC8754 2 として公開された。 SRv6で使われるIPv6ヘッダのSRH(Segment Routing Header)について詳しく見ていく。 まず、Routing TypeはSegment Routingではマジックナンバー4になる。 Segment List[0] ~ Segment list[n] のエントリに、最後のセグメントから降順に経由させたいセグメント一覧を列挙していく。 次のセグメントへの番号をSegments Left、最後のSegmentの番号をLast Entryに格納する。 セキュリティ機構であるHMACなど付加情報がある場合には、TLV(Type Length Value)として追加する。 Routing headers are defined in [RFC8200]. The Segment Routing Header (SRH) has a new Routing Type (4). The SRH is defined as follows: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Next Header | Hdr Ext Len | Routing Type | Segments Left | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Last Entry | Flags | Tag | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | Segment List[0] (128-bit IPv6 address) | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | ....

January 17, 2021

QEMU/KVM on WSL2 のログ

Windows10 WSL2上のゲストから/dev/kvmを経由して、その上にさらにネストさせる形で仮想マシンを動かすことができた。 環境 Windows 10 Pro Insider Program (Devチャネル、OSビルド 20246.1) 1 WSL2上のゲスト Ubuntu 20.04.1 LTS (Focal Fossa) Linux 4.19.128-microsoft-standard カーネルパラメータ initrd=\initrd.img panic=-1 nr_cpus=4 swiotlb=force pty.legacy_count=0 QEMU emulator version 4.2.1 (Debian 1:4.2-3ubuntu6.7) Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz 手順 WSL2(Windows Subsystem for Linux 2)全体の設定 C:\Users\ユーザ名\.wslconfig に以下の設定を追加することで、 仮想化支援のネストを許可する。 [wsl2] nestedVirtualization=true 参考にしたブログ記事 2 では、WSL2上のゲストカーネルを再ビルドするような手順が紹介されているが、今回は必要なかった。 単純に nestedVirtualization=true とするだけで対応できた。 動作確認 Part.1 QEMU/KVM上でcirrosイメージを動かしてみる。 # non-root ユーザからも KVM の利用を許可 $ sudo chmod a+rw /dev/kvm $ kvm-ok INFO: /dev/kvm exists KVM acceleration can be used # cloud-init を無効化した cirros イメージを取得 $ wget https://github....

November 4, 2020

vDPA(Virtio Data Path Acceleration)メモ

仮想マシンやコンテナ環境で、高パフォーマンス(NICワイヤレート)かつ柔軟なIOを実現する方法。 まだあんまり日本語の情報は見つからない。 触ったわけではないので、勘違いなどあるかも。 vDPAカーネルフレームワーク 2020年3月に、vDPA カーネルフレームワークがLinux 5.7にマージされた。 vDPAカーネルフレームワークが扱うvDPA デバイスとは、 データプレーンがvirtio仕様、コントロールプレーンがベンダ仕様であるデバイスを指す。 ゲスト上のvirtio-netデバイスから見ると、データプレーンがホストをバイパスして物理NICに直接アクセスし、 コントロールプレーンがホストのvDPAフレームワーク(とベンダ依存のドライバ)を経由する、と捉えることができる。 かつてvDPAカーネルフレームワークは、mdevベースで作られた。 mdevはVFIOパススルー時にコントロールプレーンを仲介するもので、 ベンダ依存のコマンドとエミュレートされたPCIデバイス間の変換を担当する。 ただ以下の理由でmdevベースのアプローチを辞め、VFIOから独立した新しいサブシステムとしてvDPAカーネルフレームワークを設計した。 VFIOとmdevは、vDPAと比べるとレイヤの低い部分で抽象化を行っているため、レイヤの高いAPIをVFIOに取り入れるのは自然ではない 世の中のNICがすべてVFIO IOMMUの設計に適しているわけではない vDPAカーネルフレームワークはvhostキャラクタデバイスを提供するので、 QEMUのようなユーザスペースのドライバからvhostデバイスとして扱える。 独立したサブシステムなので、新たなハードウェアの機能に追従できる。 例えば、ライブマイグレーションのために、vhost API経由でデバイスの状態を保存・復元することができる。 また、SVA(Shared Virtual Address)やPASID(Process Address Space ID)もサポートする。 QEMUのようなユーザスペースでホスト・ゲストを仲介するレイヤが必要になるが、 SR-IOVによって単純にパススルーする構成と比べると、攻撃の対象領域を小さく保てる。 Intel Scalable IOVやSub Function(SF)とも相性が良い。 vDPA DPDK フレームワーク vDPAのもう一つのフレームワークとして、ホストのDPDKを使った vDPA DPDK framework も存在する。 QEMUから見ると、単純に vhost-user バックエンドと接続するだけで良い。 ほとんどのメジャーなNICに対して、このvDPA DPDK driverが存在している。 ただvDPA DPDK フレームワークには以下の制約があった。 vhost-userはユーザスペースのAPIなので、ホストのカーネルサブシステムを操作することができない。例えば、eBPFのような機能と協調して動かすことができない。 DPDKはデータプレーンに注力しているため、ハードウェアの操作するためのツールを提供していない。 これらの制約を取り除くために、vDPAカーネルフレームワークが必要だった。 How deep does the vDPA rabbit hole go?, redhat.com にさらに突っ込んだ内容が書かれている。...

October 27, 2020

XDPメモ(アーキテクチャ、性能、ユースケース)

はじめに The eXpress data path: fast programmable packet processing in the operating system kernel 1 を読んだ。 この文章はほとんどこの論文をもとに書いたが、一部ニュース記事を引用している。 eBPF/XDPが流行っているということは、BCC、bpftrace、Facebook Katran、Cloudflare Gatebot などeBPF/XDPを使うプロジェクトのGithub Star数から感じ取れる。 eBPF/XDPには、特殊なハードウェア・ソフトウェアに依存せず、 カーネルの仕掛けとして高速パケット処理を実現できるという強力なメリットがある。 一方であまり弱点を主張するような記事は見当たらないので、実際のところどうなのか感触を知りたい。 XDPを使うとNICデバイスドライバのコンテキストで、eBPF Verifilerの制約はありつつも、 比較的自由にパケット処理を実現できる。 また、成熟したLinuxのネットワークスタックと共存しつつ、1コアで24Mppsという高速なパケット処理を実現できる。 XDPプログラムは、eBPFの制約のもとC言語で記述することができ、clangでELFバイナリにコンパイルする。 XDPの競合としてカーネルバイパスなDPDKがある。両者の特徴は以下のとおり。 DPDK: カーネルバイパスによってコンテキストスイッチを避け高速化を図る コアを専有する(特定のコアでCPU 100%に張り付かせてポーリング) スループットとレイテンシどちらの観点で見てもDPDKのほうが優れている ネットワークスタックを再実装する必要がある XDP: 専用コアが不要(電力面などで有利) 容易にロード・アンロードできる 名前空間などカーネルの機能に強く依存するコンテナ環境の普及で、XDPの重要度が増しているように感じる やはりカーネルネットワークスタックと共存できるというメリットが強い アーキテクチャ パケット着信のたびに、デバイスドライバのフックポイントでXDPプログラムが実行される。 このフックポイントは、デバイスドライバの処理の中でも初期に位置する(sk_buff の割り当て前)。 XDPプログラムは、ヘッダのパース、eBPFマップの読み書き、ヘルパ関数(FIBのルックアップなど)の呼び出しを経て、 最終的にパケットをどこに送り出すか決定する。 送り先については、XDPプログラムの終了コードで制御することができ、 ドロップさせる、同インターフェイスに送り返す、他インターフェイスに転送する、AF_XDPとしてユーザプログラムに送る、 通常のネットワークスタックに送る、から選択する。 2つ以上のXDPプログラムを同インターフェイスに紐付けたい場合には tail call として処理を引き渡すことができる。 外部のシステムとデータのやり取りをするために、eBPF Mapが用意されている。 eBPF Verifilerが厳しくチェックしているので、必ずeBPF Mapを使うことになりそうだ。 出典:The eXpress data path: fast programmable packet processing in the operating system kernel...

September 17, 2020

KVM TLB Shootdown Preemption メモ

KVM FORUM 2018 で発表されたTowards a more Scalable KVM Hypervisorについてのメモ。 [PATCH v8 0/4] KVM: X86: Add Paravirt TLB Shootdown ゲストOSはOSレベルの同期機構であるTLB shoot downやRCUの処理において、ホストOSのスケジューラの影響を受ける。ベアメタル環境であればすぐに完了する操作であっても、仮想環境上ではその遅延を無視することができなくなる。 TLB(Translatoin Lookaside buffer)は仮想メモリアドレスと物理メモリアドレスのマッピングをキャッシュするために使われる。あるCPUがそのマッピングを切り替えたとき、その以外のCPUでTLBをflushしなかればならない。これを TLB shoot down と呼ぶ。 モダンなOSでは、TLB shoot down はパフォーマンスクリティカルな箇所として位置づけされ、この処理によって遅延が発生しないようチューニングされている。TLBのflushはすぐさま完了するという前提で、IPI(Inter Processor Interrupt)で実装されている。Remote CPUのTLB flushの完了は busy wait で待っているため、ベアメタル環境とは相性が良い。一方で、仮想環境ではvCPUが別のゲストに横取りされたり、実行がブロックされたり、ということが起きるため、長時間にわたって busy wait が継続することになる。 この問題は、準仮想化TLB shoot down によって解決できる。準仮想化TLB shoot down では、動作していないvCPUに対する操作を遅延させておいて、次回そのvCPUが起動するときに、KVMがTLB flushを担当する。特にオーバコミットされた環境で顕著なパフォーマンス向上が見られる。 ゲストとホストどちらからも参照できるメモリ領域に「あるvCPUが preempt されたかどうか」を示すフラグを用意しておく。pv_mmu_ops.flush_tlb_others関数は、アクティブなvCPUに対してはIPI経由でTLB Flushの通知を送り、そうでないvCPUに対してはKVM_VCPU_FLUSH_TLBフラグを付与しておく。その後、KVMはKVM_VCPU_FLUSH_TLBフラグがついているvCPUを起動する際に、INVVPIDを発行する。VM数が増えるに従ってこのチューニングの効果が顕著に現れる。

July 1, 2020

Linux Observability with BPF 読書メモ

Linux Observability with BPFを読んだので、メモしておく。 今月、Brendan Gregg氏のBPF本も出るので、 それも読みたいな。 BPFを使うことで、カーネルのイベントをフックして安全にコードを実行できる そのコードがシステムを破壊したりクラッシュさせたりすることが無いよう、BPF側で検証してくれる カーネルモジュールと異なり、BPFプログラムはカーネル再コンパイルの必要がない BPFコードが検証された後、BPFバイトコードは機械命令にJITされる BPFプログラムは、bpf syscall によってBPF VMへとロードされる 2014年前半にAlexei Starovoitov氏がeBPFを導入した 過去のBPFでは2つの32bit registerのみが使えたが、eBPFでは10個の64bit registerまで使える 2014年6月にはeBPFはユーザスペースにも拡張された BPFプログラムタイプ:トレーシングとネットワーキングに大きく分類できる Socket Filter:カーネルに最初に入ったプログラムタイプ。観測目的のみ Kprobe:kprobe ハンドラとしてBPFプログラムを動かす。関数に入るときと出る時が、それぞれSEC(kprobe/sys_exec)とSEC(kretprobe/sys_exec)に対応する Tracepoint:事前にカーネル側で定義されたtracepointに対してBPFプログラムを動かす。/sys/kernel/debug/tracing/eventsで一覧を確認できる XDP:パケット着信時に、早い段階でBPFプログラムを動かす。 Perf:perf eventに対してBPFプログラムを動かす。perfがデータを吐き出す度にBPFプログラムが実行される Cgroup Socket:そのcgroup内のすべてのプロセスにアタッチされる Socket Option:Facebookはデータセンター内の接続において、RTOs(recovery time objectives)の制御に、これを使っている。 Socket Map:BPFでロードバランサを実装するときに使う。CilliumやFacebook Katranはこれを使っている。 BFP Vefirier CVE-2017-16995 のように、過去BFPをバイパスしてカーネルメモリをバイパスできる脆弱性があった DFSで、そのプログラムが終了すること、危険なコードパスが存在しないことを検証する 無限ループを弾くために、すべてのループを禁止している。ループ許可については、本書を書いている時点では、まだ提案の段階。 命令数は4096に制限されている bpf syscall の log_*を設定すれば、検証結果を確認できる BPFプログラムは tail calls によって、他のBPFプログラムを呼び出すことができる 呼び出し時にすべてのコンテキストは失われるので、何らかの方法で情報を共有する必要がある BPFマップ 直接bpf syscall でBPFマップを作成することができる bpf_map_createヘルパ関数を使うのが簡単 bpf_map_update_elem のシグネチャは、カーネル側bpf/bpf_helpers....

December 14, 2019

TP-Link Archer T3U AC1300をLinuxから使う

USB接続の無線LAN子機 TP-Link Archer T3U AC1300 を、Thinkpad X1 Carbon 2015(Fedora release 29 (Twenty Nine)、linux 5.3.6-100.fc29.x86_64)から使えるようにしたので手順を残しておく。 # 公式HPではドライバが配布されていなかったので rtl88x2bu を使った git clone https://github.com/cilynx/rtl88x2bu cd rtl88x2bu/ git rev-parse HEAD # 962cd6b1660d3dae996f0bde545f641372c28e12 VER=$(sed -n 's/\PACKAGE_VERSION="\(.*\)"/\1/p' dkms.conf) sudo rsync -rvhP ./ /usr/src/rtl88x2bu-${VER} # /usr/src/<module_name>_<module_version> に移動させて dkms で管理する sudo dkms add -m rtl88x2bu -v ${VER} sudo dkms build -m rtl88x2bu -v ${VER} sudo dkms install -m rtl88x2bu -v ${VER} sudo dkms status sudo modprobe 88x2bu 2種類の子機(オンボードの子機とTP-Linkの子機)を同一のネットワークにつなぐと、metric によって優先順位が決まる。 metric の値を見ると、オンボードの子機側を優先していた(値が小さい)ので入れ替えた。...

November 9, 2019