Closed54

アドベントカレンダー2021ネタ調査

ozaki-rozaki-r

AF_XDPネタということで、2つの動作モードについて調べる。

  • XDP_FLAGS_SKB_MODE vs. XDP_FLAGS_DRV_MODE
  • XDP_ZEROCOPY vs. XDP_COPY
ozaki-rozaki-r
  • XDP_FLAGS_SKB_MODEはカーネルに渡す用でカーネル内ではXDP_MODE_SKBを参照している
  • それでもnet/core/dev.cでしか見てなさそう
  • dev_xdp_bpf_opでSKBだとgeneric_xdp_installを返して、そうでないとdev->netdev_ops->ndo_bpfを返している
ozaki-rozaki-r
static int generic_xdp_install(struct net_device *dev, struct netdev_bpf *xdp)                                                                                            
{                                                                                                                                                                         
        struct bpf_prog *old = rtnl_dereference(dev->xdp_prog);                                                                                                           
        struct bpf_prog *new = xdp->prog;                                                                                                                                 
        int ret = 0;                                                                                                                                                      
                                                                                                                                                                          
        switch (xdp->command) {                                                                                                                                           
        case XDP_SETUP_PROG:                                                                                                                                              
                rcu_assign_pointer(dev->xdp_prog, new);                                                                                                                   
                if (old)                                                                                                                                                  
                        bpf_prog_put(old);                                                                                                                                
                                                                                                                                                                          
                if (old && !new) {                                                                                                                                        
                        static_branch_dec(&generic_xdp_needed_key);                                                                                                       
                } else if (new && !old) {                                                                                                                                 
                        static_branch_inc(&generic_xdp_needed_key);                                                                                                       
                        dev_disable_lro(dev);                                                                                                                             
                        dev_disable_gro_hw(dev);                                                                                                                          
                }                                                                                                                                                         
                break;                                                                                                                                                    
                                                                                                                                                                          
        default:                                                                                                                                                          
                ret = -EINVAL;                                                                                                                                            
                break;                                                                                                                                                    
        }                                                                                                                                                                 
                                                                                                                                                                          
        return ret;                                                                                                                                                       
}

generic_xdp_installはインタフェースにBPFプログラムを設定している模様。

LROとGRO HW?を無効にしてるのが気になる。

ozaki-rozaki-r

脱線だがstatic_branch_incの話。

static-keys のAPIらしい。

APIにstatic_branch_likely, static_branch_unlikelyというのがあったので、分岐予測の話かと思ったが、そうではないらしい。

likely/unlikelyを使う場合でも、何らかのフラグ変数を読む必要があってそのコストも場合によってはばかにならないので、それも避けるためのもの。技術的にはコードパッチングを使っている。デフォルトでno-op命令を置いて、逆側に分岐させたくなったら動的にジャンプ命令に書き換える。no-opは通常は無視されるのでデフォルト状態だとコストが0になる。逆側に分岐させる場合は、ジャンプ命令なので分岐予測に失敗した場合よりはコストが大きそうだが、必要経費だと思われる。

ozaki-rozaki-r

generic_xdp_installはdev_xdp_installで呼ばれる。dev_xdp_installはそれほど面白いことはやっていない。

dev->netdev_ops->ndo_bpfでは何が実行されるのだろう?多少知っているixgbeのコードを読んでみる。

static int ixgbe_xdp(struct net_device *dev, struct netdev_bpf *xdp)
{
        struct ixgbe_adapter *adapter = netdev_priv(dev);

        switch (xdp->command) {
        case XDP_SETUP_PROG:
                return ixgbe_xdp_setup(dev, xdp->prog);
        case XDP_SETUP_XSK_POOL:
                return ixgbe_xsk_pool_setup(adapter, xdp->xsk.pool,
                                            xdp->xsk.queue_id);

        default:
                return -EINVAL;
        }
}

という感じでixgbe_xdp_setupを呼んで、固有の初期化をするのだろう。

逆にSKB_MODEだとそういう初期化すらないのかな?

ozaki-rozaki-r

ndo_bpfはAF_XDP専用なのだろうか?通常のXDPでも使われるのだろうか?

 * int (*ndo_bpf)(struct net_device *dev, struct netdev_bpf *bpf);
 *      This function is used to set or query state related to XDP on the
 *      netdevice and manage BPF offload. See definition of
 *      enum bpf_netdev_command for details.

AF_XDP専用というわけでもなさそう。

ozaki-rozaki-r

ixgbe_xdp_setupは、BPFが使える状態かチェックして、adapter->xdp_progにBPFプログラムを設定している。

最後にAF_XDP関連のことをやっている。

	/* Kick start the NAPI context if there is an AF_XDP socket open
	 * on that queue id. This so that receiving will start.
	 */
	if (need_reset && prog) {
		num_queues = min_t(int, adapter->num_rx_queues,
				   adapter->num_xdp_queues);
		for (i = 0; i < num_queues; i++)
			if (adapter->xdp_ring[i]->xsk_pool)
				(void)ixgbe_xsk_wakeup(adapter->netdev, i,
						       XDP_WAKEUP_RX);
	}

カーネル内ではAF_XDPはxskと省略して呼ばれているらしい。

AF_XDPのソケットが設定されていれば受信処理を起動する。(NAPIコンテキストって何だっけ?) 前に読んだAF_XDPの初期化では、BPFプログラムのロードは最後だったはずなので、AF_XDPのソケットがすでに設定されているのは正しそう。

ozaki-rozaki-r

struct ixgbe_adapterのBPF/XDP関連のメンバ変数。

struct ixgbe_adapter {

	struct bpf_prog *xdp_prog;

	/* XDP */
	int num_xdp_queues;
	struct ixgbe_ring *xdp_ring[MAX_XDP_QUEUES];
	unsigned long *af_xdp_zc_qps; /* tracks AF_XDP ZC enabled rings */

};
struct ixgbe_ring {

	struct bpf_prog *xdp_prog;

	struct xdp_rxq_info xdp_rxq;
	struct xsk_buff_pool *xsk_pool;

};

Tx, Rxリングの他にXDPリングを持ってるのか。

ozaki-rozaki-r

ixgbe_xsk_wakeupは何をやっている?

色々フラグ等をチェックした後、ixgbe_irq_rearm_queuesを呼んでいる。

        if (!napi_if_scheduled_mark_missed(&ring->q_vector->napi)) {
                u64 eics = BIT_ULL(ring->q_vector->v_idx);

                ixgbe_irq_rearm_queues(adapter, eics);
        }

ixgbe_irq_rearm_queuesはNICのレジスタに書き込みをしているのだが、別の場所の呼び出し側にCause software interrupt to ensure rings are cleanedと書いてあったので、強制的に受信割り込みを起こしていると思われる。書き込んでいるビットの割り込み要因の説明がRTx Queue Interruptだし。

追記:RTxなので送受信割り込みだね。

ozaki-rozaki-r

ixgbe_xsk.cというファイルがあるのでこの中は全部見ておいたほうが良いかもしれない。

static int ixgbe_xsk_pool_enable(struct ixgbe_adapter *adapter,
static int ixgbe_xsk_pool_disable(struct ixgbe_adapter *adapter, u16 qid)
int ixgbe_xsk_pool_setup(struct ixgbe_adapter *adapter,
static int ixgbe_run_xdp_zc(struct ixgbe_adapter *adapter,
bool ixgbe_alloc_rx_buffers_zc(struct ixgbe_ring *rx_ring, u16 count)
static struct sk_buff *ixgbe_construct_skb_zc(struct ixgbe_ring *rx_ring,
static void ixgbe_inc_ntc(struct ixgbe_ring *rx_ring)
int ixgbe_clean_rx_irq_zc(struct ixgbe_q_vector *q_vector,
void ixgbe_xsk_clean_rx_ring(struct ixgbe_ring *rx_ring)
static bool ixgbe_xmit_zc(struct ixgbe_ring *xdp_ring, unsigned int budget)
static void ixgbe_clean_xdp_tx_buffer(struct ixgbe_ring *tx_ring,
bool ixgbe_clean_xdp_tx_irq(struct ixgbe_q_vector *q_vector,
int ixgbe_xsk_wakeup(struct net_device *dev, u32 qid, u32 flags)
void ixgbe_xsk_clean_tx_ring(struct ixgbe_ring *tx_ring)

zcはzero copyぽい。

ozaki-rozaki-r

結局AF_XDPが設定されたかどうかのフラグがなかったけど、ixgbe_main.cを見るとxsk_poolが設定されているかどうかで動作を変えているので、xsk_poolがフラグ代わりになっている?

xsk_poolはでixgbe_configure_tx_ringで設定されている。

        ring->xsk_pool = NULL;
        if (ring_is_xdp(ring))
                ring->xsk_pool = ixgbe_xsk_pool(adapter, ring);

ring_is_xdpというマクロは__IXGBE_TX_XDP_RINGが立っているかチェックしてる。これはどこで設定されているんだろう...?

ixgbe_xsk_poolはadapter->af_xdp_zc_qpsに対応するキューのIDが立っていなければNULLを返している。キューのIDはixgbe_xsk_pool_enableでセットされる。ixgbe_xsk_pool_enableはixgbe_xsk_pool_setupから呼ばれる。ixgbe_xsk_pool_setupはixgbe_xdp(XDP_SETUP_XSK_POOL)から呼ばれる。

ixgbe_xdp(XDP_SETUP_XSK_POOL)はいつ呼ばれるんだろう?

ozaki-rozaki-r

ixgbe_xdp(XDP_SETUP_XSK_POOL)はxp_assign_devから呼ばれている。xp_assign_devはnet/xdp/xsk_buff_pool.cに定義されている。xp_assign_devはxsk_bindから呼ばれる。

ここでいうbindはおそらくbind(2)のことで、AF_XDPソケットを作るAPI(xsk_socket__create)から呼ばれていたはず。umem(ユーザ・カーネル間共有メモリ領域)をインタフェース&キューに設定するときにbindが使われていたような気がする。

ixgbe_configure_tx_ringがAF_XDPソケットを初期化するときの値を参照しているということは、AF_XDPソケットを使うときにドライバはリセットされるのかな...?

ozaki-rozaki-r

struct net_device_opsにあるXDP関連の関数

        .ndo_bpf                = ixgbe_xdp,
        .ndo_xdp_xmit           = ixgbe_xdp_xmit,
        .ndo_xsk_wakeup         = ixgbe_xsk_wakeup,
ozaki-rozaki-r
  • ixgbe_clean_xdp_tx_irq
    • 対となるixgbe_clean_tx_irqの説明通りReclaim resources after transmit completesなのかな
    • 送信完了したパケットがあればxsk_tx_completed呼んでcompletion ringを進めている
    • xsk_uses_need_wakeup()が真ならxsk_set_tx_need_wakeup()を呼ぶ
      • 後で調べる
    • 最終的にixgbe_xmit_zcを呼んでいる
      • ixgbe_xmit_zcを呼んでる箇所はここのみ
    • ちなみにixgbe_pollから呼ばれる
  • ixgbe_xmit_zc
    • budget数分(xskの)tx ringにあるパケットを送信する
    • 1つでも送信したら、ixgbe_xdp_ring_update_tailz()とxsk_tx_release()を呼ぶ
      • xsk_tx_releaseはtx ringを進めている?
  • ixgbe_clean_rx_irq_zc
    • txと同様にやってることはClean completed descriptors from Rx ring - bounce bufらしい
    • ixgbe_pollから呼ばれ、最終的にixgbe_rx_skbを呼んでパケットをネットワークスタックに渡す
ozaki-rozaki-r

tx, rx共に割と普通のパケット送受信処理をしているように見える。むしろSKB_MODE (generic)な方を見た方が良いのかも。

ozaki-rozaki-r

送信処理

ちょっと古いが 送受信 - Linuxカーネルメモ を参考にコードを読むと、tx用のsoftirqを起動するまでは以下のようなコールパスになるようだ。プロトコルスタックからは最初にdev_queue_xmitが呼ばれるらしい(ほんとか?)。

  • dev_queue_xmit
  • __dev_queue_xmit
  • __dev_xmit_skb
  • __qdisc_run
  • (qdisc_restart)
  • __netif_schedule
  • __netif_reschedule
  • raise_softirq_irqoff(NET_TX_SOFTIRQ);
ozaki-rozaki-r

IPv4パケット送信処理の最後の関数(__ip_local_out)は、最後にnf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, ...)を呼んでいてその先がよくわからない...

ozaki-rozaki-r

重要なデータ構造。sendmsgやpollはここにある。

static const struct proto_ops xsk_proto_ops = {
        .family         = PF_XDP,
        .owner          = THIS_MODULE,
        .release        = xsk_release,
        .bind           = xsk_bind,
        .connect        = sock_no_connect,
        .socketpair     = sock_no_socketpair,
        .accept         = sock_no_accept,
        .getname        = sock_no_getname,
        .poll           = xsk_poll,
        .ioctl          = sock_no_ioctl,
        .listen         = sock_no_listen,
        .shutdown       = sock_no_shutdown,
        .setsockopt     = xsk_setsockopt,
        .getsockopt     = xsk_getsockopt,
        .sendmsg        = xsk_sendmsg,
        .recvmsg        = xsk_recvmsg,
        .mmap           = xsk_mmap,
        .sendpage       = sock_no_sendpage,
};
ozaki-rozaki-r
static int xsk_sendmsg(struct socket *sock, struct msghdr *m, size_t total_len)
{
// snip
        if (pool->cached_need_wakeup & XDP_WAKEUP_TX)
                return __xsk_sendmsg(sk);
        return 0;
}

static int __xsk_sendmsg(struct sock *sk)
{
// snip
        return xs->zc ? xsk_zc_xmit(xs) : xsk_generic_xmit(sk);
}
  • XDP_WAKEUP_TXフラグが立っていたら送信処理を行なう
    • 立っていなければやらない
  • ゼロコピーの時はxsk_zc_xmit、そうでなければxsk_generic_xmitを呼ぶ
ozaki-rozaki-r
static int xsk_wakeup(struct xdp_sock *xs, u8 flags)
{
        struct net_device *dev = xs->dev;
        int err;

        rcu_read_lock();
        err = dev->netdev_ops->ndo_xsk_wakeup(dev, xs->queue_id, flags);
        rcu_read_unlock();

        return err;
}

static int xsk_zc_xmit(struct xdp_sock *xs)
{
        return xsk_wakeup(xs, XDP_WAKEUP_TX);
}

ゼロコピーの時はndo_xsk_wakeupをXDP_WAKEUP_TXフラグで呼ぶ。

ixgbeの場合、ixgbe_xsk_wakeupが呼ばれる。

ixgbe_xsk_wakeupは↑で調べた通り受信割り込みを強制的に起こしているはず。TXでも受信割り込みで良いのだろうか?1つの割り込みで受信処理と送信処理の両方を実行することはよくあるから良いのかな。

なお現実装ではフラグは無視されている。

ozaki-rozaki-r

ゼロコピーでないとき(xsk_generic_xmit)は、ざっくりいうと、Tx ringにセットされたバッファからskbを作って(xsk_build_skb)、__dev_direct_xmitでskbを送信している。

xsk_build_skbでskbを作っているが、netdevにIFF_TX_SKB_NO_LINEARフラグが立っているとコピーなしでskbを作っている?

__dev_direct_xmitは色々前処理はしてるけど最終的にndo_start_xmitを叩いている。

ozaki-rozaki-r
        /* initialize NAPI */
        netif_napi_add(adapter->netdev, &q_vector->napi,
                       ixgbe_poll, 64);

ixgbe_pollは初期化時にNAPIに登録されているのか。

ozaki-rozaki-r

MSI-Xの場合、request_irqでixgbe_msix_clean_ringsが登録されているので、割り込みがあった場合は、最初にこれが呼ばれているはず。

static irqreturn_t ixgbe_msix_clean_rings(int irq, void *data)
{
        struct ixgbe_q_vector *q_vector = data;

        /* EIAM disabled interrupts (on this vector) for us */

        if (q_vector->rx.ring || q_vector->tx.ring)
                napi_schedule_irqoff(&q_vector->napi);

        return IRQ_HANDLED;
}

napi_schedule_irqoffを呼ぶだけ。この先でなんやかんやあってixgbe_pollが呼ばれるのかな。

ozaki-rozaki-r

napi_schedule_irqoffはRTカーネルでなければ____napi_scheduleを呼ぶ。

____napi_scheduleは、スレッドを使うように設定されていたらスレッドを起こし、そうでなければ__raise_softirq_irqoff(NET_RX_SOFTIRQ)を呼ぶ。

ozaki-rozaki-r

softirqは起動タイミングはともかく、NET_TX_SOFTIRQならnet_tx_action, NET_RX_SOFTIRQならnet_rx_actionが呼ばれることになる。

net_tx_actionは送信完了したskbを解放したり、送信可能なqdiscを起動したりしている。

net_rx_actionは____napi_scheduleでスケジュール要求があったデバイスのpoll関数を呼んでいる。

ozaki-rozaki-r
  • 結局skbモードとそうでない時の違いがいまいちわからない
  • 送信時のzcはなんとなくわかったが、受信時がわからない
ozaki-rozaki-r

drvモードだとpoolの初期化をするはず。ixgbeだとixgbe_xsk_pool_enableでセットアップする。

ozaki-rozaki-r

ndo_bpf, ndo_xdp_xmit, ndo_xsk_wakeupの全部を実装しているドライバは少ない。ndo_bpfのみ実装しているものは割と多い。多分eBPFを使えるようにしている?

XDP_SETUP_XSK_POOLを実装しているのはdrv mode対応していると思って良さそう。つまりあまりない。

しかしskb modeはこれなしでどうやってpoolを扱うんだろう?

ozaki-rozaki-r

struct xdp_sockがpoolを持っているな。

static int xsk_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
// snip
                /* This xsk has its own umem. */
                xs->pool = xp_create_and_assign_umem(xs, xs->umem);
                if (!xs->pool) {
                        err = -ENOMEM;
                        goto out_unlock;
                }

                err = xp_assign_dev(xs->pool, dev, qid, flags);
                if (err) {
                        xp_destroy(xs->pool);
                        xs->pool = NULL;
                        goto out_unlock;
                }
// snip

xp_create_and_assign_umemでpoolを生成して、xp_assign_devでpoolをnetdevに登録する?

xp_assign_devのロジックは

  • copy modeならnetdevに対して何もしない
  • netdevがndo_bpfとndo_xsk_wakeupを実装してなければエラーを返す
    • ただしzero copy mode指定でなければ、copy modeにfallback(エラーにしない)
  • ndo_bpf(XDP_SETUP_XSK_POOL)を呼ぶ

ということでzcにしたいならnetdevに登録するけど、そうでなければ何もしないということか。

ozaki-rozaki-r

この辺の処理には結局skb modeかどうかはあまり関係ない...

ozaki-rozaki-r

xsk_poolはAF_XDPを使っているというより、AF_XDPでdrv modeかつzc modeを使っている状態なのかな。

ozaki-rozaki-r

ixgbe_clean_rx_irq_zc vs. ixgbe_clean_rx_irq

                rx_buffer = ixgbe_get_rx_buffer(rx_ring, rx_desc, &skb, size, &rx_buffer_pgcnt);

                /* retrieve a buffer from the ring */
                if (!skb) {
                        unsigned char *hard_start;

                        hard_start = page_address(rx_buffer->page) +
                                     rx_buffer->page_offset - offset;
                        xdp_prepare_buff(&xdp, hard_start, offset, size, true);
#if (PAGE_SIZE > 4096)
                        /* At larger PAGE_SIZE, frame_sz depend on len size */
                        xdp.frame_sz = ixgbe_rx_frame_truesize(rx_ring, size);
#endif
                        skb = ixgbe_run_xdp(adapter, rx_ring, &xdp);
                }

ディスクリプタが指す受信バッファにskbがなければ、それはXDPを使っているということなので、XDPを実行する?

                if (IS_ERR(skb)) {
                        unsigned int xdp_res = -PTR_ERR(skb);

                        if (xdp_res & (IXGBE_XDP_TX | IXGBE_XDP_REDIR)) {
                                xdp_xmit |= xdp_res;
                                ixgbe_rx_buffer_flip(rx_ring, rx_buffer, size);
                        } else {
                                rx_buffer->pagecnt_bias++;
                        }
                        total_rx_packets++;
                        total_rx_bytes += size;
                } else if (skb) {
                        ixgbe_add_rx_frag(rx_ring, rx_buffer, skb, size);
                } else if (ring_uses_build_skb(rx_ring)) {
                        skb = ixgbe_build_skb(rx_ring, rx_buffer,
                                              &xdp, rx_desc);
                } else {
                        skb = ixgbe_construct_skb(rx_ring, rx_buffer,
                                                  &xdp, rx_desc);
                }

IS_ERR(skb)はエラーのように見えるけど、XDPの結果を返しているだけらしい。

ちなみにこのXDPはringに設定されたものなので、skb modeだと発動しないはず。

ixgbe_add_rx_fragは受信バッファの中身をskbに移している。

後の2つのケースはskbをどこかから取ってきてから受信バッファの中身をskbに移す(コピー)している?

いずれにせよ最終的にixgbe_rx_skbを呼ぶ。

ozaki-rozaki-r

ixgbe_rx_skbはnapi_gro_receiveを呼んでいる。

do_xdp_genericは__netif_receive_skb_coreから呼ばれるけど、napi_gro_receiveも最終的にはここに辿りつくのだろうか?

違う。napi_gro_receiveはskbをリストに繋ぐだけで、実際の処理はixgbe_pollから返ってきた後にnapi側で行われる?

budgetを使い切ったらixgbe_pollないから直でnapi_complete_doneを呼んで処理するケースもあるな。

いずれの場合でも(napi側の場合は__napi_pollから)gro_normal_listが呼ばれて、

  • netif_receive_skb_list_internal
  • __netif_receive_skb_list
  • __netif_receive_skb_list_core
  • __netif_receive_skb_core

という感じでdo_xdp_genericにたどり着く。

ozaki-rozaki-r

つまり、skb modeは受信時はskbを作った後でXDPの処理が実行される(ので効率は悪い)。

この辺はAF_XDPの話というよりXDP一般の話だと思われる。

ozaki-rozaki-r

ixgbe_clean_rx_irq_zc

受信データが正常ならixgbe_run_xdp_zcを呼んでXDPのeBPFを実行する。結果は普通はXDP_REDIRECTのはずで、もしそうなら次の受信データの処理に移る。

もしXDP_REDIRECT以外なら、skbを組み立てて(ixgbe_construct_skb_zc)ixgbe_rx_skbへ渡す。

ozaki-rozaki-r

まとめ

  • XDP_FLAGS_SKB_MODE
    • ドライバに修正が不要なモード
    • 受信
      • genric XDP相当
      • skbを作ってネットワークスタックに渡す直前(__netif_receive_skb_core)にXDPを実行する(do_xdp_generic)
    • 送信
      • 送信(sendmgs => xsk_generic_xmit)もskbを生成してドライバに渡す(__dev_direct_xmit)
    • XDP_ZEROCOPYとの併用は不可
      • 常にXDP_COPY相当?
  • XDP_FLAGS_DRV_MODE
    • ドライバがXDPをサポートしている場合 and/or さらにzero copyのための修正をしている場合
    • 先にXDP_ZEROCOPYモードを試してダメならXDP_COPYにfallbackする
      • 明示的にcopy modeを指定することも可能
    • XDP_ZEROCOPY
      • 送信
        • ドライバのAF_XDP用の送信処理を行なう(xsk_wakeup(xs, XDP_WAKEUP_TX);)
        • ixgbeは強制的に割り込みハンドラを起こすだけ
          • napi pollに任せる
          • ixgbe_clean_xdp_tx_irq => ixgbe_xmit_zc
      • 受信
        • napi pollのドライバの処理中(ixgbe_clean_rx_irq_zc)にXDPを実行する
        • XDP redirectでユーザスペースに受信パケットを渡すときにコピーしない
    • XDP_COPY
      • 送信
        • skb modeと同じ
        • ixgbe_clean_xdp_tx_irqの代わりに通常のixgbe_clean_tx_irqが呼ばれる
      • 受信
        • skb modeと違いドライバ処理中にXDPを実行する
        • ixgbe_clean_rx_irq_zcでなくixgbe_clean_rx_irqで処理する
        • バッファは通常のルーチンで割り当てるので、XDP redirectでユーザスペースで受信パケットを渡すときにコピーが発生する
ozaki-rozaki-r

これはixgbeの実装の話になってるな。copyモードでもskbモードより効率的な実装は可能な気がする。

ozaki-rozaki-r

いや、ixgbeもskb modeよりは効率が良い。XDPを実行する場所が違う。

ozaki-rozaki-r

ixgbe_alloc_rx_buffersとixgbe_alloc_rx_buffers_zcは何が違う?

ozaki-rozaki-r

おそらくメモリを割り当て元が通常のカーネルからかAF_XDPのumemからかの違いだけ?

ozaki-rozaki-r

https://www.kernel.org/doc/html/v5.15/networking/af_xdp.html#xdp-copy-and-xdp-zerocopy-bind-flags

XDP_COPY and XDP_ZEROCOPY bind flags
When you bind to a socket, the kernel will first try to use zero-copy copy. If zero-copy is not supported, it will fall back on using copy mode, i.e. copying all packets out to user space. But if you would like to force a certain mode, you can use the following flags. If you pass the XDP_COPY flag to the bind call, the kernel will force the socket into copy mode. If it cannot use copy mode, the bind call will fail with an error. Conversely, the XDP_ZEROCOPY flag will force the socket into zero-copy mode or fail.

デフォルトはzcで、ダメだったらcopy modeへフォールバックする。

ozaki-rozaki-r

copying all packets out to user space

コピーモードの定義(?)はパケットをユーザスペースにコピーすること。手段は問わないのかな。

ozaki-rozaki-r

ixgbeのcopy modeはほぼskb modeだけどそれ以外のドライバだと違うのだろうか?

ozaki-rozaki-r

ndo_xdp_xmitはいつ呼ばれる?

bond_xdp_xmit or bq_xmit_allから呼ばれる。

bq_xmit_allはbq_enqueue or __dev_flushから呼ばれる。

XDPの処理の流れで呼ばれると思われる。

ndo_xdp_xmitは実はAF_XDPはあまり関係ないのか。

ozaki-rozaki-r

drv modeの実装に必要なのは、ndo_bpf(XDP_SETUP_XSK_POOL)とndo_xsk_wakeupなのかな。

ozaki-rozaki-r

skb vs. drv mode

AF_XDP can operate in two different modes: XDP_SKB and XDP_DRV. If the driver does not have support for XDP, or XDP_SKB is explicitly chosen when loading the XDP program, XDP_SKB mode is employed that uses SKBs together with the generic XDP support and copies out the data to user space. A fallback mode that works for any network device. On the other hand, if the driver has support for XDP, it will be used by the AF_XDP code to provide better performance, but there is still a copy of the data into user space.

もしドライバがXDPをサポートしていれば、それを利用することで性能は良くなるけど、やっぱりコピーは発生する。

これは受信でXDPを呼ぶ場所の違いだろうか?XDP対応ドライバだとnapi poll時にXDPを実行できるので、xdp_do_genericでXDPを実行するよりかは効率が良さそう。ただし、バッファは通常のルーチン(ixgbe_alloc_rx_buffers)で割り当てているので、ユーザに渡す際にコピーが必要。(どこでコピーが発生するんだろう?)

ozaki-rozaki-r

zcなxdp redirectと普通のxdp redirectの違い。

https://www.kernel.org/doc/html/v5.15/networking/af_xdp.html#xskmap-bpf-map-type-xskmap

On XDP side there is a BPF map type BPF_MAP_TYPE_XSKMAP (XSKMAP) that is used in conjunction with bpf_redirect_map() to pass the ingress frame to a socket.

  • xdp_do_redirect(BPF_MAP_TYPE_XSKMAP)
  • __xsk_map_redirect
  • xsk_rcv
  • __xsk_rcv_zc if (xdp->rxq->mem.type == MEM_TYPE_XSK_BUFF_POOL)
    • xskq_prod_reserve_descを呼んで終わり
  • __xsk_rcv otherwise
    • socketに設定されているpool(xs->pool)からバッファを確保してそこへコピー
    • 後は__xsk_rcv_zcと同じ
ozaki-rozaki-r

__dev_direct_xmit to ndo_start_xmit

int __dev_direct_xmit(struct sk_buff *skb, u16 queue_id)
{
// snip
	if (!netif_xmit_frozen_or_drv_stopped(txq))
		ret = netdev_start_xmit(skb, dev, txq, false);
// snip
}

static inline netdev_tx_t netdev_start_xmit(struct sk_buff *skb, struct net_device *dev,
					    struct netdev_queue *txq, bool more)
{
// snip
	rc = __netdev_start_xmit(ops, skb, dev, more);
// snip
}

static inline netdev_tx_t __netdev_start_xmit(const struct net_device_ops *ops,
					      struct sk_buff *skb, struct net_device *dev,
					      bool more)
{
	__this_cpu_write(softnet_data.xmit.more, more);
	return ops->ndo_start_xmit(skb, dev);
}
ozaki-rozaki-r

zc変数

  • xs->zc = xs->umem->zc; at xsk_bind
  • pool->umem->zc = true; at xp_assign_dev

やっぱりzerocopyのときにtrueになる。

このスクラップは2021/12/20にクローズされました