skb是linux kernel中收发数据包用到的控制结构体,有些字段指向分配的内存用于存放数据包,
向协议栈传送时,通过移动指针来获取到以太头,网络头,传输头等信息。

  1. skb结构和相关操作函数
    a. skb结构体,如下图片(盗图)

    image.png

struct sk_buff {__u16 transport_header; //传输头相对于skb->head的偏移__u16 network_header;//网络头相对于skb->head的偏移__u16 mac_header;//以太网头相对于skb->head的偏移/* These elements must be at the end, see alloc_skb() for details. */sk_buff_data_t tail;sk_buff_data_t end;unsigned char *head, *data;}

head和end分别指向存放数据内存区域的头和尾,一旦分配就固定不变。

data和tail分别是真正数据的起始位结束。

head和data之间的区域成为headroom,data和tail之间的区域存放真正的数据,tail和end之间的区域成为tailroom。skb刚分配时,head,data和tail在同一位置,end在末尾,所以刚开始时,headroom大小为0,tailroom大小为size,后续对数据包的操作,通过移动data和tail完成,head和end固定不变。

head|data|tail ----size-----end

len 和 data_len

len代表整个数据区域的长度!
这里要提前解释几个定义,skb的组成是有sk_buff控制 + 线性数据 + 非线性数据
(skb_shared_info) 组成!后面会具体解释是什么意思!在sk_buff这个里面没有实际的数据,这
里仅仅是控制信息,数据是通过后面的data指针指向其他内存块的!那个内存块中是线性数据和
非线性数据!那么len就是length(线性数据) + length(非线性数据)!!!
data_len: 指的是length(非线性数据)!!!那么可以知道:length(线性数据) = skb->len - skb->data_len

b. 分配skb函数

struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
int flags, int node)
{cache = (flags & SKB_ALLOC_FCLONE)
? skbuff_fclone_cache : skbuff_head_cache;
//从 cache里取出一个skb结构体。为了提高分配skb效率,会在初始化时,分配一个skb
//放在cache中。
skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
//分配数据区域和skb_shared_info,它俩是在一块连续内存中
size = SKB_DATA_ALIGN(size);
size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);
/* kmalloc(size) might give us more room than requested.
* Put skb_shared_info exactly at the end of allocated zone,
* to allow max possible filling before reallocation.
*/
分配完内存后,将size减去skb_shared_info 的大小,此时size只表示存放数据的大小
size = SKB_WITH_OVERHEAD(ksize(data));
#define SKB_WITH_OVERHEAD(X) \
((X) - SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
prefetchw(data + size);
/*
* Only clear those fields we need to clear, not those that we will
* actually initialise below. Hence, don't put any more fields after
* the tail pointer in struct sk_buff!
*/
//将skb tail前面的成员全部清零。tail后面的不用清零,因为随后就会赋值
memset(skb, 0, offsetof(struct sk_buff, tail));
/* Account for allocated memory : skb + skb->head */
skb->truesize = SKB_TRUESIZE(size);
skb->pfmemalloc = pfmemalloc;
//引用计数设置为1
atomic_set(&skb->users, 1);
//在后续报文处理过程中,head和end分别表示数据内存的起始和结尾,是固定不变的
//通过偏移data和tail来指向不同的数据位置
//初始化时head和data指针都指向data
skb->head = data;
skb->data = data;
//在64位下,tail和end都是整数,表示相对于head的偏移
//初始时tailf为0,即指向head
//end为tail+size,即指向内存中存放数据的末尾,skb_shared_info的起始
skb_reset_tail_pointer(skb);skb->tail = skb->data - skb->head
skb->end = skb->tail + size;
skb->mac_header = (typeof(skb->mac_header))~0U;
skb->transport_header = (typeof(skb->transport_header))~0U;/* make sure we initialize shinfo sequentially */
//返回 skb->head + skb->end,即为skb_shared_info的首地址
shinfo = skb_shinfo(skb);#define skb_shinfo(SKB) ((struct skb_shared_info *)  (skb_end_pointer(SKB)))skb->head + skb->end
memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
//设置shinfo引用计数为1
atomic_set(&shinfo->dataref, 1);
kmemcheck_annotate_variable(shinfo->destructor_arg);
}
  1. 操作skb的一些函数
    在skb头部添加 len 字节
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)skb->data -= len;skb->len += len;

在skb头部删除 len 字节

unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)skb->len -= len;skb->data += len;

在skb尾部添加 len 字节

unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
skb->tail += len;
skb->len += len;

预留headroom

static inline void skb_reserve(struct sk_buff *skb, int len)skb->data += len;skb->tail += len;
  1. 数据包从驱动接收到上送协议栈的处理中,skb的操作,mac,网络和传输头的变化
    3.1 eth_type_trans
    不管虚拟网卡还是硬件网卡,数据包都会存放在skb中。收到数据包后,调用eth_type_trans
    设置skb->mac_header,向后偏移skb->data, 减少skb->len,设置数据包类型
eth_type_trans//reset mac头位置,mac_header 是相对于skb->skb的偏移skb_reset_mac_header(skb);skb->mac_header = skb->data - skb->head;//将data指针向后偏移14字节,指向下一个协议头,即三层头或者vlan头//skb->len表示数据包的总长度,偏移14字节后,len也要减去14#define ETH_HLEN 14 /* Total octets in header. */skb_pull_inline(skb, ETH_HLEN);skb->len -= len;skb->data += len;//通过 skb->mac_header 仍然可以获取mac头eth = eth_hdr(skb);(struct ethhdr *)skb_mac_header(skb);skb->head + skb->mac_header;//根据数据包的目的mac决定此数据包的pkt_type//如果目的mac中从左往右第二个字节为1为组播,并且如果mac为     全1,则为广播,否则为组播//如果目的mac中从左往右第二个字节不为1为单播,并且和接收设备的mac不同则设置为PACKET_OTHERHOST//如果和接收设备的mac相同,则不用设置,默认为0,即   PACKET_HOST,表示 to us的数据包if (unlikely(is_multicast_ether_addr(eth->h_dest))) {if (ether_addr_equal_64bits(eth->h_dest, dev->broadcast))skb->pkt_type = PACKET_BROADCAST;elseskb->pkt_type = PACKET_MULTICAST;}else if (unlikely(!ether_addr_equal_64bits(eth->h_dest,dev->dev_addr)))skb->pkt_type = PACKET_OTHERHOST;//如果以太网协议大于ETH_P_802_3_MIN,则返回以太网协议即可if (likely(ntohs(eth->h_proto) >= ETH_P_802_3_MIN))return eth->h_proto;

所以eth_type_trans执行后,
skb->mac_header = skb->data - skb->head;
skb->data 指向网络头

  1. __netif_receive_skb_core
    经过软中断处理后,会调用__netif_receive_skb_core上送到协议栈,根据协议调用相应的hook函数
__netif_receive_skb_core//设置网络头偏移量skb_reset_network_header(skb);skb->network_header = skb->data - skb->head;//重置mac头长度skb_reset_mac_len(skb);skb->mac_len = skb->network_header - skb->mac_header;

执行后,
skb->mac_header = skb->data - skb->head;
skb->network_header = skb->data - skb->head;
skb->data 仍然指向网络头

3.3. ip_rcv 数据包进入网络层的处理

ip_rcv
当数据包进入协议栈往上层递交的过程中,比如在IP层,它需要对数据包的IP头部进行分析,比如头部合法性等,这时候就需要确保IP头部在线性缓冲区中,这样才能对它进行分析,如果在非线性缓冲区中,而非线性缓冲区是unmapped的page,因此就需要从这些unmapped page当中把数据复制到线性缓冲区中。
这个艰难的工作就是__pskb_pull_tail完成的。我将对它的代码进行单独的分析。
当然最好情况是skb->data指向的线性缓冲区中的数据至少是大于len的,这样就可以直接返回了成功了。
len一定是不能大于整个skb的数据总长的。这个就不必说明吧...
线性缓冲区中数据不足,不幸还是发生了...调用__pskb_pull_tail。
if (!pskb_may_pull(skb, sizeof(struct iphdr)))/* skb_headlen定义为skb->len - skb->data_len。即skb->head指向  的线性缓冲区里当前* 有效数据的长度。*/if (likely(len <= skb_headlen(skb)))return 1;if (unlikely(len > skb->len))return 0;return __pskb_pull_tail(skb, len - skb_headlen(skb)) != NULL;iph = ip_hdr(skb);(struct iphdr *)skb_network_header(skb);skb->head + skb->network_header;skb->transport_header = skb->network_header + iph->ihl*4;

执行后,
skb->mac_header = skb->data - skb->head;
skb->network_header = skb->data - skb->head;
skb->transport_header = skb->network_header + iph->ihl*4;
skb->data 仍然指向网络头

3.4. 经过查找路由,发现是本地的数据包

ip_local_deliver_finish//获取网络头长度static inline u32 skb_network_header_len(const struct sk_buff *skb)return skb->transport_header - skb->network_header;//偏移data到传输头__skb_pull(skb, skb_network_header_len(skb));skb->len -= len;skb->data += len;int protocol = ip_hdr(skb)->protocol;ipprot = rcu_dereference(inet_protos[protocol]);ipprot->handler(skb);

执行后,
skb->mac_header = skb->data - skb->head;
skb->network_header = skb->data - skb->head;
skb->transport_header = skb->network_header + iph->ihl*4;
skb->data 指向传输层

3.5. 数据到达传输层,以icmp为例

icmp_rcvpskb_pull(skb, sizeof(*icmph))skb->len -= len;skb->data += len;icmph = icmp_hdr(skb);(struct icmphdr *)skb_transport_header(skb);skb->head + skb->transport_header;

执行后,
skb->mac_header = skb->data - skb->head;
skb->network_header = skb->data - skb->head;
skb->transport_header = skb->network_header + iph->ihl*4;
skb->data 指向应用层

也可参考:skb结构和相关操作函数 - 简书 (jianshu.com)

skb结构和相关操作函数相关推荐

  1. linux skb 结构和相关操作函数分析

    sk_buff是Linux网络中最核心的结构体,它用来管理和控制接收或发送数据包的信息.各层协议都依赖于sk_buff而存在.内核中sk_buff结构体在各层协议之间传输不是用拷贝sk_buff结构体 ...

  2. uCOS-II消息邮箱的相关操作函数

    定位到uCOS-II/Source/os_mbox.c,该文件是消息邮箱管理的相关操作函数.   任务与任务之间需要数据传递,那么为了适应传递的数据的不同类型,可以建立一个缓冲区(void*类型可以接 ...

  3. SystemV 信号量(一) —— SystemV信号量的相关操作函数(semget / semop /semctl)

    SystemV IPC 方案的相关内容都是通过 "房间密码"来创建房间,获取到房间的ID,后面其他进程也可以根据这个房间密码来拿到同一个房间的ID.这是理解下面这些操作函数的关键. ...

  4. 封装进程内存相关操作函数

    //ProcessMemory.h:进程内存操作封装 #pragma once #include <windows.h> #include <TLHELP32.H>class ...

  5. linux kernel --- dts的相关操作函数

    一.compatible匹配 1.dts 中写法 compatible = "aaa,bbb" 当一个驱动支持多个设备的时候,在每个设备的dts中,都会配置各自的compatibl ...

  6. c语言 信号函数,C语言中进程信号集的相关操作函数详解

    C语言sigismember()函数:测试某个信号是否已加入至信号头文件:#include 定义函数:int sigismember(const sigset_t *set, int signum); ...

  7. c语言实现新建目录函数,C语言中改变目录的相关操作函数详解

    C语言fchdir()函数:改变当前工作目录头文件: #include 定义函数: int fchdir(int fd); 函数说明:fchdir()用来将当前的工作目录改变成以参数fd 所指的文件描 ...

  8. linux协议栈skb操作函数,linux协议栈skb操作函数

    1,struct sk_buff数据结构 struct sk_buff{ //这两个结构必须放在最前面 struct sk_buff *next; struct sk_buff *prev; stru ...

  9. C语言 文件操作 深度解析 #重点知识:文件操作函数的使用#

    文章目录 前言 1. 为什么使用文件 2. 什么是文件 程序文件 数据文件 3. 文件的打开和关闭 4. 文件的顺序读写 `fgetc` `fputc` `fgets` `fputs` `fprint ...

最新文章

  1. (转)Activity的四种launchMode
  2. python多线程编程(5): 条件变量同步
  3. 40个超酷的jQuery动画教程
  4. springboot 关于 Class path contains multiple SLF4J bindings.警告的解决
  5. 杭电1214 圆桌会议
  6. 导轮式机器人_轮式移动机器人导航控制与路径规划研究
  7. Banner字符可以通过类似以下网站生成
  8. java设计模式-State模式
  9. Flink1.13 DataStream API - Event Time - Generating Watermarks
  10. 永别了.武器------爱好和平人民的美好愿望(图)
  11. 为什么亚马逊无货源是国际电商新时代
  12. 一款游戏让你成为 Vim 高手!
  13. 【超超超详细mysql下载安装攻略(有手就行)】
  14. 卷积神经网络(一)- 卷积神经网络
  15. 流媒体学习-WebRTC全面入门学习-1
  16. HDU2209+POJ3279 枚举+dfs
  17. PHP File 文件格式
  18. python舆情系统开发_舆情系统开发
  19. vue+zrender实现医院体温单
  20. 计算机技术在本专业的应用与探索3000字,【计算机专业论文】移动学习在计算机专业教学中的应用(共3000字)...

热门文章

  1. Oc LocateMap(地图定位)
  2. 机载 LiDAR 系统检校
  3. linux 内核协议栈 UDP数据报校验和
  4. python+opencv横向拼接视频
  5. 国王统治没有显示服务器,国王的统治
  6. 《无线收发器设计指南-现代无线设备与系统篇》摘录
  7. 三相异步电动机的等效电路
  8. 分数计算器(C++课设)
  9. STP(生成树协议)
  10. 工作流规范WfMC是什么?