在内核中操作 sk_buff 的函数非常丰富,主要可以分为分配与释放、数据指针操作、管理链表、克隆与复制这几大类。这些函数是处理网络数据包的基础,驱动或协议模块通过它们来安全地操作数据。
下面为你详细介绍每一类中最核心的函数及其用途。
1. 分配与释放
这是 sk_buff 生命周期的起点和终点。
| 函数 | 作用 | 关键说明 |
|---|---|---|
alloc_skb() |
分配一个 sk_buff 结构体及其数据缓冲区。 |
最基础的分配函数,需指定大小和优先级(如 GFP_ATOMIC)。 |
dev_alloc_skb() |
设备驱动专用的分配函数。 | 它是 alloc_skb() 的封装,通常用于接收路径,会在数据包开头预留空间(skb_reserve)以优化头部对齐。 |
kfree_skb() |
释放 sk_buff。 |
内核核心使用的释放函数,会检查引用计数,只在最后一个引用时真正释放内存。 |
dev_kfree_skb() / dev_kfree_skb_any() |
驱动专用的释放函数。 | 驱动应使用这些变体,dev_kfree_skb_any() 可以在中断和进程上下文中安全调用。 |
2. 数据指针操作
这类函数用于移动指针,在协议栈各层添加或移除协议头,是核心操作。
| 函数 | 作用 | 关键说明 |
|---|---|---|
skb_reserve() |
在数据缓冲区头部预留空间。 | 常用于接收数据包时,为各层协议头预留空间。它同时移动 data 和 tail 指针。 |
skb_put() |
在数据缓冲区尾部增加数据。 | 扩大 data 到 tail 之间的有效数据区域。返回值指向新的数据起始位置。 |
skb_push() |
在数据缓冲区头部增加数据。 | 将 data 指针向上移动,通常用于在发送时添加一个协议头(如以太网头)。返回值指向新的数据起始位置。 |
skb_pull() |
从数据缓冲区头部移除数据。 | 将 data 指针向下移动,通常用于接收时剥离协议头。它会相应减小 len 值。 |
skb_trim() |
从数据缓冲区尾部移除数据。 | 将数据包长度裁剪到指定值,常用于处理填充字节或截断数据包。 |
下图直观展示了这些指针操作函数如何改变 sk_buff 的结构:
skb_pull
head
headroom
已移除头部
data
data area
tail
end
tailroom
skb_push
head
headroom
data
新头部
新头部
data area
tail
end
tailroom
skb_put
head
headroom
data
data area (已用)
tail
end
tailroom
skb_reserve
head
headroom
data
data area
tail
end
tailroom
初始状态
head
headroom (保留区)
data
tail
end
tailroom (可用空间)
3. 队列管理操作
sk_buff 通常被组织成双向链表(sk_buff_head)进行管理。
| 函数 | 作用 | 关键说明 |
|---|---|---|
skb_queue_head() |
将一个 skb 添加到队列头部。 |
带锁操作,保证原子性。 |
skb_queue_tail() |
将一个 skb 添加到队列尾部。 |
最常用的入队操作,带锁。 |
skb_dequeue() |
从队列头部取出一个 skb。 |
最常用的出队操作,带锁。 |
skb_dequeue_tail() |
从队列尾部取出一个 skb。 |
带锁。 |
skb_insert() / skb_append() |
在指定 skb 前后插入新 skb。 |
带锁。 |
skb_unlink() |
将 skb 从当前队列中移除。 |
带锁。 |
skb_queue_purge() |
清空整个队列并释放所有 skb。 |
带锁。 |
注意 :上述所有队列操作函数都是锁安全的 。在中断上下文或已经持有自旋锁的情况下,可以使用不带锁的下划线版本(如
__skb_queue_tail()),这些版本在函数名前有双下划线前缀。
4. 克隆与复制
为了避免昂贵的拷贝,内核通过引用计数来共享数据。理解克隆与复制在涉及多路径或重传时非常重要。
| 函数 | 作用 | 关键说明 |
|---|---|---|
skb_clone() |
克隆 sk_buff 结构体本身。 |
只复制元数据,不复制数据 。新 skb 指向同一个数据缓冲区。这是最快、最常用的方法。 |
skb_copy() |
深度拷贝整个 sk_buff 和数据。 |
复制元数据和数据缓冲区,产生完全独立的副本。开销大,应尽量避免。 |
pskb_copy() |
拷贝 sk_buff 和线性数据部分。 |
介于克隆和深度拷贝之间,只拷贝线性数据(data 到 tail),分片数据(frags)被共享。 |
5. 高级操作与信息获取
这些函数用于处理更复杂的情况,如分散-聚集I/O(Scatter/Gather I/O)和校验和。
| 函数 | 作用 | 关键说明 |
|---|---|---|
skb_is_nonlinear() |
判断 skb 是否使用分片(非线性)。 |
即数据是否部分存储在 frags 数组中,用于支持Scatter/Gather I/O。 |
skb_headlen() |
获取线性部分的长度。 | 即 skb->len - skb->data_len。 |
skb_checksum_help() |
在软件中计算并填充数据包的校验和。 | 当硬件不支持特定校验和卸载时,驱动必须调用此函数。 |
skb_shared() / skb_cloned() |
检查 skb 的共享/克隆状态。 |
在修改数据前,需检查这些函数以确保不会破坏其他引用者的数据。 |
总结
以上是内核中操作 sk_buff 最常用、最核心的函数。理解它们的分类和作用,特别是分配(alloc_skb)、指针操作(push/pull/put/reserve)、释放(kfree_skb)以及队列管理(queue/dequeue),是进行网络驱动开发或协议栈分析的基础。