Linux 网卡驱动sk_buff内核源码随笔
这几天在调试有关网卡驱动的东西,有很多地方不清楚。有关网卡驱动部分主要有两个很重要的结构体:struct net_device 和struct sk_buff。 驱动大部分都是围绕这两个东西进行操作的,包括加协议头尾,去头去尾等。为了搞清楚协议栈如何处理数据包,周末闲来无事就看看内核代码去了解下这部分东西,并做了简要记录:
/*
*sk_buff->h :传输层头 :udp头和tcp头
*sk_buff->nh :网络层头 :ip头
*sk_buff->mac :数据链路层头 :mac头
*
*
*sk_buff->head :指向数据缓冲区头部
*sk_buff->data :指向实际数据的头部
*sk_buff->tail :指向实际数据的尾部
*sk_buff->end :指向数据缓冲区尾部
*
*
*sk_buff控制区 :struct sk_buff所在的区域
*线性数据区 :数据缓冲区域 : sk_buff->head~sk_buff->end 之间的区域
*非线性数据区 :数据缓冲区域的补充区域 :skb_shared_info区域
*
*
*sk_buff->truesize:线性数据区+非线性数据区+sizeof(struct sk_buff)
*sk_buff->len :线性数据区+非线性数据区
*sk_buff->data_len:非线性数据区
*
*
*一个完整的网络帧(skb_buff)包括:
* 线性数据区 + 非线性数据区 + skb_buff控制区
*
* skb_clone() :只复制skb_buff控制区,其中新分配的skb_buff和原来的skb_buff共享线性数据区和非线性数据区
*
* pskb_copy() :复制skb_buff控制区 + 线性数据区,共享非线性数据区
*
* skb_copy() :复制skb_buff控制区 + 线性数据区 + 非线性数据区
*
*
*
*
*/
源码附上:
struct sk_buff { /*表示接收或发送数据包的包头信息,其成员变量在从一层向另一层传递时会发生修改*/
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
struct sk_buff_head *list;
struct sock *sk;
struct timeval stamp;
struct net_device *dev;
struct net_device *input_dev;
struct net_device *real_dev;
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; /*链路层*/
struct dst_entry *dst; /*记录了到达目的地的路由信息,以及其他的一些网络特征信息*/
struct sec_path *sp;
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/
char cb[40];
/*
*
*在sk_buff这个里面没有实际的数据,这里仅仅是控制信息,数据是通过后面的data指针指向其他内存块的!
*那个内存块中是线性数据和非线性数据!那么len 就是length(线性数据) + length(非线性数据),alloc分配的长度
*
*/
unsigned int len, /* len : 代表整个数据区域的长度!skb的组成是有sk_buff控制 + 线性数据 + 非线性数据(skb_shared_info) 组成!*/
data_len, /*data_len: 指的是length(非线性数据)*/
mac_len,
csum;
unsigned char local_df,
cloned:1,
nohdr:1, /*仅仅引用数据区域*/
pkt_type,
ip_summed;
__u32 priority;
unsigned short protocol,
security;
void (*destructor)(struct sk_buff *skb);
#ifdef CONFIG_NETFILTER
unsigned long nfmark; /*nfmark,用于钩子之间通信*/
__u32 nfcache;
__u32 nfctinfo;
struct nf_conntrack *nfct;
#ifdef CONFIG_NETFILTER_DEBUG
unsigned int nf_debug;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
struct nf_bridge_info *nf_bridge;
#endif
#endif /* CONFIG_NETFILTER */
#if defined(CONFIG_HIPPI)
union {
__u32 ifield;
} private;
#endif
#ifdef CONFIG_NET_SCHED
__u32 tc_index; /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
__u32 tc_verd; /* traffic control verdict */
__u32 tc_classid; /* traffic control classid */
#endif
#endif
/*
* 划重点!
*
* These elements must be at the end, see alloc_skb() for details.
*
*/
unsigned int truesize;
atomic_t users;
unsigned char *head, /*指向分配给的线性数据内存首地址*/
*data, /*指向保存数据内容的首地址*/
*tail, /*指向数据的结尾*/
*end; /*指向分配的内存块的结尾*/
};
/**
* skb_copy - create private copy of an sk_buff 如果要修改数据,使用该函数。不仅复制sk_buff控制区,也复制数据区。是一个完整的备份
* @skb: buffer to copy
* @gfp_mask: allocation priority
*
* Make a copy of both an &sk_buff and its data. This is used when the
* caller wishes to modify the data and needs a private copy of the
* data to alter. Returns %NULL on failure or the pointer to the buffer
* on success. The returned buffer has a reference count of 1.
*
* As by-product this function converts non-linear &sk_buff to linear
* one, so that &sk_buff becomes completely private and caller is allowed
* to modify all the data of returned buffer. This means that this
* function is not recommended for use in circumstances when only
* header is going to be modified. Use pskb_copy() instead.
*/
struct sk_buff *skb_copy(const struct sk_buff *skb, int gfp_mask)
{
int headerlen = skb->data - skb->head;
/*
* Allocate the copy buffer
*/
struct sk_buff *n = alloc_skb(skb->end - skb->head + skb->data_len, /*分配的空间大小为:sizeof(线性数据区 + 非线性数据区)*/
gfp_mask);
if (!n)
return NULL;
/* Set the data pointer */
skb_reserve(n, headerlen); /*skb_reserve 分配headerlen大小的headroom 空间*/
/* Set the tail pointer and length */
skb_put(n, skb->len);
n->csum = skb->csum;
n->ip_summed = skb->ip_summed;
if (skb_copy_bits(skb, -headerlen, n->head, headerlen + skb->len))
BUG();
copy_skb_header(n, skb);
return n;
}
/**
* pskb_copy - create copy of an sk_buff with private head.
* @skb: buffer to copy
* @gfp_mask: allocation priority
*
* Make a copy of both an &sk_buff and part of its data, located
* in header. Fragmented data remain shared. This is used when
* the caller wishes to modify only header of &sk_buff and needs
* private copy of the header to alter. Returns %NULL on failure
* or the pointer to the buffer on success.
* The returned buffer has a reference count of 1.
*/
struct sk_buff *pskb_copy(struct sk_buff *skb, int gfp_mask)/*复制sk_buff控制区和线性数据区,非线性数据区依然共享*/
{
/*
* Allocate the copy buffer
*/
struct sk_buff *n = alloc_skb(skb->end - skb->head, gfp_mask);
if (!n)
goto out;
/* Set the data pointer */
skb_reserve(n, skb->data - skb->head);
/* Set the tail pointer and length */
skb_put(n, skb_headlen(skb));
/* Copy the bytes */
memcpy(n->data, skb->data, n->len);
n->csum = skb->csum;
n->ip_summed = skb->ip_summed;
n->data_len = skb->data_len;
n->len = skb->len;
if (skb_shinfo(skb)->nr_frags) {
int i;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i];
get_page(skb_shinfo(n)->frags[i].page);
}
skb_shinfo(n)->nr_frags = i;
}
if (skb_shinfo(skb)->frag_list) {
skb_shinfo(n)->frag_list = skb_shinfo(skb)->frag_list;
skb_clone_fraglist(n);
}
copy_skb_header(n, skb);
out:
return n;
}
/**
* skb_clone - duplicate an sk_buff :只复制一个和skb_buff,该skb_buff的指针的值与原来的skb值相同
* @skb: buffer to clone
* @gfp_mask: allocation priority
*
* Duplicate an &sk_buff. The new one is not owned by a socket. Both
* copies share the same packet data but not structure. The new
* buffer has a reference count of 1. If the allocation fails the
* function returns %NULL otherwise the new buffer is returned.
*
* If this function is called from an interrupt gfp_mask() must be
* %GFP_ATOMIC.
*/
struct sk_buff *skb_clone(struct sk_buff *skb, int gfp_mask)
{
struct sk_buff *n = kmem_cache_alloc(skbuff_head_cache, gfp_mask);
if (!n)
return NULL;
#define C(x) n->x = skb->x /*只复制控制区,因此定义一个宏函数方便复制操作*/
n->next = n->prev = NULL;
n->list = NULL;
n->sk = NULL;
C(stamp);
C(dev);
C(real_dev);
C(h);
C(nh);
C(mac);
C(dst);
dst_clone(skb->dst);
C(sp);
#ifdef CONFIG_INET
secpath_get(skb->sp);
#endif
memcpy(n->cb, skb->cb, sizeof(skb->cb));
C(len);
C(data_len);
C(csum);
C(local_df);
n->cloned = 1;
n->nohdr = 0;
C(pkt_type);
C(ip_summed);
C(priority);
C(protocol);
C(security);
n->destructor = NULL;
#ifdef CONFIG_NETFILTER
C(nfmark);
C(nfcache);
C(nfct);
nf_conntrack_get(skb->nfct);
C(nfctinfo);
#ifdef CONFIG_NETFILTER_DEBUG
C(nf_debug);
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
C(nf_bridge);
nf_bridge_get(skb->nf_bridge);
#endif
#endif /*CONFIG_NETFILTER*/
#if defined(CONFIG_HIPPI)
C(private);
#endif
#ifdef CONFIG_NET_SCHED
C(tc_index);
#ifdef CONFIG_NET_CLS_ACT
n->tc_verd = SET_TC_VERD(skb->tc_verd,0);
n->tc_verd = CLR_TC_OK2MUNGE(skb->tc_verd);
n->tc_verd = CLR_TC_MUNGED(skb->tc_verd);
C(input_dev);
C(tc_classid);
#endif
#endif
C(truesize);
atomic_set(&n->users, 1);
C(head);
C(data);
C(tail);
C(end);
atomic_inc(&(skb_shinfo(skb)->dataref));
skb->cloned = 1;
return n;
}
Linux 网卡驱动sk_buff内核源码随笔的更多相关文章
- Linux内核驱动学习(二)添加自定义菜单到内核源码menuconfig
文章目录 目标 drivers/Kconfig demo下的Kconfig 和 Makefile Kconfig Makefile demo_gpio.c 目标 Kernel:Linux 4.4 我编 ...
- Linux内核源码分析方法
一.内核源码之我见 Linux内核代码的庞大令不少人“望而生畏”,也正因为如此,使得人们对Linux的了解仅处于泛泛的层次.如果想透析Linux,深入操作系统的本质,阅读内核源码是最有效的途径.我们都 ...
- Linux基础系列—Linux内核源码目录结构
/** ****************************************************************************** * @author 暴走的小 ...
- Linux内核分析(一)---linux体系简介|内核源码简介|内核配置编译安装
原文:Linux内核分析(一)---linux体系简介|内核源码简介|内核配置编译安装 Linux内核分析(一) 从本篇博文开始我将对linux内核进行学习和分析,整个过程必将十分艰辛,但我会坚持到底 ...
- Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7)【转】
原文地址:Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://bl ...
- Linux内核(3) - 分析内核源码如何入手(下)
下面的分析,米卢教练说了,内容不重要,重要的是态度.就像韩局长对待日记的态度那样,严谨而细致. 只要你使用这样的态度开始分析内核,那么无论你选择内核的哪个部分作为切入点,比如USB,比如进程管理,在花 ...
- Linux内核(2) - 分析内核源码如何入手(上)
透过现象看本质,兽兽们无非就是一些人体艺术展示.同样往本质里看过去,学习内核,就是学习内核的源代码,任何内核有关的书籍都是基于内核,而又不高于内核的. 既然要学习内核源码,就要经常对内核代码进行分析, ...
- 【转】Linux内核源码分析方法
一.内核源码之我见 Linux内核代码的庞大令不少人“望而生畏”,也正因为如此,使得人们对Linux的了解仅处于泛泛的层次.如果想透析Linux,深入操作系统的本质,阅读内核源码是最有效的途径.我们都 ...
- Linux内核源码目录说明
Linux内核源代码位于/usr/src/linux目录下,其结构分布如图1.3所示,每一个目录或子目录可以看作一个模块,其目录之间的连线表示“子目录或子模块”的关系.下面是对每一个目录的简单描述. ...
随机推荐
- Lambda--Optional、Collectors高级进阶方法
Lambda--Collectors.optional高级使用 偶然看到了同事groupingBy用法,然后百度衍生出了optional,collectors,map等各种用法.突然发现自己之前写的代 ...
- Vue slot 插槽用法:自定义列表组件
Vue 框架的插槽(slot)功能相对于常用的 v-for, v-if 等指令使用频率少得多,但在实现可复用的自定义组件时十分有用.例如,如果经常使用前端组件库的话,就会经常看到类似的用法: < ...
- Springboot通过拦截器拦截请求信息收集到日志
1.需求 最近在工作中遇到的一个需求,将请求中的客户端类型.操作系统类型.ip.port.请求方式.URI以及请求参数值收集到日志中,网上找资料说用拦截器拦截所有请求然后收集信息,于是就开始了操作: ...
- Convert a Private Project on bitbucket.com to a github Public Project
Create a public repo on github, you can add README or License files on the master branch, suppose th ...
- STM32—中断详解(配合按键中断代码,代码亲测)
在STM32中执行中断主要分三部分: 1.配置NVIC_Config()函数 2.配置EXTI_Config()函数 3.编写中断服务函数 (注:本文章所用代码为中断按键代码,实现了按键进入中断从而控 ...
- NB-IoT四大关键特性及实现告诉你,为啥NB
摘要:NB- IoT 网络是基于4G网络演进过来的,所以它在上行和下行的复用技术上还是沿用了4G的OFDMA和SC-FDMA. 本文分享自华为云社区<一文了解NB-IoT四大关键特性以及实现技术 ...
- vue--三种组件中之间的传值
参考网址:https://www.jianshu.com/p/46573a741c29 一.父子组件之间的传值----props/$emit 组件之间的传值,我们比较常用到的是props/$emit ...
- TortoiseGit冲突和解决冲突
产生冲突原因 产生:多个开发者同时使用或者操作git中的同一个文件,最后在依次提交commit和推送push的时候,第一个操作的是可以正常提交的,而之后的开发者想要执行pull(拉)和pull(推)操 ...
- WPF学习笔记一 依赖属性及其数据绑定
本文想通过由浅入深的讲解让读者比较深的理解依赖属性. 首先,我们回顾一下依赖属性的发展历史. 最初,人们提出面向对象编程时,并没有属性这个说法,当时叫做成员变量.一个对象由成员变量和成员函数组成,如 ...
- Mybatis轻松入门(仅要求会用看着一个就够了,略过源码分析部分即可)
文章目录 ==一.概念== 二.快速入门 1.开发步骤 2.环境搭建 2.1.导入Mybatis的坐标和其他坐标 2.2.创建User表 2.3.编写实体 2.4.编写UserMapper配置文件 2 ...