上一篇讲了xdp的hook点,我猜你肯定想到了xdp程序是如何与网卡驱动关联上的,也即如何在bpf_prog_run_xdp中被成功调用。
先看看bpf_prog_run_xdp函数的代码:
static __always_inline u32 bpf_prog_run_xdp(const struct bpf_prog *prog,
struct xdp_buff *xdp)
{
/* Caller needs to hold rcu_read_lock() (!), otherwise program
* can be released while still running, or map elements could be
* freed early while still having concurrent users. XDP fastpath
* already takes rcu_read_lock() when fetching the program, so
* it's not necessary here anymore.
*/
return BPF_PROG_RUN(prog, xdp);
}
#define BPF_PROG_RUN(filter, ctx) (*(filter)->bpf_func)(ctx, (filter)->insnsi)
看到这个代码,可能会有疑问,xdp程序只能挂载一个功能函数 ?是的,确实是只能挂载一个,等你看到struct bpf_prog对象更新的逻辑就会明白的。
很明显,我们要跟踪struct bpf_prog *prog 对象来自哪里,下面我们看看bpf_prog_run_xdp的上级函数ixgbe_run_xdp,ixgbe_run_xdp是ixgbe网卡xdp最关键的函数了,贴出来大家看看。
static struct sk_buff *ixgbe_run_xdp(struct ixgbe_adapter *adapter,
struct ixgbe_ring *rx_ring,
struct xdp_buff *xdp)
{
int err, result = IXGBE_XDP_PASS;
struct bpf_prog *xdp_prog;
struct xdp_frame *xdpf;
u32 act;
rcu_read_lock();
xdp_prog = READ_ONCE(rx_ring->xdp_prog);
if (!xdp_prog)
goto xdp_out;
prefetchw(xdp->data_hard_start); /* xdp_frame write */
act = bpf_prog_run_xdp(xdp_prog, xdp);
switch (act) {
case XDP_PASS:
break;
case XDP_TX:
xdpf = convert_to_xdp_frame(xdp);
if (unlikely(!xdpf)) {
result = IXGBE_XDP_CONSUMED;
break;
}
result = ixgbe_xmit_xdp_ring(adapter, xdpf);
break;
case XDP_REDIRECT:
err = xdp_do_redirect(adapter->netdev, xdp, xdp_prog);
if (!err)
result = IXGBE_XDP_REDIR;
else
result = IXGBE_XDP_CONSUMED;
break;
default:
bpf_warn_invalid_xdp_action(act);
/* fallthrough */
case XDP_ABORTED:
trace_xdp_exception(rx_ring->netdev, xdp_prog, act);
/* fallthrough -- handle aborts by dropping packet */
case XDP_DROP:
result = IXGBE_XDP_CONSUMED;
break;
}
xdp_out:
rcu_read_unlock();
return ERR_PTR(-result);
}
你一定看过很多xdp的其他文章,对于XDP_PASS/XDP_TX/XDP_REDIRECT等字眼很熟,这个函数就是ixbge xdp最关键的函数,我们前面讲的bpf_prog_run_xdp也是在这个函数中被唯一调用。
当然这些都不是重点,重点是我们想跟踪bpf_prog_run_xdp的struct bpf_prog *xdp_prog对象是哪里来的,所以我们还要继续往下看。
xdp_prog = READ_ONCE(rx_ring->xdp_prog) 这行代码已经告诉你bpf_prog_run_xdp的struct bpf_prog *xdp_prog对象来自于struct ixgbe_ring *rx_ring对象的xdp_prog对象。
继续跟踪代码,发现struct ixgbe_ring *rx_ring对象的xdp_prog对象又来自于struct ixgbe_adapter *对象的xdp_prog。而struct ixgbe_adapter *对象xdp_prog实在函数 ixgbe_xdp_setup初始化的。
static int ixgbe_xdp_setup(struct net_device *dev, struct bpf_prog *prog)
{
int i, frame_size = dev->mtu + ETH_HLEN + ETH_FCS_LEN + VLAN_HLEN;
struct ixgbe_adapter *adapter = netdev_priv(dev);
struct bpf_prog *old_prog;
if (adapter->flags & IXGBE_FLAG_SRIOV_ENABLED)
return -EINVAL;
if (adapter->flags & IXGBE_FLAG_DCB_ENABLED)
return -EINVAL;
/* verify ixgbe ring attributes are sufficient for XDP */
for (i = 0; i < adapter->num_rx_queues; i++) {
struct ixgbe_ring *ring = adapter->rx_ring[i];
if (ring_is_rsc_enabled(ring))
return -EINVAL;
if (frame_size > ixgbe_rx_bufsz(ring))
return -EINVAL;
}
if (nr_cpu_ids > MAX_XDP_QUEUES)
return -ENOMEM;
old_prog = xchg(&adapter->xdp_prog, prog);
/* If transitioning XDP modes reconfigure rings */
if (!!prog != !!old_prog) {
int err = ixgbe_setup_tc(dev, adapter->hw_tcs);
if (err) {
rcu_assign_pointer(adapter->xdp_prog, old_prog);
return -EINVAL;
}
} else {
for (i = 0; i < adapter->num_rx_queues; i++)
(void)xchg(&adapter->rx_ring[i]->xdp_prog,
adapter->xdp_prog);
}
if (old_prog)
bpf_prog_put(old_prog);
return 0;
}
而struct ixgbe_adapter *对象xdp_prog实在函数 ixgbe_xdp_setup中初始化的,所以我们的问题就变成了ixgbe_xdp_setup在什么时候调用。当日前面也提过了xdp只能挂载一个功能性函数,也在上面的ixgbe_xdp_setup函数中得到了印证。
继续ixgbe_xdp_setup函数的调用用关系:
ixgbe_xdp_setup
ixgbe_xdp(.ndo_bpf = ixgbe_xdp)
dev_xdp_install
dev_change_xdp_fd
do_setlink
--------------
bpf_set_link_xdp_fd
main (samples/bpf/xdp1_user.c)
好了,到这里,你肯定知道怎么回事了。
很明显在xdp的用户态程序中(比如samples/bpf/xdp1_user.c),一定会调用bpf_set_link_xdp_fd这个用户态函数,这个函数 通过netlink方式实现了网卡的xdp功能函数设置和替换。
这个函数中真正调用了.ndo_bpf = ixgbe_xdp,实现了网卡的prog对象切换。
static int dev_xdp_install(struct net_device *dev, bpf_op_t bpf_op,
struct netlink_ext_ack *extack, u32 flags,
struct bpf_prog *prog)
{
struct netdev_bpf xdp;
memset(&xdp, 0, sizeof(xdp));
if (flags & XDP_FLAGS_HW_MODE)
xdp.command = XDP_SETUP_PROG_HW;
else
xdp.command = XDP_SETUP_PROG;
xdp.extack = extack;
xdp.flags = flags;
xdp.prog = prog;
return bpf_op(dev, &xdp);
}
好了,讲到这里就结束了,估计你还有疑问,xdp的功能程序存储在哪里,它又是怎么被找到的。
下一篇文章再讲。