本章的这些特性在于提高网络性能,这些技术中的一些需要NICs的支持,一些特性是软件实现的。这些特性在服务器上使用较为流行,但是对于嵌入式领域,尤其是视频等网络等带宽需求较大的应用场景,这些特性也可能被使用。
以太网数据包长是1500个字节,如果要传输大量的数据,TCP层就需要将这些数据分成若干小片MSS(Maximum Segment Size),然后再将这些小片发送出去。
如果将一个大数据包(通常是MSS整数倍)送给网卡,那么网卡就需要完成分片工作,将数据分片成1460字节+40字节的包头信息,传送给PHY芯片。这就意味着网卡需要支持这类的特性才能这么使用。网卡特性的跟踪相对而言比较简单,在网卡注册的时候,就将网卡支持的一些特性放在了一个flag中了。
这些特性被称为offload特性,其寓意就是将CPU从繁重的工作中offload了,就意味着CPU可以有更多的时间让给操作系统其它功能部分了。
rx-checksumming TCP接收的校验和由网卡验证。
tx-checksumming: 网卡完成发送TCP头校验和的计算
scatter-gather: 分散/聚集IO,网卡支持该特性,可以减少内存的拷贝。
(TSO)tcp-segmentation-offload: TCP、DCCP数据包分片(依赖tx-checksumming和scatter-gather)
(UFO)udp-fragmentation-offload: 支持UDP分片,ip层不需要对最大64K的数据进行分片。
(GSO)generic-segmentation-offload: 支持tcp、UDP和ipv6分片,由软件模拟来完成,于硬件无关。
(GRO)generic-receive-offload: 支持tcp、UDP和ipv6分片,由软件模拟来完成,于硬件无关。
(LRO)large-receive-offload: 小包聚合成大包传递给协议栈,减小上层开销。
15.1 TSO/GSO
1429 static int tcp_init_tso_segs(const struct sock *sk, struct sk_buff *skb,
1430 unsigned int mss_now)
1431 {
1432 int tso_segs = tcp_skb_pcount(skb);//获得skb被分成了多少个tso段,准确来说是tso特性支持了多个段。
//当1、当前分段成员值为0
//2、skb的mss和当前的mss值不相等,则需要初始化skb的TSO相关成员
1434 if (!tso_segs || (tso_segs > 1 && tcp_skb_mss(skb) != mss_now)) {
1435 tcp_set_skb_tso_segs(sk, skb, mss_now);//设置skb的TSO成员。见下
1436 tso_segs = tcp_skb_pcount(skb);//获得准确的tso段个数。
1437 }
1438 return tso_segs;
1439 }
//
977 static void tcp_set_skb_tso_segs(const struct sock *sk, struct sk_buff *skb,
978 unsigned int mss_now)
979 {
//1、数据长度小于当前mss;2、网卡不支持gso;3、ip_summed原本存放的是校验和,但如果等于CHECKSUM_NONE,//则表示网卡没能完成校验和的计算,网卡不支持硬件计算校验和就会这样。以上三种情况没有必要分片。
980 if (skb->len <= mss_now || !sk_can_gso(sk) ||
981 skb->ip_summed == CHECKSUM_NONE) {// CHECKSUM_NONE,--device failed to checksum this packet.
982 /* Avoid the costly divide in the normal
983 * non-TSO case.
984 */
985 skb_shinfo(skb)->gso_segs = 1;
986 skb_shinfo(skb)->gso_size = 0;
987 skb_shinfo(skb)->gso_type = 0;
988 } else {
989 skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss_now); //倍数关系
990 skb_shinfo(skb)->gso_size = mss_now;
//gso类型,比如SKB_GSO_TCPV4,SKB_GSO_UDP…
991 skb_shinfo(skb)->gso_type = sk->sk_gso_type;
992 }
993 }
拥塞窗口
//返回true,则需要增加拥塞窗口,否则不需要增加拥塞窗口。
284 bool tcp_is_cwnd_limited(const struct sock *sk, u32 in_flight)
285 {
286 const struct tcp_sock *tp = tcp_sk(sk);
287 u32 left;
//已发送还未ack数据包等于发送拥塞窗口长度,
289 if (in_flight >= tp->snd_cwnd)
290 return true;
//还可以发送的数据量。
292 left = tp->snd_cwnd - in_flight;
//
293 if (sk_can_gso(sk) &&
294 left * sysctl_tcp_tso_win_divisor < tp->snd_cwnd &&
295 left * tp->mss_cache < sk->sk_gso_max_size &&
296 left < sk->sk_gso_max_segs)
297 return true;
298 return left <= tcp_max_tso_deferred_mss(tp);
299 }
为了最小化TSO分片,延迟发送数据包。TSONagle算法。返回值是1,表示可以延迟发送,返回0,则立即发送。
1596 static bool tcp_tso_should_defer(struct sock *sk, struct sk_buff *skb)
1597 {
1598 struct tcp_sock *tp = tcp_sk(sk);
1599 const struct inet_connection_sock *icsk = inet_csk(sk);
1600 u32 send_win, cong_win, limit, in_flight;
1601 int win_divisor;
// TCPHDR_FIN意味着是close()调用触发的关闭套接字连接,则立即发送。
1603 if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
1604 goto send_now;
//正常状态,则立即发送。
1606 if (icsk->icsk_ca_state != TCP_CA_Open)
1607 goto send_now;
1608
//上一个skb被延迟了,且超过一个时钟,则立即发送。
1610 if (tp->tso_deferred &&
1611 (((u32)jiffies << 1) >> 1) - (tp->tso_deferred >> 1) > 1)
1612 goto send_now;
//发送但没收到ack包,包括重传的数据包。
1614 in_flight = tcp_packets_in_flight(tp);
//通告窗口剩余量
1618 send_win = tcp_wnd_end(tp) - TCP_SKB_CB(skb)->seq;
//拥塞窗口剩余量。
1621 cong_win = (tp->snd_cwnd - in_flight) * tp->mss_cache;
//而这较小值,作为发送门限。
1623 limit = min(send_win, cong_win);
1624
1625 /* If a full-sized TSO skb can be sent, do it. */
1626 if (limit >= min_t(unsigned int, sk->sk_gso_max_size,
1627 sk->sk_gso_max_segs * tp->mss_cache))
1628 goto send_now;
1629
1630 /* Middle in queue won't get any more data, full sendable already? */
1631 if ((skb != tcp_write_queue_tail(sk)) && (limit >= skb->len))
1632 goto send_now;
1633
1634 win_divisor = ACCESS_ONCE(sysctl_tcp_tso_win_divisor);
1635 if (win_divisor) {
//一个RTT内能够发送的最大字节数。
1636 u32 chunk = min(tp->snd_wnd, tp->snd_cwnd * tp->mss_cache);
1637
1638 /* If at least some fraction of a window is available,
1639 * just use it.
1640 */
1641 chunk /= win_divisor; //单个TSO占用的发送量。
1642 if (limit >= chunk)
1643 goto send_now;
1644 } else {
1645 /* Different approach, try not to defer past a single
1646 * ACK. Receiver should ACK every other full sized
1647 * frame, so if we have space for more than 3 frames
1648 * then send now.
1649 */
1650 if (limit > tcp_max_tso_deferred_mss(tp) * tp->mss_cache)
1651 goto send_now;
1652 }
1653
1654 /* Ok, it looks like it is advisable to defer.
1655 * Do not rearm the timer if already set to not break TCP ACK clocking.
1656 */
1657 if (!tp->tso_deferred)
1658 tp->tso_deferred = 1 | (jiffies << 1); //记录延迟时间戳
1659
1660 return true;
1661
1662 send_now:
1663 tp->tso_deferred = 0;
1664 return false;
1665 }
15.2 LRO/GRO
15.1节所述特性用在skb的发送流程上,而这一节的特性用在了skb的接收流程上,LRO(Large Receive Offload)针对TCP数据包计数,将多个数据包聚合到一个大skb发送给上层协议栈,这样提高网络效率,GRO是更通用的版本。
LRO管理结构体Include/linux/inet_lro.h
74 struct net_lro_mgr {
75 struct net_device *dev; //关联LRO功能和网络设备
76 struct net_lro_stats stats; //LRO 统计信息
77
78 /* LRO features */
79 unsigned long features; //如下#define定义的特性
80 #define LRO_F_NAPI //通过NAPI方式传递Packet
81 #define LRO_F_EXTRACT_VLAN_ID 2 // VLAN 和8021Q
89 u32 ip_summed; //在启用分片模式时当生成的SKB没有被添加到分片管理链表ip_summed会被置位
90 u32 ip_summed_aggr; /* 聚合成大的SKBs校验CHECKSUM_UNNECESSARY或者CHECKSUM_NONE */
92
93 int max_desc; //LRO描述符最多个数,每一个LRO描述符对应一路TCP流。
94 int max_aggr; //聚合成一个SKB的最多包数目
95
96 int frag_align_pad; /* 当使用分片时为L3在生成的SKB对其头而预留的填充
98
99 struct net_lro_desc *lro_arr; /* Array of LRO descriptors */
// get_skb_header: returns tcp and ip header for packet in SKB
106 int (*get_skb_header)(struct sk_buff *skb, void **ip_hdr,
107 void **tcpudp_hdr, u64 *hdr_flags, void *priv);
108
109 /* hdr_flags: */
110 #define LRO_IPV4 1 /* ip_hdr is IPv4 header */
111 #define LRO_TCP 2 /* tcpudp_hdr is TCP header */
112
113 /*
114 * get_frag_header: returns mac, tcp and ip header for packet in SKB
115 *
116 * @hdr_flags: Indicate what kind of LRO has to be done
117 * (IPv4/IPv6/TCP/UDP)
118 */
119 int (*get_frag_header)(struct skb_frag_struct *frag, void **mac_hdr,
120 void **ip_hdr, void **tcpudp_hdr, u64 *hdr_flags,
121 void *priv);
122 };
LRO属于NICs特性,在具体NICs驱动程序接收数据包时会调用lro_receive_skb函数以lro方式接收数据包。
496 void lro_receive_skb(struct net_lro_mgr *lro_mgr,
497 struct sk_buff *skb,
498 void *priv)
499 {
500 if (__lro_proc_skb(lro_mgr, skb, priv)) {
501 if (lro_mgr->features & LRO_F_NAPI)
502 netif_receive_skb(skb);
503 else
504 netif_rx(skb);
505 }
506 }
通常数据包会聚合到最大值,但是对延迟要求苛刻可能立即发送,这会调用lro_flush
GRO是LRO的加强版,其使用范围比LRO要广。然后__napi_gro_receive 会调用 dev_gro_receive ,dev_gro_receive会先调用ptype->gso_receive,一般而言就是ip协议对应的inet_gso_receive
inet_gro_receive 主要做如下事情:
首先拿到ip包头,然后对包头做check,如果check passed则开始遍历napi_struct->gro_list,根据ip saddr, daddr, tos, protocol等来和那些之前在二层有可能是同一flow的skb进行判断,如果不一致就把same_flow置0,当然光是slow_flow并不能就此开始merge,还要进行flush的判断,任何flush判断不过都会放弃merge而调用直接调用skb_gro_flush函数交到协议栈上层去处理
ip层结束之后,会继续tcp层的gro_receive,调用tcp_gro_receive ,其核心也是遍历napi_struct->gro_list,基于source addr判断是否是same_flow,对是否需要flush做计算,这里提一下关于ack一致的要求,ack一致说明是同一个tcp payload被tso/gso分段之后的结果,所以是必需条件
如果 tcp 也认为不需要flush,那么会进到 skb_gro_receive 中,这个函数就是用来合并的,第一个参数是gro_list里的那个skb,第二个是新来的skb,这里不多说了,我推荐的博客文章里讲的很清楚了。其实就分两种情况,如果是scatter-gather的skb包,则把新skb里的frags的数据放到gro_list的skb对应的frags数据后面;否则skb数据都在skb的线性地址中,这样直接alloc一个新的skb,把新skb挂到frag_list里面,最后放到原来gro_list的位置上;如果gro_list的skb已经有了frag_list,那么就直接挂进去好了
现在返回到dev_gro_receive中了,这时如果需要flush或者same_flow为0,说明需要传给上层协议栈了,此时调用napi_gro_complete
走到最后一种情况即这个skb是个新的flow,那么就加到gro_list的链表中
最后提下,所谓flush是指把现有gro_list中的skb flush到上层协议。
1539 struct packet_type {
1540 __be16 type; /* This is really htons(ether_type). */
1541 struct net_device *dev; /* NULL is wildcarded here */
1542 int (*func) (struct sk_buff *,
1543 struct net_device *,
1544 struct packet_type *,
1545 struct net_device *);
1546 bool (*id_match)(struct packet_type *ptype,
1547 struct sock *sk);
1548 void *af_packet_priv;
1549 struct list_head list;
1550 };
1551
1552 struct offload_callbacks {
1553 struct sk_buff *(*gso_segment)(struct sk_buff *skb,
1554 netdev_features_t features);
1555 int (*gso_send_check)(struct sk_buff *skb);
1556 struct sk_buff **(*gro_receive)(struct sk_buff **head,
1557 struct sk_buff *skb);
1558 int (*gro_complete)(struct sk_buff *skb);
1559 };
1560
1561 struct packet_offload {
1562 __be16 type; /* This is really htons(ether_type). */
1563 struct offload_callbacks callbacks;
1564 struct list_head list;
1565 };
Include/linux/netdevice.h
306 struct napi_struct {
307 /* The poll_list must only be managed by the entity which
308 * changes the state of the NAPI_STATE_SCHED bit. This means
309 * whoever atomically sets that bit can add this napi_struct
310 * to the per-cpu poll_list, and whoever clears that bit
311 * can remove from the list right before clearing the bit.
312 */
313 struct list_head poll_list;
314
315 unsigned long state;
316 int weight;
317 unsigned int gro_count;
318 int (*poll)(struct napi_struct *, int);
319 #ifdef CONFIG_NETPOLL
320 spinlock_t poll_lock;
321 int poll_owner;
322 #endif
323 struct net_device *dev;
324 struct sk_buff *gro_list;
325 struct sk_buff *skb;
326 struct list_head dev_list;
327 };
Net/core/dev.c
3829 static gro_result_t napi_skb_finish(gro_result_t ret, struct sk_buff *skb)
3830 {
3831 switch (ret) {
3832 case GRO_NORMAL:
3833 if (netif_receive_skb(skb))
3834 ret = GRO_DROP;
3835 break;
3836
3837 case GRO_DROP:
3838 kfree_skb(skb);
3839 break;
3840
3841 case GRO_MERGED_FREE:
3842 if (NAPI_GRO_CB(skb)->free == NAPI_GRO_FREE_STOLEN_HEAD)
3843 kmem_cache_free(skbuff_head_cache, skb);
3844 else
3845 __kfree_skb(skb);
3846 break;
3847
3848 case GRO_HELD:
3849 case GRO_MERGED:
3850 break;
3851 }
3852
3853 return ret;
3854 }
3855
3856 static void skb_gro_reset_offset(struct sk_buff *skb)
3857 {
3858 const struct skb_shared_info *pinfo = skb_shinfo(skb);
3859 const skb_frag_t *frag0 = &pinfo->frags[0];
3860
3861 NAPI_GRO_CB(skb)->data_offset = 0;
3862 NAPI_GRO_CB(skb)->frag0 = NULL;
3863 NAPI_GRO_CB(skb)->frag0_len = 0;
3864
3865 if (skb->mac_header == skb->tail &&
3866 pinfo->nr_frags &&
3867 !PageHighMem(skb_frag_page(frag0))) {
3868 NAPI_GRO_CB(skb)->frag0 = skb_frag_address(frag0);
3869 NAPI_GRO_CB(skb)->frag0_len = skb_frag_size(frag0);
3870 }
3871 }
3872
3873 gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
3874 {
3875 skb_gro_reset_offset(skb);
3876
3877 return napi_skb_finish(dev_gro_receive(napi, skb), skb);
3878 }
3736 static enum gro_result dev_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
3737 {
3738 struct sk_buff **pp = NULL;
3739 struct packet_offload *ptype;
3740 __be16 type = skb->protocol;
3741 struct list_head *head = &offload_base;
3742 int same_flow;
3743 enum gro_result ret;
3744
3745 if (!(skb->dev->features & NETIF_F_GRO) || netpoll_rx_on(skb))
3746 goto normal;
3747
3748 if (skb_is_gso(skb) || skb_has_frag_list(skb))
3749 goto normal;
3750
3751 gro_list_prepare(napi, skb);
3752
3753 rcu_read_lock();
3754 list_for_each_entry_rcu(ptype, head, list) {
3755 if (ptype->type != type || !ptype->callbacks.gro_receive)
3756 continue;
3757
3758 skb_set_network_header(skb, skb_gro_offset(skb));
3759 skb_reset_mac_len(skb);
3760 NAPI_GRO_CB(skb)->same_flow = 0;
3761 NAPI_GRO_CB(skb)->flush = 0;
3762 NAPI_GRO_CB(skb)->free = 0;
3763
3764 pp = ptype->callbacks.gro_receive(&napi->gro_list, skb);
3765 break;
3766 }
3767 rcu_read_unlock();
3768
3769 if (&ptype->list == head)
3770 goto normal;
3771
3772 same_flow = NAPI_GRO_CB(skb)->same_flow;
3773 ret = NAPI_GRO_CB(skb)->free ? GRO_MERGED_FREE : GRO_MERGED;
3774
3775 if (pp) {
3776 struct sk_buff *nskb = *pp;
3777
3778 *pp = nskb->next;
3779 nskb->next = NULL;
3780 napi_gro_complete(nskb);
3781 napi->gro_count--;
3782 }
3783
3784 if (same_flow)
3785 goto ok;
3786
3787 if (NAPI_GRO_CB(skb)->flush || napi->gro_count >= MAX_GRO_SKBS)
3788 goto normal;
3789
3790 napi->gro_count++;
3791 NAPI_GRO_CB(skb)->count = 1;
3792 NAPI_GRO_CB(skb)->age = jiffies;
3793 skb_shinfo(skb)->gso_size = skb_gro_len(skb);
3794 skb->next = napi->gro_list;
3795 napi->gro_list = skb;
3796 ret = GRO_HELD;
3797
3798 pull:
3799 if (skb_headlen(skb) < skb_gro_offset(skb)) {
3800 int grow = skb_gro_offset(skb) - skb_headlen(skb);
3801
3802 BUG_ON(skb->end - skb->tail < grow);
3803
3804 memcpy(skb_tail_pointer(skb), NAPI_GRO_CB(skb)->frag0, grow);
3805
3806 skb->tail += grow;
3807 skb->data_len -= grow;
3808
3809 skb_shinfo(skb)->frags[0].page_offset += grow;
3810 skb_frag_size_sub(&skb_shinfo(skb)->frags[0], grow);
3811
3812 if (unlikely(!skb_frag_size(&skb_shinfo(skb)->frags[0]))) {
3813 skb_frag_unref(skb, 0);
3814 memmove(skb_shinfo(skb)->frags,
3815 skb_shinfo(skb)->frags + 1,
3816 --skb_shinfo(skb)->nr_frags * sizeof(skb_frag_t));
3817 }
3818 }
3819
3820 ok:
3821 return ret;
3822
3823 normal:
3824 ret = GRO_NORMAL;
3825 goto pull;
3826 }
15.3 RSS(Receive Side Scaling)队列:
网卡多队列技术,硬件支持这一特性,将受到的数据包发往不同的队列,已在不同的CPU上处理。RSS流程如图15.2,NICs在接收到串行数据包后,根据数据包的L3层和L4层头信息,硬件计算哈希值,不同的哈希值对应于不同的队列,即将数据包按哈希值分配到不同的队列上,不同的队列有不同的CPU处理,这样就实现了负载均衡。这一特性在drivers/net/ ethernet/XXX等各种网卡驱动中设置网卡RSS控制寄存器来实现。目前内核支持的队列个数取8和CPU个数中较小的那个值。每个队列都有一个IRQ号,对应中断的服务CPU号(一个或多个)使用affinity设置,见Documentation/IRQ-affinity.txt。
图15.2 RSS队列处理流程
15.4 RPS(Receive Packet Steering)队列:
RPS(Receive Packet Steering)源于谷歌patch,软件方法实现RSS。将数据包的源IP和目的IP、源端口和目的端口计算得到一个哈希值,根据该哈希值决定了软中断执行的核号,,这种方法来均衡软中断在各个核的分布,以其达到负载均衡的目的。
sysctl -w net.core.rps_sock_flow_entries=
/sys/class/net/eth*/queues/rx-*/rps_flow_cnt
/sys/class/net/eth*/queues/rx-*/rps_cpus
两个重要的函数
该结构体包含一个RX队列。Include/linux/netdevice.h
struct rps_map {
//零长数组的长度
unsigned int len;
struct rcu_head rcu;
//能够处理RPS数据包的cpu ID数组。
u16 cpus[0];
};
struct netdev_rx_queue {
//保存一个RPS映射,映射的对象是能够处理这个队列的CPU核数组。
struct rps_map __rcu *rps_map;
//流控映射表
struct rps_dev_flow_table __rcu *rps_flow_table;
struct kobject kobj;
struct net_device *dev;
} ____cacheline_aligned_in_smp;
get_rps_cpu由netif_receive_skb(NAPI)和netif_rx函数调用,为一个给定的skb,从RPS接收队列映射关系中返回处理该数据包的CPU ID。哈希值计算可以网卡计算。
Net/core/dev.c
2939 static int get_rps_cpu(struct net_device *dev, struct sk_buff *skb,
2940 struct rps_dev_flow **rflowp)
2941 {
2942 struct netdev_rx_queue *rxqueue;
2943 struct rps_map *map;
2944 struct rps_dev_flow_table *flow_table;
2945 struct rps_sock_flow_table *sock_flow_table; //包含skb期望在那个核被处理信息。
2946 int cpu = -1;
2947 u16 tcpu;
//获得网卡对应的接收队列
2949 if (skb_rx_queue_recorded(skb)) { //判断是否支持多队列,一个队列对应于一个netdev_rx_queue
2950 u16 index = skb_get_rx_queue(skb); //获得skb所在队列的索引
2951 if (unlikely(index >= dev->real_num_rx_queues)) { //索引范围合法性检查
2952 WARN_ONCE(dev->real_num_rx_queues > 1,
2953 "%s received packet on queue %u, but number "
2954 "of RX queues is %u\n",
2955 dev->name, index, dev->real_num_rx_queues);
2956 goto done;
2957 }
2958 rxqueue = dev->_rx + index; //队列首地址+队列索引,获得skb所在的队列。
2959 } else
2960 rxqueue = dev->_rx; //不支持多队列,直接返回即可。
//rcu变量引用方法,获得rps_map员。2962行判断该成员是否为NULL,如果不为空,那么可能存在能够处理该队列的CPU //ID了
2962 map = rcu_dereference(rxqueue->rps_map);
2963 if (map) {
2964 if (map->len == 1 &&
2965 !rcu_access_pointer(rxqueue->rps_flow_table)) {//
2966 tcpu = map->cpus[0]; //获得RPS对应的CPU ID。
2967 if (cpu_online(tcpu)) //判断该CPU是否被启动(多个CPU情况,不一定每个CPU核都被启用)
2968 cpu = tcpu;
2969 goto done;
2970 }
2971 } else if (!rcu_access_pointer(rxqueue->rps_flow_table)) {
2972 goto done;
2973 }
//获得网络头
2975 skb_reset_network_header(skb);
2976 if (!skb_get_rxhash(skb)) //获得计算得到的哈希值。
2977 goto done;
//2979~3016流控处理,见RFS一节
2979 flow_table = rcu_dereference(rxqueue->rps_flow_table);
2980 sock_flow_table = rcu_dereference(rps_sock_flow_table); //包含映射了应用程序处理的流控信息。
2981 if (flow_table && sock_flow_table) {
2982 u16 next_cpu;
2983 struct rps_dev_flow *rflow;
2984
2985 rflow = &flow_table->flows[skb->rxhash & flow_table->mask];
2986 tcpu = rflow->cpu; //流控处理期望所在的CPU
2987
2988 next_cpu = sock_flow_table->ents[skb->rxhash &
2989 sock_flow_table->mask]; //流控处理期望在该CPU上被处理
2990
3002 if (unlikely(tcpu != next_cpu) && //两者不一致,需要进一步判断是否需要进行切换CPU。
3003 (tcpu == RPS_NO_CPU || !cpu_online(tcpu) ||
3004 ((int)(per_cpu(softnet_data, tcpu).input_queue_head -
3005 rflow->last_qtail)) >= 0)) {
3006 tcpu = next_cpu;
3007 rflow = set_rps_cpu(dev, skb, rflow, next_cpu); //见RFS队列。
3008 }
3009
3010 if (tcpu != RPS_NO_CPU && cpu_online(tcpu)) { //RPS设置的CPU启用,并且这里流控处理核也是该CPU
3011 *rflowp = rflow;
3012 cpu = tcpu;
3013 goto done;
3014 }
3015 }
//不做流控处理会到达这里,根据计算得到的哈希值,获取对应的CPU核号。
3017 if (map) {
3018 tcpu = map->cpus[((u64) skb->rxhash * map->len) >> 32];
3019
3020 if (cpu_online(tcpu)) { //CPU 处于开启状态。
3021 cpu = tcpu;
3022 goto done;
3023 }
3024 }
3025
3026 done:
3027 return cpu;
3028 }
enqueue_to_backlog用于将受到的skb放到对应CPU的RPS队列上。
net/core/dev.c
3106 static int enqueue_to_backlog(struct sk_buff *skb, int cpu,
3107 unsigned int *qtail)
3108 {
3109 struct softnet_data *sd;
3110 unsigned long flags;
3111
3112 sd = &per_cpu(softnet_data, cpu); //获得第二个参数cpu所指ID的cpu的softnet_data成员。
3113
3114 local_irq_save(flags);
3115
3116 rps_lock(sd);
//如果存储于接收队列(input_pkt_queue)的skb个数大于设备设定的最大值,则到3137行,更新丢弃计数并丢弃该数据包
3117 if (skb_queue_len(&sd->input_pkt_queue) <= netdev_max_backlog) {
3118 if (skb_queue_len(&sd->input_pkt_queue)) {
3119 enqueue:
3120 __skb_queue_tail(&sd->input_pkt_queue, skb); //将数据包放到队列尾部
3121 input_queue_tail_incr_save(sd, qtail);//更新计数
3122 rps_unlock(sd);
3123 local_irq_restore(flags);
3124 return NET_RX_SUCCESS;
3125 }
3126
//NAPI方式,一个硬件中断会接收多个数据包。
3130 if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
3131 if (!rps_ipi_queued(sd)) //检查该sd是否是其它CPU的,如果是其它CPU,则____napi_schedule被调用。
3132 ____napi_schedule(sd, &sd->backlog);
3133 }
3134 goto enqueue;
3135 }
3136
3137 sd->dropped++; //丢弃计数更新
3138 rps_unlock(sd);
3139
3140 local_irq_restore(flags);
3141
3142 atomic_long_inc(&skb->dev->rx_dropped);
3143 kfree_skb(skb); //丢弃该skb
3144 return NET_RX_DROP;
3145 }
15.5RFS(Receive Flow Steering),AcceleratedReceive Flow Steering
不论是RSS还是RPS考虑都是将收到的数据包进行哈希散列,这没有考虑到应用程序所在的CPU号,加入一个等待数据包的网络进程正在5号CPU上等待数据包,而RPS计算得到的哈希值是1,这就意味着数据包将将由1号CPU接收。这会导致一个问题,那就是缓存命中失败。RFS利用RPS机制将数据包插入指定CPU的backlog队列,并唤醒那个CPU执行。该函数在RPS一节调用到。全局数据流表(rps_sock_flow_table)的总数可以通过下面的参数来设置:
/proc/sys/net/core/rps_sock_flow_entries
每个队列的数据流表总数可以通过下面的参数来设置:
/sys/class/net/<dev>/queues/rx-<n>/rps_flow_cnt
2889 static struct rps_dev_flow *
2890 set_rps_cpu(struct net_device *dev, struct sk_buff *skb,
2891 struct rps_dev_flow *rflow, u16 next_cpu)
2892 {
2893 if (next_cpu != RPS_NO_CPU) {
2894 #ifdef CONFIG_RFS_ACCEL
2895 struct netdev_rx_queue *rxqueue;
2896 struct rps_dev_flow_table *flow_table;
2897 struct rps_dev_flow *old_rflow;
2898 u32 flow_id;
2899 u16 rxq_index;
2900 int rc;
2901
2902 /* Should we steer this flow to a different hardware queue? */
//skb_rx_queue_recorded判断skb多队列映射支持是否置位,没置位直接goto out
// rx_cpu_rmap 设备是否支持CPU亲和度
//dev->features & NETIF_F_NTUPLE 判断NIC是否支持n元匹配过滤
//上述任何一个回答是否,将执行goto out;
2903 if (!skb_rx_queue_recorded(skb) || !dev->rx_cpu_rmap ||
2904 !(dev->features & NETIF_F_NTUPLE))
2905 goto out;
2906 rxq_index = cpu_rmap_lookup_index(dev->rx_cpu_rmap, next_cpu);
2907 if (rxq_index == skb_get_rx_queue(skb)) //接收队列在期望的CPU上,直接返回就行了?
2908 goto out;
2909
2910 rxqueue = dev->_rx + rxq_index; //获得期望CPU的接收队列索引
2911 flow_table = rcu_dereference(rxqueue->rps_flow_table);
2912 if (!flow_table)
2913 goto out;
2914 flow_id = skb->rxhash & flow_table->mask;
//ndo_rx_flow_steer是NIC硬件支持RFS配置接口,CONFIG_RFS_ACCEL配置选项用于表示是否硬件支持
2915 rc = dev->netdev_ops->ndo_rx_flow_steer(dev, skb,
2916 rxq_index, flow_id);
2917 if (rc < 0)
2918 goto out;
2919 old_rflow = rflow;
2920 rflow = &flow_table->flows[flow_id]; //将流放在期望CPU的流控结构里
2921 rflow->filter = rc;//接收过滤器标志
2922 if (old_rflow->filter == rflow->filter)
2923 old_rflow->filter = RPS_NO_FILTER;
2924 out:
2925 #endif
2926 rflow->last_qtail =
2927 per_cpu(softnet_data, next_cpu).input_queue_head;
2928 }
2929
2930 rflow->cpu = next_cpu;
2931 return rflow;
2932 }
15.6 XPS(Transmit Packet Steering)
上面都是对接收数据包的处理,XPS是对发送数据包的多队列处理。XPS通过将发送数据包的CPU映射到一个队列的方法来实现。这类似于RPS不过是在发送端的“RPS”,RPS基于数据包队列选择处理的CPU,而XPS则是基于发送数据包的CPU选择发送队列。
一个发送队列可以由一个或多个CPU支持,配置参数位于:
/sys/class/net/eth<n>/queues/tx-<n>/xps_cpus
XPS支持
struct xps_map {
unsigned int len; //记录queues[0]成员长度
unsigned int alloc_len;
struct rcu_head rcu;
u16 queues[0]; //队列数组
};
#define XPS_MAP_SIZE(_num) (sizeof(struct xps_map) + ((_num) * sizeof(u16))) //映射num个队列时,xps_map的size
#define XPS_MIN_MAP_ALLOC ((L1_CACHE_BYTES - sizeof(struct xps_map)) \
/ sizeof(u16))
//NICs的XPS映射,映射由CPU索引
struct xps_dev_maps {
struct rcu_head rcu;
struct xps_map __rcu *cpu_map[0];
};
#define XPS_DEV_MAPS_SIZE (sizeof(struct xps_dev_maps) + \
(nr_cpu_ids * sizeof(struct xps_map *)))
在dev_queue_xmit函数发送数据包时,会调用netdev_pick_tx选定发送队列。
由skb和dev信息获得对应的发送队列索引函数位于net/core/flow_dissector.c文件。
364 struct netdev_queue *netdev_pick_tx(struct net_device *dev,
365 struct sk_buff *skb)
366 {
367 int queue_index = 0;
// real_num_tx_queues 是NICs当前发送数据包队列活跃的队列数。如果不是1,则表示启用了多队列技术。
369 if (dev->real_num_tx_queues != 1) {
370 const struct net_device_ops *ops = dev->netdev_ops;//获得NICs操作方法
371 if (ops->ndo_select_queue)//NICs支持队列选择。
372 queue_index = ops->ndo_select_queue(dev, skb);
373 else
374 queue_index = __netdev_pick_tx(dev, skb);//见下面。
375 queue_index = dev_cap_txqueue(dev, queue_index);
376 }
377
378 skb_set_queue_mapping(skb, queue_index);//存储设备多队列映射索引到skb中。
379 return netdev_get_tx_queue(dev, queue_index);
380 }
__netdev_pick_tx用于获取发送队列映射索引,如果映射索引变化,那么路由项相应的也要跟着变化。
337 u16 __netdev_pick_tx(struct net_device *dev, struct sk_buff *skb)
338 {
339 struct sock *sk = skb->sk;
340 int queue_index = sk_tx_queue_get(sk); //获得skb的发送队列映射的索引值。
// 如果queue_index非法,表明发送队列索引值需要重建,或者ooo_okay允许映射到发送队列的skb被改变标志置位,则重新//映射
342 if (queue_index < 0 || skb->ooo_okay ||
343 queue_index >= dev->real_num_tx_queues) {
344 int new_index = get_xps_queue(dev, skb); //计算新的映射索引值,见下文。
345 if (new_index < 0) //返回值小于0,说明不支持XPS。
346 new_index = skb_tx_hash(dev, skb);//根据设备是否支持发送队列映射方法获取发送队列索引
347
//新的索引值和原理的索引值,不一致,则需要更换skb的路由项,以实现流控
348 if (queue_index != new_index && sk) {
349 struct dst_entry *dst =
350 rcu_dereference_check(sk->sk_dst_cache, 1); //获得路由项
351
352 if (dst && skb_dst(skb) == dst)
353 sk_tx_queue_set(sk, queue_index);
354
355 }
356
357 queue_index = new_index;
358 }
359
360 return queue_index;
361 }
299 static inline int get_xps_queue(struct net_device *dev, struct sk_buff *skb)
300 {
301 #ifdef CONFIG_XPS
302 struct xps_dev_maps *dev_maps;
303 struct xps_map *map;
304 int queue_index = -1;
305
306 rcu_read_lock();
307 dev_maps = rcu_dereference(dev->xps_maps);
308 if (dev_maps) {
309 map = rcu_dereference(
310 dev_maps->cpu_map[raw_smp_processor_id()]); //获取当前CPU的XPS流控映射表
311 if (map) {
312 if (map->len == 1)//XPS流控映射表只有一项,则直接返回该项。
313 queue_index = map->queues[0];
314 else {
315 u32 hash;
316 if (skb->sk && skb->sk->sk_hash)// sk_hash是协议查找表的哈希值。
317 hash = skb->sk->sk_hash;
318 else//否则,需要根据协议计算这个哈希值。
319 hash = (__force u16) skb->protocol ^
320 skb->rxhash;//接收队列的哈希值。
321 hash = jhash_1word(hash, hashrnd);//上述哈希值再次哈希
322 queue_index = map->queues[
323 ((u64)hash * map->len) >> 32];//新的发送队列索引值
324 }
325 if (unlikely(queue_index >= dev->real_num_tx_queues))
326 queue_index = -1;
327 }
328 }
329 rcu_read_unlock();
330
331 return queue_index;
332 #else
333 return -1;
334 #endif
335 }