内核版本:5.10
1 netdev_core_pick_tx
对于多队列的网络设备,real_num_tx_queues大于1,如果网络设备定义了自身的队列选择函数(ixgbe_select_queue),使用此函数;否则,使用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);
}
1.1 netdev_pick_tx
首先,获取套接口中保存的队列索引值(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;
1.1.1 XPS队列选择 get_xps_queue
如果没有定义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;
}
1.1.2 哈希选择队列skb_tx_hash
目前对于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;
1.2 队列索引合法检查 netdev_cap_txqueue
如果选择的队列索引超过总的队列数量,返回队列零。
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;
}