68127">
在 NAPI 实例中,有一个 GRO 的包的列表 gro_list,用堆积收到的包,GRO 层用它来将聚集的包分发到网络协议层,而每个支持 GRO 功能的网络协议层,则需要实现 gro_receive 和 gro_complete 方法。
清单 12、协议层支持 GRO/GSO 的接口
struct packet_type { __be16 type; /* This is really htons(ether_type)。 */ struct net_device *dev; /* NULL is wildcarded here */ int (*func) (struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *); struct sk_buff *(*gso_segment)(struct sk_buff *skb, int features); int (*gso_send_check)(struct sk_buff *skb); struct sk_buff **(*gro_receive)(struct sk_buff **head, struct sk_buff *skb); int (*gro_complete)(struct sk_buff *skb); void *af_packet_priv; struct list_head list; };
其中,gro_receive 用于尝试匹配进来的数据包到已经排队的 gro_list 列表,而 IP 和 TCP 的头部则在匹配之后被丢弃;而一旦我们需要向上层协议提交数据包,则调用 gro_complete 方法,将 gro_list 的包合并成一个大包,同时 checksum 也被更新。在实现中,并没要求 GRO 长时间的去实现聚合,而是在每次 NAPI 轮询操作中,强制传递 GRO 包列表跑到上层协议。GRO 和 LRO 的最大区别在于,GRO 保留了每个接收到的数据包的熵信息,这对于像路由器这样的应用至关重要,并且实现了对各种协议的支持。以 IPv4 的 TCP 为例,匹配的条件有:
源 / 目的地址匹配
TOS/ 协议字段匹配
源 / 目的端口匹配
而很多其它事件将导致 GRO 列表向上层协议传递聚合的数据包,例如 TCP 的 ACK 不匹配或者 TCP 的序列号没有按序等等。
GRO 提供的接口和 LRO 提供的接口非常的类似,但更加的简洁,对于驱动,明确可见的只有 GRO 的收包函数了 , 因为大部分的工作实际是在协议层做掉了:
清单 13、GRO 收包接口
gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb) gro_result_t napi_gro_frags(struct napi_struct *napi)
回页首
小结
从上面的分析,可以看到,Linux 网络性能优化方法,就像一部进化史,但每步的演化,都让解决问题的办法更加的通用,更加的灵活;从 NAPI 到 Newer newer NAPI,从 TSO 到 GSO,从 LRO 到 GRO,都是一个从特例到一个更通用的解决办法的演化,正是这种渐进但连续的演化,让 Linux 保有了如此的活力。
本文来源:不详 作者:佚名