网卡发送队列选择

对于多队列的网络设备,real_num_tx_queues大于1,如果网络设备定义了自身的队列选择函数,使用此函数;否则,使用netdev_pick_tx选择发送队列。最后,队列选择完成之后,将队列索引保存到skb的成员queue_mapping中。

struct netdev_queue *netdev_core_pick_tx(struct net_device *dev,
                     struct sk_buff *skb, struct net_device *sb_dev)
{
    int queue_index = 0;
#ifdef CONFIG_XPS
    u32 sender_cpu = skb->sender_cpu - 1;

    if (sender_cpu >= (u32)NR_CPUS)
        skb->sender_cpu = raw_smp_processor_id() + 1;
#endif

    if (dev->real_num_tx_queues != 1) {
        const struct net_device_ops *ops = dev->netdev_ops;

        if (ops->ndo_select_queue)
            queue_index = ops->ndo_select_queue(dev, skb, sb_dev);
        else
            queue_index = netdev_pick_tx(dev, skb, sb_dev);

        queue_index = netdev_cap_txqueue(dev, queue_index);
    }

    skb_set_queue_mapping(skb, queue_index);
    return netdev_get_tx_queue(dev, queue_index);

首先,获取套接口中保存的队列索引值(sk_tx_queue_mapping),如果此值合法,范围在[0, skb->real_num_tx_queues]之间,并且ooo_okay不为1,意味着不需要重选队列,此时使用套接口中保存的队列索引值。

否则,使用get_xps_queue选择新的队列索引,如果新索引小于零,使用skb_tx_hash选择队列索引。最后,对于完全的套接口,如果路由缓存有效,并且新索引与保存的索引不同,更新套接口中的发送队列索引。

u16 netdev_pick_tx(struct net_device *dev, struct sk_buff *skb,
             struct net_device *sb_dev)
{
    struct sock *sk = skb->sk;
    int queue_index = sk_tx_queue_get(sk);

    sb_dev = sb_dev ? : dev;

    if (queue_index < 0 || skb->ooo_okay ||
        queue_index >= dev->real_num_tx_queues) {
        int new_index = get_xps_queue(dev, sb_dev, skb);

        if (new_index < 0)
            new_index = skb_tx_hash(dev, sb_dev, skb);

        if (queue_index != new_index && sk &&
            sk_fullsock(sk) &&
            rcu_access_pointer(sk->sk_dst_cache))
            sk_tx_queue_set(sk, new_index);

        queue_index = new_index;
    }
    return queue_index;

XPS队列选择

如果没有定义XPS,此函数返回-1。如果定义了XPS,可通过以下PROC文件配置基于CPU核心的队列选择,或者基于接收队列的发送队列选择:

/sys/class/net/ens33/queues/tx-0/xps_cpus
/sys/class/net/ens33/queues/tx-0/xps_rxqs

如果没有XPS相关配置,返回-1,非法队列值。否则,如果基于接收队列的选择(xps_rxqs_needed)没有配置,使用基于CPU核心的队列选择。如果两者同时配置,优先处理xps_rxqs配置。

static int get_xps_queue(struct net_device *dev, struct net_device *sb_dev, struct sk_buff *skb)
{
#ifdef CONFIG_XPS
    struct xps_dev_maps *dev_maps;
    struct sock *sk = skb->sk;
    int queue_index = -1;

    if (!static_key_false(&xps_needed))
        return -1;

    rcu_read_lock();
    if (!static_key_false(&xps_rxqs_needed))
        goto get_cpus_map;

首先取得接收队列索引值,如果其合法,使用函数__get_xps_queue_idx依据xps_rxqs配置将其转换为发送队列索引。

    dev_maps = rcu_dereference(sb_dev->xps_rxqs_map);
    if (dev_maps) {
        int tci = sk_rx_queue_get(sk);
        if (tci >= 0 && tci < dev->num_rx_queues)
            queue_index = __get_xps_queue_idx(dev, skb, dev_maps, tci);
    }

如果以上取得的发送队列索引值不合法,使用函数__get_xps_queue_idx将发送CPU核心作为参数,依据xps_cpus配置转换为发送队列索引。

get_cpus_map:
    if (queue_index < 0) {
        dev_maps = rcu_dereference(sb_dev->xps_cpus_map);
        if (dev_maps) {
            unsigned int tci = skb->sender_cpu - 1;
            queue_index = __get_xps_queue_idx(dev, skb, dev_maps, tci);
        }
    }
    rcu_read_unlock();

    return queue_index;
#else
    return -1;
#endif

对于多优先级Qdisc流控,tci索引到对应的流控类别(Traffic Class),在根据报文优先级取得偏移量。对于其它流控,直接使用tci作为配置表索引,如果表长度为1,直接使用其中的队列值。否则,根据报文五元组索引计算哈希值作为表索引,获得队列值。

合法的队列值不应大于队列总数real_num_tx_queues的值。

static int __get_xps_queue_idx(struct net_device *dev, struct sk_buff *skb,
                   struct xps_dev_maps *dev_maps, unsigned int tci)
{
    struct xps_map *map;
    int queue_index = -1;

    if (dev->num_tc) {
        tci *= dev->num_tc;
        tci += netdev_get_prio_tc_map(dev, skb->priority);
    }
    map = rcu_dereference(dev_maps->attr_map[tci]);
    if (map) {
        if (map->len == 1)
            queue_index = map->queues[0];
        else
            queue_index = map->queues[reciprocal_scale(
                        skb_get_hash(skb), map->len)];
        if (unlikely(queue_index >= dev->real_num_tx_queues))
            queue_index = -1;
    }
    return queue_index;

哈希选择队列

目前对于mqprio和taprio两种具有优先级队列的Qdisc,会设置num_tc相关数值,根据优先级priority,映射到tc类别,再根据tc到txq的映射计算出发送队列的范围。否则,对于其它Qdisc,发送队列的范围固定为[0,real_num_tx_queues]。

static u16 skb_tx_hash(const struct net_device *dev,
               const struct net_device *sb_dev,
               struct sk_buff *skb)
{
    u16 qoffset = 0;
    u16 qcount = dev->real_num_tx_queues;

    if (dev->num_tc) {
        u8 tc = netdev_get_prio_tc_map(dev, skb->priority);

        qoffset = sb_dev->tc_to_txq[tc].offset;
        qcount = sb_dev->tc_to_txq[tc].count;
    }

如果skb中记录了接收队列的值,确保此值(hash)位于合法的范围内,即(qoffset, qcount)之间。这样最终选择的队列索引(hash+qoffset)不超过队列总数qcount的值。

    if (skb_rx_queue_recorded(skb)) {
        hash = skb_get_rx_queue(skb);
        if (hash >= qoffset)
            hash -= qoffset;
        while (unlikely(hash >= qcount))
            hash -= qcount;
        return hash + qoffset;
    }

否则,如果没有接收队列的记录,由函数skb_get_hash通过报文的五元组信息计算hash值,此哈希值不超过qcount,最后,增加qoffset作队列索引值。此处可见,返回的队列索引可能超过队列总数qcount的值。

    return (u16) reciprocal_scale(skb_get_hash(skb), qcount) + qoffset;

队列索引合法检查

如果选择的队列索引超过总的队列数量,返回队列零。

static inline u16 netdev_cap_txqueue(struct net_device *dev, u16 queue_index)
{        
    if (unlikely(queue_index >= dev->real_num_tx_queues)) {
        net_warn_ratelimited("%s selects TX queue %d, but real number of TX queues is %d\n",
                     dev->name, queue_index,
                     dev->real_num_tx_queues); 
        return 0;
    }    

    return queue_index;   

内核版本 5.10

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值