分类:
2012-07-25 14:16:53
接着上篇的ip_fragment函数的分析,本人感觉里面有两个最麻烦的函数,他们是ip_options_fragment(),skb_copy_bits()。
第一个函数是为分段头部添加选项,由于选项中涉及很多的字段,我还没来得及仔细分析,目前就先不把结果给列出来了。。
skb_copy_bits()这个函数出现在分段中的slow_path部分,主要的功能是把原始的packet的依次到新的分段中,别看我只有这么一句话就表述了,但是他的过程是相当麻烦的,是分段中比较麻烦的过程。。
下面请看:
1182 /* Copy some data bits from skb to kernel buffer.
1183 * 2009 5.17开始分析此函数 */
1184 /*
1185 * 传入的参数的含义:
1186 * skb:原始的数据区,即被复制的区域。
1187 * offset:复制的其实位置。
1188 * to:目标区域。
1189 * len:每次复制的数据长度。
1190 * 注:对于此处的to,有一点很重要,就是to指向的每次在调用该函数后必须被复制满,否则就会出错。
1191 * 这一点尤其对于最后一个分段来说,相比较最后一个分段的to指向区域的大小会稍比前面的小,但依旧要
1192 * 被填满,否则会被报错。即在调用该函数的过程中遵循一条原则,分多大,填多少,必须填满!
1193 * ----cdc 09.5.22
1194 */
1195 int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
1196 {
1197 int i, copy; /*copy指的是被复制数据的的长度*/
1198 int start = skb_headlen(skb); /*
1199 返回由head指出的缓存大小(不包括内存页中的数据),对于纯slow_path,start的值是被复制的ip packet的大小
1200 亦可以
1201 是fast_path条件不完全满足的主skb的head指向的大小
1202 */
1203 /*
1204 * 以下的len可以是主skb的len,可以是单个skb的len,亦可以是对于
1205 * fast_path条件不完全满足的主skb的len,该skb的len是所有的skb
1206 * (包括挂载在frag_list上的skb)的len之和
1207 *
1208 */
1209 if (offset > (int)skb->len - len)
1210 goto fault;
1211
1212 /* Copy header. */
1213 if ((copy = start - offset) > 0) { /*判断数据是否复制完毕*/
1214 if (copy > len)
1215 copy = len;
1216 skb_copy_from_linear_data_offset(skb, offset, to, copy); /*若剩余被复制的数据区大于mtu(len)的话,就复制数据到该指定的区域*/
1217 if ((len -= copy) == 0)/*
1218 若copy>=len的话,以上的代码执行完后就在此退出了函数;
1219 若copy
1221 return 0;
1222 /*
1223 * copy
1225 */
1226 offset += copy;/*计算偏移量*/
1227 to += copy;/*指向即将被内存页中的数据填充的区域*/
1228
1229 /*开始用frags[]填充*/
1230 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
1231 int end;
1232
1233 WARN_ON(start > offset + len);
1234
1235 end = start + skb_shinfo(skb)->frags[i].size;/*复制区域的总长度*/
1236 /*子复制第一个页面的内容时offset=start*/
1237 if ((copy = end - offset) > 0) {/*copy指的是当前的页面内容的大小*/
1238 u8 *vaddr;/*页面的虚拟地址*/
1239 /*若页面的内容大于在slow_path中对len的定义,将页面的内容复制到目的skb中*/
1240 if (copy > len)
1241 /*
1242 * 若存在to的区域未被复制满的话,此时的len已经变成了,剩下未被复制
1243 * 区域的长度,而并非是传入整个函数的原始len,因为在在最开始复制缓存区域的时候执行了:
1244 *if ((len-=copy)==0)这段判断代码,此时的len的值已经发生改变,即to所指的skb中剩余区域长度
1245 */
1246 copy = len;
1247
1248 vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]);/*当前被页面的虚拟地址*/
1249 memcpy(to,
1250 vaddr + skb_shinfo(skb)->frags[i].page_offset+
1251 offset - start, copy); /*此处若是复制的是第一个页面,offset=start*/
1252 kunmap_skb_frag(vaddr);
1253
1254 if ((len -= copy) == 0)/*若此时的skb被填充满,则拷贝成功,退出该函数*/
1255 return 0;
1256 /*若skb依然未被填充满*/
1257 offset += copy;
1258 to += copy;
1259 }
1260 start = end;/*赋值,改变被复制的数据长度*/
1261 }/*页面填充循环结尾*/
1262 /*
1263 * 以下是针对fast_path判断条件不满足,需进行slow_path的方式
1264 * 若前一个skb的数据被复制完毕后,仍无法填充满当前to指向的复制
1265 * 目的地skb的话,就用后续的frag_list指向的后续的skb来填充
1266 */
1267 if (skb_shinfo(skb)->frag_list) {
1268 struct sk_buff *list = skb_shinfo(skb)->frag_list;
1269
1270 for (; list; list = list->next) {
1271 int end;
1272
1273 WARN_ON(start > offset + len);
1274 /*注意此处的len和主skb的len在概念上是不一样,他只代表当前skb的缓存+内存页*/
1275 end = start + list->len;
1276 if ((copy = end - offset) > 0) {
1277 if (copy > len)
1278 copy = len;/*注意此处的len也类似在页面复制,也是由于每次的复制完毕后的判
1279 断语句if ((len -= copy)==0),而使len的值发生改变
1280 */
1281 if (skb_copy_bits(list, offset - start,
1282 to, copy))/*此处是函数的递归调用,若当前的to指向的skb区域被填充满的话,函数返回0*/
1283 goto fault;
1284 if ((len -= copy) == 0)/*判断是否已经填充满*/
1285 return 0;
1286 offset += copy;
1287 to += copy;
1288 }
1289 start = end; /*end,已经被复制的数据总长度,start的意思是下次复制的起始长度*/
1290 }/*for循环结尾*/
1291 }/*frag_list遍历结尾*/
1292 if (!len) /*
1293 若最后一个skb无法填满,即len>0,则说明在为skb分配空间的时候出错,
1294 skb的空间分配大了。这样的话,收端在检查的时候就会把最后一个分段
1295 丢弃,这样的话将导致在收端无法完成重组,数据发送失败
1296 */
1297 return 0;
1298
1299 fault: /*若最后一个的skb无法填满,即len>0的话,出错*/
1300 return -EFAULT;
1301 }/*skb_copy_bits函数结束。2009 5.19*/