免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1264 | 回复: 6
打印 上一主题 下一主题

[网络子系统] 再谈ip_append_data和Scatter/Gather模式的问题!【已解决】 [复制链接]

论坛徽章:
1
IT运维版块每日发帖之星
日期:2015-11-17 06:20:00
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2016-09-24 11:15 |只看该作者 |倒序浏览
本帖最后由 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模式的了,请大家解惑







论坛徽章:
1
IT运维版块每日发帖之星
日期:2015-11-17 06:20:00
2 [报告]
发表于 2016-09-27 22:13 |只看该作者
经过3天来的思考,终于明白了我自己提出的这个问题。因为c语言可以允许数组下标为负数,所以不会出错,原因就是如此简单,之前一直没有想到内核也会有数组下标为负数的情况出现!

另外,对于第997行,我认为是多个进程同时使用一个sk进行udp数据发送时就会产生这一现象。

论坛徽章:
1
IT运维版块每日发帖之星
日期:2015-11-17 06:20:00
3 [报告]
发表于 2016-09-28 11:33 |只看该作者
jiufei19 发表于 2016-09-27 22:13
经过3天来的思考,终于明白了我自己提出的这个问题。因为c语言可以允许数组下标为负数,所以不会出错,原因 ...

另外,对于第997行,我认为是多个进程同时使用一个sk进行udp数据发送时就会产生这一现象
----------------------
这一点我还是觉得有点不正确,不知大家能否帮我解决此第997行的问题

论坛徽章:
20
程序设计版块每日发帖之星
日期:2015-08-17 06:20:00程序设计版块每日发帖之星
日期:2016-07-16 06:20:00程序设计版块每日发帖之星
日期:2016-07-18 06:20:00每日论坛发贴之星
日期:2016-07-18 06:20:00黑曼巴
日期:2016-12-26 16:00:3215-16赛季CBA联赛之江苏
日期:2017-06-26 11:05:5615-16赛季CBA联赛之上海
日期:2017-07-21 18:12:5015-16赛季CBA联赛之青岛
日期:2017-09-04 17:32:0515-16赛季CBA联赛之吉林
日期:2018-03-26 10:02:16程序设计版块每日发帖之星
日期:2016-07-15 06:20:0015-16赛季CBA联赛之江苏
日期:2016-07-07 18:37:512015亚冠之萨济拖拉机
日期:2015-08-17 12:21:08
4 [报告]
发表于 2016-10-04 20:36 |只看该作者
   990             struct page *page = sk->sk_sndmsg_page;
   991             int off = sk->sk_sndmsg_off;

我看的版本,连这两行都没有,page和off是函数的局部变量。

不过有了这两行就好理解多了,使用全局量存储状态,下次函数调用的时候尝试重用这个page(上次可能没有填满最后一个frag page)以及这个槽位。
但是两次调用之间可能穿插了其它的skb操作,nr_frags有可能发生变化,那么就得使用新的槽位来表示page。

论坛徽章:
20
程序设计版块每日发帖之星
日期:2015-08-17 06:20:00程序设计版块每日发帖之星
日期:2016-07-16 06:20:00程序设计版块每日发帖之星
日期:2016-07-18 06:20:00每日论坛发贴之星
日期:2016-07-18 06:20:00黑曼巴
日期:2016-12-26 16:00:3215-16赛季CBA联赛之江苏
日期:2017-06-26 11:05:5615-16赛季CBA联赛之上海
日期:2017-07-21 18:12:5015-16赛季CBA联赛之青岛
日期:2017-09-04 17:32:0515-16赛季CBA联赛之吉林
日期:2018-03-26 10:02:16程序设计版块每日发帖之星
日期:2016-07-15 06:20:0015-16赛季CBA联赛之江苏
日期:2016-07-07 18:37:512015亚冠之萨济拖拉机
日期:2015-08-17 12:21:08
5 [报告]
发表于 2016-10-04 20:54 |只看该作者
-1的下标看起来有点问题啊,楼主怎么理解的?
这里假设sendmsg_page非空的话==>nr_frags至少为1?(看起来是合理的假设?)

论坛徽章:
1
IT运维版块每日发帖之星
日期:2015-11-17 06:20:00
6 [报告]
发表于 2016-10-06 21:38 |只看该作者
回复 5# nswcfd


-1数组下标没有问题呀,因为后面的代码并不会对此-1下标数组元素进行操作,而由于此时sk->sk_sndmsg_page会为NULL,于是后面就直接跳转到申请page部分代码了

论坛徽章:
20
程序设计版块每日发帖之星
日期:2015-08-17 06:20:00程序设计版块每日发帖之星
日期:2016-07-16 06:20:00程序设计版块每日发帖之星
日期:2016-07-18 06:20:00每日论坛发贴之星
日期:2016-07-18 06:20:00黑曼巴
日期:2016-12-26 16:00:3215-16赛季CBA联赛之江苏
日期:2017-06-26 11:05:5615-16赛季CBA联赛之上海
日期:2017-07-21 18:12:5015-16赛季CBA联赛之青岛
日期:2017-09-04 17:32:0515-16赛季CBA联赛之吉林
日期:2018-03-26 10:02:16程序设计版块每日发帖之星
日期:2016-07-15 06:20:0015-16赛季CBA联赛之江苏
日期:2016-07-07 18:37:512015亚冠之萨济拖拉机
日期:2015-08-17 12:21:08
7 [报告]
发表于 2016-10-20 20:48 |只看该作者
是的,没有问题,之前看错了,以为fill_page的时候用到了frag,实际上没有。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP