- 论坛徽章:
- 1
|
本帖最后由 jiufei19 于 2016-10-20 20:51 编辑
之前曾发了一帖http://bbs.chinaunix.net/thread-4253698-1-1.html,谈了对ip_append_data函数处理过程涉及Scatter/Gather的疑问,现在回头仔细阅读了下代码,发现之前的疑问没有描述太精确,下面我结合代码把发现的问题描述下,希望能得到大家的帮助。
在列出关键代码前,先来看看《Understanding Linux Network Internals》一书,其中第21章的关于SG方式的描述,原书中第21章有如下两句:
1、When Scatter/Gather I/O is in use, the memory area to which skb->data points is used only the first time. The following chunks of data are copied into pages of memory allocated specifically for this purpose.
2、Figure 21-5(a) shows memory use after the first call and Figure 21-5(b) shows it after the second call, when Scatter/Gather I/O is enabled
注意上面红色字体的内容,似乎有问题,下面我列出内核版本2.6.23中ip_append_data函数的关键代码,然后设定某种场景来看看该函数是如何做的
768 int ip_append_data(struct sock *sk,
769 int getfrag(void *from, char *to, int offset, int len,
770 int odd, struct sk_buff *skb),
771 void *from, int length, int transhdrlen,
772 struct ipcm_cookie *ipc, struct rtable *rt,
773 unsigned int flags)
774 {
... ...
866 if ((skb = skb_peek_tail(&sk->sk_write_queue)) == NULL)
867 goto alloc_new_skb;
868
869 while (length > 0) {
870 /* Check if the remaining data fits into current packet. */
871 copy = mtu - skb->len;
872 if (copy < length)
873 copy = maxfraglen - skb->len;
874 if (copy <= 0) {
... ...
881 alloc_new_skb:
882 skb_prev = skb;
883 if (skb_prev)
884 fraggap = skb_prev->len - maxfraglen;
885 else
886 fraggap = 0;
... ...
892 datalen = length + fraggap;
893 if (datalen > mtu - fragheaderlen)
894 datalen = maxfraglen - fragheaderlen;
895 fraglen = datalen + fragheaderlen;
896
897 if ((flags & MSG_MORE) &&
898 !(rt->u.dst.dev->features&NETIF_F_SG))
899 alloclen = mtu;
900 else
901 alloclen = datalen + fragheaderlen;
... ...
911 /*
... * 此处分配一个skb,代码略去
924 */
...
954 copy = datalen - transhdrlen - fraggap;
955 if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
... ...
959 }
... ...
970 __skb_queue_tail(&sk->sk_write_queue, skb);
971 continue;
972 }
...
974 if (copy > length)
975 copy = length;
976
977 if (!(rt->u.dst.dev->features&NETIF_F_SG)) {
978 unsigned int off;
979
980 off = skb->len;
981 if (getfrag(from, skb_put(skb, copy),
982 offset, copy, off, skb) < 0) {
983 __skb_trim(skb, off);
984 err = -EFAULT;
985 goto error;
986 }
987 } else {
988 int i = skb_shinfo(skb)->nr_frags;
989 skb_frag_t *frag = &skb_shinfo(skb)->frags[i-1];
990 struct page *page = sk->sk_sndmsg_page;
991 int off = sk->sk_sndmsg_off;
992 unsigned int left;
993
994 if (page && (left = PAGE_SIZE - off) > 0) {
995 if (copy >= left)
996 copy = left;
997 if (page != frag->page) {
... ...
现在假定是首次调用udp_sendmsg发送length字节数据,因此ip_append_data函数也是首次调用,并且我们再假定length>mtu,网卡支持SG模式,带MSG_MORE标记。下面我描述下上述代码执行过程中的问题。
1、第866行if条件为真,于是跳转到881行
2、由于目前还没有skb,故执行第886行fraggap为0
3、由于我们假定length>mtu,故datalen将被设置为maxfraglen - fragheaderlen,并且fraglen就等于maxfraglen
4、由于网卡支持SG,因此执行第901行,alloclen按实际大小分配,此时也就是maxfraglen
5、执行第911-924行,申请一个skb
6、第954行计算出本次要拷贝的数据长度copy,其实就是maxfraglen中去掉 transhdrlen这个udp首部的长度
7、第995行调用getfrag指向的函数进行实际数据拷贝,拷贝的目标地址为data + transhdrlen,即之前确定好的当前skb的主缓存中的目标位置
从上面的步骤来看,的确符合《Understanding Linux Network Internals》一书中所说:the memory area to which skb->data points is used only the first time,然而我们继续刚才的场景来分析代码就会发现这句话实际好像有问题。
8、因为之前假定length大于mtu,所以本次数据切割操作尚未完成,第970行将刚才分配的skb挂接到sk_write_queue队列中,然后执行第971行返回到第869行while
9、此时length>0成立,于是执行第871行copy = mtu - skb->len,显然此时copy大于0
10、此时我们假定copy小于length,即余下数据尚不能全部填入当前skb的空闲区,则执行第873行,而我们刚才在第4步时已经按实际大小分配了skb,因此此时copy = maxfraglen - skb->len一定为0,这样就又重复我们之前描述的过程,重新分配了一个新的skb,且再假定当前length的长度恰好和首次我们分析的具有一样的特征,则第2次数据拷贝仍然是向第2个skb的主缓存区进行复制,这个过程可以不断重复,所以《Understanding Linux Network Internals》一书所说的is used only the first time,并不准确!
但是,让我们再换一个length的场景来看看,假定首次执行ip_append_data时,length的长度比maxfraglen还要小,且同样支持SG模式,则:
4、由于网卡支持SG,因此执行第901行,alloclen按实际大小分配,此时也就是小于maxfraglen
5、执行第911-924行,申请一个skb
6、第954行计算出本次要拷贝的数据长度copy,其实就是当前实际长度中去掉 transhdrlen这个udp首部的长度
7、第995行调用getfrag指向的函数进行实际数据拷贝,拷贝的目标地址为data + transhdrlen,即之前确定好的当前skb的主缓存中的目标位置
8、第970行将刚才分配的skb挂接到sk_write_queue队列中,然后执行第971行返回到第869行while,此时while条件不成立,结束本次ip_append_data调用。
由于前次udp_sendmsg带有MSG_MORE标记,接下来我们来看看当第2次ip_append_data函数又被调用时,又有length字节数据等待被切割,则SG是如何起作用的:
1、因为此时sk_write_queue队列中已经有一个skb存在,且其skb->len尚不到maxfraglen长度,于是第866行不成立,执行第869行,while条件成立
2、第873行计算出前一个skb中距离maxfraglen还有copy空间可以填充新数据
3、第874行if条件不成立,跳转到第974行,此时我们假定copy>length
4、由于设备支持SG,所以从第987行开始执行,则第988行的结果该如何确定???如果该语句无法正确处理,则后续向page中拷贝数据的操作就无处谈起了!
另外,第997行这个if条件在什么场景下会被满足,我也没有理解!!!
至此,我就无法理解代码是如何支持SG模式的了,请大家解惑
|
|