内核版本: 2.6.19, maybe
简介
struct sk_buff
- 存储一个封包 (packet)
- 所有网络分层都会使用这个结构存储
- 报头
- 有关用户数据的信息(有效载荷)
- 用来协调其工作的其它内部信息
struct net_device
- 在 Linux 内核中每种网络设备都有这个数据结构表示,包括软硬件的配置信息
struct sock
- 存储套接字的网络信息
套接字缓冲区:sk_buff 结构
已接收或正要传输的数据的报头
定义在 include/linux/skbuff.h 头文件中
网络选项以及内核结构
- 只有在编译期间定义了
CONFIG_NET_SCHED符号,字段tc_index才会是此数据结构的一部分
1 | struct sk_buff { |
布局字段
- 内核在一个双向链表中维护所有的
sk_buff结构,但该表的组织比传统的双向链表更复杂- 每个
sk_buff结构必须能够迅速找出整个表的头。为了实现这项需求,在表的开端额外增加一个sk_buff_head结构作为 dummy element
- 每个
1 | struct sk_buff_head { |

1 | struct sk_buff { |


char cb[48]
私有信息的存储空间,为每一层内部使用起维护的作用
在 sk_buff 结构内静态分配
TCP 使用这个空间存储 tcp_skb_cb 数据结构
1 | struct tcp_skb_cb { |
- 在每一层的代码中通过宏进行访问
1 |
TCP 使用 tcp_transmit_skb 函数把一个数据段压入 IP 层以便于传输
管理函数
include/linux/skbuff.h 和 net/core/skbuff.c 文件中几乎所有函数都有两个版本
do_something:wrapper 函数,增加了额外的合理性检查,或在调用第二个函数前后加入上锁机制__do_something
分配内存:alloc_skb
建立一个缓冲区会涉及到两次内存分配
- 分配缓冲区
- 分配 sk_buff 结构
- alloc_skb
1 | /* Get the HEAD */ |

分配内存:dev_alloc_skb
- 由设备驱动程序使用的缓冲区分配函数,应该在中断模式中执行
- 只是一个包裹 alloc_skb 的函数
- 要求原子操作
1 | static inline struct sk_buff *dev_alloc_skb(unsigned int length) |
释放内存:kfree_skb
- 释放一个缓冲区,使其返回缓冲池(缓存)
- 当
skb->users计数器为 1 时,才会释放一个缓冲区;否则,递减计数器

释放内存:dev_kfree_skb
1 |
数据预留及对齐:skb_reserve, skb_put, skb_push, skb_pull

几种 Ethernet 驱动程序的接收函数,把任何数据存储到刚分配的缓冲区之前,都会使用下列命令:
1 | skb_reserve(skb, 2); |


当缓冲区往下传播经过网络协议栈时,每个协议都会把 skb->data 往下传,并将其报头拷贝进来,然后更新 skb->len。
skb_reserve 函数没有真正把任何东西移入数据缓冲区,只是更新两个指针。
1 | static inline void skb_reserve(struct sk_buff *skb, int len) |
skb_push 把一个数据块添加到缓冲区的开端
skb_put 把一个数据块添加到缓冲区的尾端
skb_pull 通过 head 指针向前移动,把一个数据块从缓冲区的头部删除。
skb_shared_info 结构和 skb_shinfo 函数
如图 2-5 所示,数据缓冲区尾端有个名为 skb_shared_info 的数据结构,用以保持此数据区块的附加信息。
1 | struct skb_shared_info { |
skb_is_nonlinear函数:检查缓冲区是否为片段skb_linearize函数:把几个片段合压(collapse)成一个均一的(single flat)缓冲区
sk_buff 结构中没有指向 skb_shared_info 数据结构的字段,必须使用返回 end 指针的 skb_shinfo 宏:
1 |
skb_clone
skb_clone 函数:只拷贝 sk_buff 结构,然后使用引用计数,以免过早释放共享的数据块。
skb_buff的克隆没有链接到任何表(list),也没有引用套接字的拥有者skb->cloned在克隆和原有的缓冲区都置为 1- 克隆的
skb->users置为 1 - 对包含数据的缓冲区的引用数目 (dataref) 则会递增

也可用于检查一个 skb 缓冲区的克隆状态。
skb_share_check
检查引用计数 skb_users,当该缓冲区是共享时,可以克隆该缓冲区。
skb_copy && skb_copy
当一个缓冲区被克隆时,数据区块的内容不能修改,访问该数据的代码不需要加锁。
只需修改介于
skb->start和skb->end的区域的数据内容时,可以使用pskb_copy只克隆该区域必须连片段数据区块的内容也修改时,必须使用
skb_copy

列表管理函数
操作 sk_buff 元素列表,列表也称为队列(queue)
skb_queue_head_init:
skb_queue_head, skb_queue_tail
skb_dequeue, skb_dequeue_tail
skb_queue_purge: 把队列变为空队列
skb_queue_walk: 依次循环运行队列中的每个元素
这类函数都必须以原子方式执行,每个函数按照以下方式实现:
1 | void skb_queue_head(struct sk_buff_head *list, struct sk_buff *newsk) |
net_device 结构
- 存储特定网络设备的所有信息
- 每种设备都有一个这种结构,无论真实设备(Ethernet NIC)或虚拟设备(Bonding, VLAN)
- 所有设备的
net_device结构放在一个全局变量dev_base所指的全局列表中。
标识符
int ifindex
独一无二的 ID,当设备以 dev_new_index 注册时分派给每个设备
int iflink
由(虚拟)隧道设别使用,可用于标识抵达隧道另一端的真实设备
unsigned short dev_id
IPv6