6. Sk_buffer 的秘密
当调用 alloc_skb() 构造 SKB 和 data buffer时,需要的 buffer 大小是这样计算的:
data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
除了指定的 size 以外,还包括一个 struct skb_shared_info 结构的空间大小。也就是说,当调用 alloc_skb(size) 要求分配 size 大小的 buffer 的时候,同时还创建了一个 skb_shared_info 。
这个结构定义如下:
struct skb_shared_info {
atomic_t dataref;
unsigned int nr_frags;
unsigned short tso_size;
unsigned short tso_segs;
struct sk_buff *frag_list;
skb_frag_t frags[MAX_SKB_FRAGS];
};
我们只要把 end 从 char* 转换成skb_shared_info* ,就能访问到这个结构
Linux 提供一个宏来做这种转换:
#define skb_shinfo(SKB) ((struct skb_shared_info *)((SKB)->end))
那么,这个隐藏的结构用意何在?
它至少有两个目的:
1、 用于管理 paged data
2、 用于管理分片
接下来分别研究 sk_buffer 对paged data 和分片的处理。
7. 对 paged data 的处理
某些情况下,希望能将保存在文件中的数据,通过 socket 直接发送出去,这样,避免了把数据先从文件拷贝到缓冲区,从而提高了效率。
Linux 采用一种 “paged data” 的技术,来提供这种支持。这种技术将文件中的数据直接被映射为多个 page。
Linux 用 struct skb_frag_strut 来管理这种 page:
typedef struct skb_frag_struct skb_frag_t;
struct skb_frag_struct {
struct page *page;
__u16 page_offset;
__u16 size;
};
并在shared info 中,用数组 frags[] 来管理这些结构。
如此一来,sk_buffer 就不仅管理着一个 buffer 空间的数据了,它还可能通过 share info 结构管理一组保存在 page 中的数据。
在采用 “paged data” 时,data_len 成员派上了用场,它表示有多少数据在 page 中。因此,
如果 data_len 非0,这个 sk_buffer 管理的数据就是“非线性”的。
Skb->len – skb->data_len 就是非 paged 数据的长度。
在有 “paged data” 情况下, skb_put()就无法使用了,必须使用 pskb_put() 。。。
8. 对分片的处理
9. SKB 的管理函数
9.1. Data Buffer 的基本管理函数
· unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
“推”入数据
在 buffer 的结束位置,增加数据,len是要增加的长度。
这个函数有两个限制,需要调用者自己注意,否则后果由调用者负责
1)、不能用于 “paged data” 的情况
这要求调用者自己判断是否为 “paged data” 情况
2)、增加新数据后,长度不能超过 buffer 的实际大小。
这要求调用者自己计算能增加的数据大小
· unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
“压”入数据
从 buffer 起始位置,增加数据,len 是要增加的长度。
实际就是将新的数据“压”入到 head room 中
· unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
“拉”走数据
从 buffer 起始位置,去除数据, len 是要去除的长度。
如果 len 大于 skb->len,那么,什么也不做。
在处理接收到的 packet 过程中,通常要通过 skb_pull() 将最外层的协议头去掉;例如当网络层处理完毕后,就需要将网络层的 header 去掉,进一步交给传输层处理。
· void skb_trim(struct sk_buff *skb, unsigned int len)
调整 buffer 的大小,len 是调整后的大小。
如果 len 比 buffer 小,则不做调整。
因此,实际是将 buffer 底部的数据去掉。
对于没有 paged data 的情况,很好处理;
但是有 paged data 情况下,则需要调用 __pskb_trim() 来进行处理。
9.2. “Paged data” 和 分片的管理函数
· char *pskb_pull(struct sk_buff *skb, unsigned int len)
“拉“走数据
如果 len 大于线性 buffer 中的数据长度,则调用__pskb_pull_tail() 进行处理。
(Q:最后, return skb->data += len; 是否会导致 skb->data 超出了链头范围?)
· int pskb_may_pull(struct sk_buff *skb, unsigned int len)
在调用 skb_pull() 去掉外层协议头之前,通常先调用此函数判断一下是否有足够的数据用于“pull”。
如果线性 buffer足够 pull,则返回1;
如果需要 pull 的数据超过 skb->len,则返回0;
最后,调用__pskb_pull_tail() 来检查 page buffer 有没有足够的数据用于 pull。
· int pskb_trim(struct sk_buff *skb, unsigned int len)
将 Data Buffer 的数据长度调整为 len
在没有 page buffer 情况下,等同于 skb_trim();
在有 page buffer 情况下,需要调用___pskb_trim() 进一步处理。
· int skb_linearize(struct sk_buff *skb, gfp_t gfp)
· struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)
‘clone’ 一个新的 SKB。新的 SKB 和原有的 SKB 结构基本一样,区别在于:
1)、它们共享同一个 Data Buffer
2)、它们的 cloned 标志都设为1
3)、新的 SKB 的 sk 设置为空
(Q:在什么情况下用到克隆技术?)
· struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask)
· struct sk_buff *pskb_copy(struct sk_buff *skb, gfp_t gfp_mask)
· struct sk_buff *skb_pad(struct sk_buff *skb, int pad)
· void skb_clone_fraglist(struct sk_buff *skb)
· void skb_drop_fraglist(struct sk_buff *skb)
· void copy_skb_header(struct sk_buff *new, const struct sk_buff *old)
· pskb_expand_head(struct sk_buff *skb, int nhead, int ntail, gfp_t gfp_mask)
· int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
· int skb_store_bits(const struct sk_buff *skb, int offset, void *from, int len)
· struct sk_buff *skb_dequeue(struct sk_buff_head *list)
· struct sk_buff *skb_dequeue(struct sk_buff_head *list)
· void skb_queue_purge(struct sk_buff_head *list)
· void skb_queue_purge(struct sk_buff_head *list)
· void skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk)
· void skb_unlink(struct sk_buff *skb, struct sk_buff_head *list)
· void skb_append(struct sk_buff *old, struct sk_buff *newsk, struct sk_buff_head *list)
· void skb_insert(struct sk_buff *old, struct sk_buff *newsk, struct sk_buff_head *list)
· int skb_add_data(struct sk_buff *skb, char __user *from, int copy)
· struct sk_buff *skb_padto(struct sk_buff *skb, unsigned int len)
· int skb_cow(struct sk_buff *skb, unsigned int headroom)
这个函数要对 SKB 的 header room 调整,调整后的 header room 大小是 headroom.
如果 headroom 长度超过当前header room 的大小,或者 SKB 被 clone 过,那么需要调整,方法是:
分配一块新的 data buffer 空间,SKB 使用新的 data buffer 空间,而原有空间的引用计数减1。在没有其它使用者的情况下,原有空间被释放。
· struct sk_buff *dev_alloc_skb(unsigned int length)
· void skb_orphan(struct sk_buff *skb)
· void skb_reserve(struct sk_buff *skb, unsigned int len)
· int skb_tailroom(const struct sk_buff *skb)
· int skb_headroom(const struct sk_buff *skb)
· int skb_pagelen(const struct sk_buff *skb)
· int skb_headlen(const struct sk_buff *skb)
· int skb_is_nonlinear(const struct sk_buff *skb)
· struct sk_buff *skb_share_check(struct sk_buff *skb, gfp_t pri)
如果skb 只有一个引用者,直接返回 skb否则 clone 一个 SKB,将原来的 skb->users 减1,返回新的 SKB需要特别留意 pskb_pull() 和 pskb_may_pull() 是如何被使用的:
1)、在接收数据的时候,大量使用 pskb_may_pull(),其主要目的是判断 SKB 中有没有足够的数据,例如在 ip_rcv() 中:
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto inhdr_error;
iph = skb->nh.iph;
它的目的是拿到 IP header,但取之前,先通过 pskb_may_pull() 判断一下有没有足够一个 IP header 的数据。
2)、当我们构造 IP 分组的时候,对于数据部分,通过 put向下扩展空间(如果一个sk_buffer 不够用怎么分片?);对于 传输层、网络层、链路层的头,通过 push 向上扩展空间;
3)、当我们解析 IP 分组的时候,通过 pull(),从头开始,向下压缩空间。
因此,put 和 push 主要用在发送数据包的时候;而pull 主要用在接收数据包的时候。
10. 各种 header
union {
struct tcphdr *th;
struct udphdr *uh;
struct icmphdr *icmph;
struct igmphdr *igmph;
struct iphdr *ipiph;
struct ipv6hdr *ipv6h;
unsigned char *raw;
} h;
union {
struct iphdr *iph;
struct ipv6hdr *ipv6h;
struct arphdr *arph;
unsigned char *raw;
} nh;
union {
unsigned char *raw;
} mac;
详细解决方案
sk_buffer 详细分析 (下)
热度:18 发布时间:2024-01-09 09:56:40.0
相关解决方案
- Material Design学习之 Sliders(详细分析,悬空气球显示进度值,捎带Eclipse可以jar)
- Android GSM驱动模块(rild)详细分析(1)基本架构及初始化
- Android GSM驱动模块(rild)详细分析(2)request流程
- Android GSM驱动模块(rild)详细分析(3)response流程
- Java中的 和 和 详细分析
- ASSO 布尔矩阵分解算法 详细分析
- 爬虫实战 链家房源爬取(BeautifulSoup) 详细分析 2020最新
- String.intern()详细分析,JDK1.8
- 【详细分析】Java的String和StringBuffer和StringBuilder的区别
- (详细分析)python PyQt5图形界面编程(含pyqtgraph画3D散点图、父子窗口间传递信息、pyinstaller打包可执行exe文件)
- 为程序添加版本自动更新功能(转+详细分析)
- [详细分析]Java-ArrayList源码全解析
- 剑指offer--旋转数组的最小数字(详细分析+特殊情况讨论)
- sk_buffer 详细分析 (下)
- sk_buffer 详细分析(上)
- 恶意代码分析实战Lab03——04.exe(详细分析)
- 区间DP之 能量项链 (详细分析)
- 【题解】模拟、字符串 之 字符串的展开(详细分析)
- 【题解】模拟 之 神奇的幻方(详细分析)
- 详细分析 vue-admin-template
- Hive-窗口函数,详细分析,案例实践
- 详细分析 Java 中启动线程的正确和错误方式