skb 操作函数skb_linearize
skb_linearize:分配新的skb->data,将旧的skb->data、skb_shinfo(skb)->frags、skb_shinfo(skb)->frag_list中的内容拷贝到新skb->data的连续内存空间中,释放frags或frag_list
//其中frags用于支持分散聚集IO,frags_list用于支持数据分片 1.1 int __skb_linearize(struct sk_buff *skb, int gfp_mask) { unsigned int size; u8 *data; long offset; struct skb_shared_info *ninfo; int headerlen = skb->data - skb->head; int expand = (skb->tail + skb->data_len) - skb->end; //如果此skb被共享 if (skb_shared(skb)) BUG();//产生BUG oops //还需要的内存大小 if (expand <= 0) expand = 0; //新申请的skb的大小 size = skb->end - skb->head + expand; //将size对齐到SMP_CACHE_BYTES size = SKB_DATA_ALIGN(size); //分配物理上联系的内存 data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask); if (!data) return -ENOMEM; //拷贝 if (skb_copy_bits(skb, -headerlen, data, headerlen + skb->len)) BUG(); //初始化skb的skb_shared_info结构 ninfo = (struct skb_shared_info*)(data + size); atomic_set(&ninfo->dataref, 1); ninfo->tso_size = skb_shinfo(skb)->tso_size; ninfo->tso_segs = skb_shinfo(skb)->tso_segs; //fraglist为NULL ninfo->nr_frags = 0; ninfo->frag_list = NULL; offset = data - skb->head; //释放之前skb的data skb_release_data(skb); //将skb指向新的data skb->head = data; skb->end = data + size; //重新初始化新skb的各个报头指针 skb->h.raw += offset; skb->nh.raw += offset; skb->mac.raw += offset; skb->tail += offset; skb->data += offset; skb->cloned = 0; skb->tail += skb->data_len; skb->data_len = 0; return 0; } 1.2 SKB_DATA_ALIGN(X) (((X) + (SMP_CACHE_BYTES - 1)) & \ ~(SMP_CACHE_BYTES - 1)) //将skb中起始offset的内容拷贝到to中,拷贝长度为len 1.3 int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len) { int i, copy; //skb->len-skb->data_len,得到skb->head到skb->end之间的数据量 int start = skb_headlen(skb); //偏移量+len > skb->len,说明可供拷贝的数据量不够 if (offset > (int)skb->len - len) goto fault; //计算需要拷贝的数据量 if ((copy = start - offset) > 0) { if (copy > len) copy = len; //拷贝 memcpy(to, skb->data + offset, copy); if ((len -= copy) == 0)//拷贝量=需要拷贝的长度 return 0; offset += copy;//更新偏移量 to += copy; } //接下来的数据从skb_shinfo的frags数组中进行拷贝 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { int end; //遍历frags end = start + skb_shinfo(skb)->frags[i].size; if ((copy = end - offset) > 0) { u8 *vaddr; if (copy > len) copy = len; //映射skb的frag到内核地址空间 vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]); //拷贝 memcpy(to, vaddr + skb_shinfo(skb)->frags[i].page_offset+ offset - start, copy); //解除映射 kunmap_skb_frag(vaddr); if ((len -= copy) == 0) return 0; offset += copy; to += copy; } start = end; } //从skb的frag_list中拷贝 if (skb_shinfo(skb)->frag_list) { struct sk_buff *list = skb_shinfo(skb)->frag_list; for (; list; list = list->next) { int end; BUG_TRAP(start <= offset + len); end = start + list->len; if ((copy = end - offset) > 0) { if (copy > len) copy = len; //递归调用 if (skb_copy_bits(list, offset - start, to, copy)) goto fault; if ((len -= copy) == 0) return 0; offset += copy; to += copy; } start = end; } } if (!len) return 0; fault: return -EFAULT; }
//保证skb->data 到 skb->tail之间有len长度的数据 2.1 static inline int pskb_may_pull(struct sk_buff *skb, unsigned int len) { //skb->data 到 skb->tail之间的数据足够len长度 if (likely(len <= skb_headlen(skb))) return 1; //len长度超过skb总长度 if (unlikely(len > skb->len)) return 0; //移动后边的数据到skb->data中 return __pskb_pull_tail(skb, len-skb_headlen(skb)) != NULL; } //调用流程pskb_may_pull->__pskb_pull_tail //delta为需要从frags或者frag_list向前移动的数据量 2.2 unsigned char *__pskb_pull_tail(struct sk_buff *skb, int delta) { //eat为去除当前skb可用内存,还需要多少内存 int i, k, eat = (skb->tail + delta) - skb->end; //判断当前skb是否被克隆 if (eat > 0 || skb_cloned(skb)) { //对sk_buff重新分配头 if (pskb_expand_head(skb, 0, eat > 0 ? eat + 128 : 0, GFP_ATOMIC)) return NULL; } //从skb的offset(skb->tail),拷贝delta个字节到skb->tail之后 if (skb_copy_bits(skb, skb_headlen(skb), skb->tail, delta)) BUG(); //没有分段 if (!skb_shinfo(skb)->frag_list) goto pull_pages; //由于数据已经拷贝到了skb->data中,因此需要释放frags,frag_list中被拷贝过的数据 //计算从frags数组中拷贝的数据量 eat = delta; for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { //寻找到满足eat这么多数据量的最后一个page if (skb_shinfo(skb)->frags[i].size >= eat) //在frags数组中的数据量可以满足delta时,则只释放frags即可 goto pull_pages; eat -= skb_shinfo(skb)->frags[i].size; } //eat仍不为0,说明从frag_list中进行了拷贝,释放frag_list if (eat) { struct sk_buff *list = skb_shinfo(skb)->frag_list; struct sk_buff *clone = NULL; struct sk_buff *insp = NULL; do { //list为null,说明数据量不够 if (!list) BUG(); //当前skb的长度小于需要的长度 if (list->len <= eat) { //找到下一个skb eat -= list->len; //list指向下一个需要的skb list = list->next; //insp指向当前的skb insp = list; } else { //此时insp指向前一个skb //说明当前skb可以满足需要的数据量 if (skb_shared(list)) {//但是当前skb被共享 clone = skb_clone(list, GFP_ATOMIC);//对最后那个拷贝不完全的skb,进行克隆 if (!clone) return NULL; //list指向当前被克隆的的skb //insp指向下一个skb insp = list->next; list = clone; } else { //list与insp指向当前的skb insp = list; } //修改最后一个skb,移动指针,删除掉被拷贝的数据 if (!pskb_pull(list, eat)) { if (clone) kfree_skb(clone);//递减clone的引用计数 return NULL; } break; } } while (eat); //list指向frag_list头 //直到list遍历到数据量足够的最后一个skb while ((list = skb_shinfo(skb)->frag_list) != insp) { skb_shinfo(skb)->frag_list = list->next; //释放当前的skb kfree_skb(list);//递减当前skb的引用技术,如果引用计数=0,则释放list } //说明最后一个skb只被拷贝了一部分,将此skb挂到frag_list头 if (clone) { clone->next = list; skb_shinfo(skb)->frag_list = clone; } } pull_pages: eat = delta; k = 0; //释放frags中的page for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { if (skb_shinfo(skb)->frags[i].size <= eat) { put_page(skb_shinfo(skb)->frags[i].page); eat -= skb_shinfo(skb)->frags[i].size; } else { skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i]; if (eat) { skb_shinfo(skb)->frags[k].page_offset += eat; skb_shinfo(skb)->frags[k].size -= eat; eat = 0; } k++; } } skb_shinfo(skb)->nr_frags = k; skb->tail += delta; skb->data_len -= delta; return skb->tail; }
//skb->users指定skb被引用的个数 3.1 static inline int skb_shared(const struct sk_buff *skb) { return atomic_read(&skb->users) != 1; }
关于skb data操作函数有:
- skb_put:在tail偏移后面扩展n个字节的空间,但不会超过end偏移的限制空间。返回扩展空间的第一个字节指针
- skb_push:在data指针前面扩展n个字节的空间,但不能超过head指针限制。返回新的data指针
- skb_pull:将data指针后移n个字节,将来再使用skb_push操作就会将这n个字节的空间内容覆盖,返回新的data指针
- skb_reserve:在缓冲区头部保留n个字节的空间,这个操作只允许对空的缓冲区进行操作
分配内存
定义在skbuff.c源文件猪的alloc_skb是分配缓冲区的主要函数。数据缓冲区和sk_buff结构是两个不同的实例,建议一个缓冲区会涉及两次内存分配,分配数据缓冲区和分配sk_buff结构。
alloc_skb通过调研kmem_cache_alloc函数从一个缓存中取得一个sk_buff结构,然后调研kmalloc分配一个数据缓冲区:
在调用kmalloc前,size参数会被SKB_DATA_ALIGN宏进行调整强制对齐。alloc_skb函数会对sk_buff结构中的元素进行参数初始化,其中几个参数的值如下图:
在数据缓冲区的底端的skb_shared_info数据结构主要用于处理一些IP分片。
dev_alloc_skb是由设备驱动程序使用的缓冲区分配函数,应该是在中断模式中执行。此函数是alloc_skb的一个封装函数,为了优化在申请大小上加了16个字节。
而且由于此函数是由中断事件处理函数调用的,所以会要求原子操作(GFP_ATOMIC):
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!!
但行好事 莫问前程
--身高体重180的胖子
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2021-04-23 tcp关闭 close-wait last-ack fin-wait1 fin-wait2 closing 状态处理
2021-04-23 IPC-信号量 以及pthread-mutex