内核网络数据结构-SKB
struct sk_buff
一个封包就存储在这里,所有网络分层都会使用这个结构来储存其报文,有关用户数据的信息(有效载荷),以及用来协调其工作的其他内部信息。
这个结构体是Linux网络代中最重要的数据结构,代表已接收或者正要传输的数据包头,此结构定义在include/linux/skbuff.h头文件中,由巨大的变量堆(head)组成,试图满足所有人的需求。
在内核的进化历程进程中,这个结构历经多次变动,不但新增了选项,同时也重组了现存的字段,使得布局更为清晰,其字段可粗略划分为下列几个类型:
- 布局(Layout)
- 通用(General)
- 功能共用(Feature-specific)
- 管理函数(Management functions)
多个不同的网络分层(MAC或者L2分层上的另一个链路层协议,L3的IP以及L4的TCP或者UDP)都会使用这个结构,而且当该结构从一个分层传到另一个分层时,其不同的字段会随之发生变化,L4在传给L3之前会附加一个报头,而L3在传给L2之前又会加上其自己的报头,附加报头比起把数据从一个分层拷贝到另一个分层更有效率,由于要在一个缓冲区开端新增空间-——也就是要改变指向该缓冲区的变量-——是一种复杂的运算,内核提供skb_reserve函数来执行这一操作,所以,当缓冲区往下传经过每个分层,每层的协议首先要做的就是调用skb_reserve函数,为该协议报文预留空间。
当缓冲区往上经过各个网络分层时,每个源自于旧分层的报头就不再有用处,例如,L2报头只由处理L2协议的设备驱动程序使用,所以对L3而言并无用处,不过,并没有把L2的报头从缓存区删除,而是把指向有效载荷开端指针向前移动到L3报头的开端,这样就需要很少的CPU周期。
//include/linux/skbuff.h
/*** struct sk_buff - socket buffer* @next: Next buffer in list* @prev: Previous buffer in list* @tstamp: Time we arrived/left //收发包的时间戳* @skb_mstamp_ns: (aka @tstamp) earliest departure time; start point* for retransmit timer* @rbnode: RB tree node, alternative to next/prev for netem/tcp* @list: queue head* @sk: Socket we are owned by* @ip_defrag_offset: (aka @sk) alternate use of @sk, used in* fragmentation management* @dev: Device we arrived on/are leaving by* @dev_scratch: (aka @dev) alternate use of @dev when @dev would be %NULL* @cb: Control buffer. Free for use by every layer. Put private vars here* @_skb_refdst: destination entry (with norefcount bit)* @sp: the security path, used for xfrm* @len: Length of actual data* @data_len: Data length* @mac_len: Length of link layer header* @hdr_len: writable header length of cloned skb* @csum: Checksum (must include start/offset pair)* @csum_start: Offset from skb->head where checksumming should start* @csum_offset: Offset from csum_start where checksum should be stored* @priority: Packet queueing priority* @ignore_df: allow local fragmentation* @cloned: Head may be cloned (check refcnt to be sure)* @ip_summed: Driver fed us an IP checksum* @nohdr: Payload reference only, must not modify header* @pkt_type: Packet class* @fclone: skbuff clone status* @ipvs_property: skbuff is owned by ipvs* @inner_protocol_type: whether the inner protocol is* ENCAP_TYPE_ETHER or ENCAP_TYPE_IPPROTO* @remcsum_offload: remote checksum offload is enabled* @offload_fwd_mark: Packet was L2-forwarded in hardware* @offload_l3_fwd_mark: Packet was L3-forwarded in hardware* @tc_skip_classify: do not classify packet. set by IFB device* @tc_at_ingress: used within tc_classify to distinguish in/egress* @redirected: packet was redirected by packet classifier* @from_ingress: packet was redirected from the ingress path* @peeked: this packet has been seen already, so stats have been* done for it, don't do them again* @nf_trace: netfilter packet trace flag* @protocol: Packet protocol from driver* @destructor: Destruct function* @tcp_tsorted_anchor: list structure for TCP (tp->tsorted_sent_queue)* @_nfct: Associated connection, if any (with nfctinfo bits)* @nf_bridge: Saved data about a bridged frame - see br_netfilter.c* @skb_iif: ifindex of device we arrived on* @tc_index: Traffic control index* @hash: the packet hash* @queue_mapping: Queue mapping for multiqueue devices* @head_frag: skb was allocated from page fragments,* not allocated by kmalloc() or vmalloc().* @pfmemalloc: skbuff was allocated from PFMEMALLOC reserves* @active_extensions: active extensions (skb_ext_id types)* @ndisc_nodetype: router type (from link layer)* @ooo_okay: allow the mapping of a socket to a queue to be changed* @l4_hash: indicate hash is a canonical 4-tuple hash over transport* ports.* @sw_hash: indicates hash was computed in software stack* @wifi_acked_valid: wifi_acked was set* @wifi_acked: whether frame was acked on wifi or not* @no_fcs: Request NIC to treat last 4 bytes as Ethernet FCS* @encapsulation: indicates the inner headers in the skbuff are valid* @encap_hdr_csum: software checksum is needed* @csum_valid: checksum is already valid* @csum_not_inet: use CRC32c to resolve CHECKSUM_PARTIAL* @csum_complete_sw: checksum was completed by software* @csum_level: indicates the number of consecutive checksums found in* the packet minus one that have been verified as* CHECKSUM_UNNECESSARY (max 3)* @dst_pending_confirm: need to confirm neighbour* @decrypted: Decrypted SKB* @napi_id: id of the NAPI struct this skb came from* @sender_cpu: (aka @napi_id) source CPU in XPS* @secmark: security marking* @mark: Generic packet mark* @reserved_tailroom: (aka @mark) number of bytes of free space available* at the tail of an sk_buff* @vlan_present: VLAN tag is present* @vlan_proto: vlan encapsulation protocol* @vlan_tci: vlan tag control information* @inner_protocol: Protocol (encapsulation)* @inner_ipproto: (aka @inner_protocol) stores ipproto when* skb->inner_protocol_type == ENCAP_TYPE_IPPROTO;* @inner_transport_header: Inner transport layer header (encapsulation)* @inner_network_header: Network layer header (encapsulation)* @inner_mac_header: Link layer header (encapsulation)* @transport_header: Transport layer header* @network_header: Network layer header* @mac_header: Link layer header* @kcov_handle: KCOV remote handle for remote coverage collection* @tail: Tail pointer* @end: End pointer* @head: Head of buffer* @data: Data head pointer* @truesize: Buffer size* @users: User count - see {datagram,tcp}.c* @extensions: allocated extensions, valid if active_extensions is nonzero*/struct sk_buff {union {struct {/* These two members must be first. */struct sk_buff *next; //sk_buff链表中的下一个sock缓冲区struct sk_buff *prev; //sk_buff链表中的前一个sock缓冲区union {struct net_device *dev; //接收到此网络报文的网络设备/* Some protocols might use this space to store information,* while device pointer would be NULL.* UDP receive path is one user.*/unsigned long dev_scratch;};};struct rb_node rbnode; /* used in netem, ip4 defrag, and tcp stack */ //分片struct list_head list; //内核链表结构,用于快速定位链表头sk_buff_head};union {struct sock *sk; //网络报文所属的sock结构,此值仅在本机发出的报文中有效,从网络收到的报文此值为空int ip_defrag_offset; //用于分片管理中};union {ktime_t tstamp; //收到此报文的时间戳u64 skb_mstamp_ns; /* earliest departure time */ //最早出发时间};/** 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[48] __aligned(8); //控制缓冲区,用于存储私有信息,每层协议自己维护并使用,并且只在本层有效union {struct {unsigned long _skb_refdst; //路由缓存,输入或者输出报文都要查询到目的路由缓存项,才能确定流向void (*destructor)(struct sk_buff *skb);};struct list_head tcp_tsorted_anchor;};#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)unsigned long _nfct;
#endifunsigned int len, //报文的总长度(主缓存区+片段(比如各种头))data_len; //片段包含的全部报文长度__u16 mac_len, //mac报头大小hdr_len; //cloned skb的可写报文头的长度/* Following fields are _not_ copied in __copy_skb_header()* Note that queue_mapping is here mostly to fill a hole.*/__u16 queue_mapping;/* if you move cloned around you also must adapt those constants */
#ifdef __BIG_ENDIAN_BITFIELD
#define CLONED_MASK (1 << 7)
#else
#define CLONED_MASK 1
#endif
#define CLONED_OFFSET() offsetof(struct sk_buff, __cloned_offset)/* private: */__u8 __cloned_offset[0];/* public: */__u8 cloned:1, //该skb是clonednohdr:1, //payload是否被单独引用,不存在协议首部,如果被引用,则不能修改协议协议首部,也不能通过skb->data来访问协议首部fclone:2, //当前克隆状态peeked:1,head_frag:1,pfmemalloc:1;
#ifdef CONFIG_SKB_EXTENSIONS__u8 active_extensions;
#endif/* fields enclosed in headers_start/headers_end are copied* using a single memcpy() in __copy_skb_header()*//* private: */__u32 headers_start[0];/* public: *//* if you move pkt_type around you also must adapt those constants */
#ifdef __BIG_ENDIAN_BITFIELD
#define PKT_TYPE_MAX (7 << 5)
#else
#define PKT_TYPE_MAX 7
#endif
#define PKT_TYPE_OFFSET() offsetof(struct sk_buff, __pkt_type_offset)/* private: */__u8 __pkt_type_offset[0];/* public: */__u8 pkt_type:3; //包类别__u8 ignore_df:1;__u8 nf_trace:1;__u8 ip_summed:2;__u8 ooo_okay:1;__u8 l4_hash:1;__u8 sw_hash:1;__u8 wifi_acked_valid:1;__u8 wifi_acked:1;__u8 no_fcs:1;/* Indicates the inner headers are valid in the skbuff. */__u8 encapsulation:1;__u8 encap_hdr_csum:1;__u8 csum_valid:1;#ifdef __BIG_ENDIAN_BITFIELD
#define PKT_VLAN_PRESENT_BIT 7
#else
#define PKT_VLAN_PRESENT_BIT 0
#endif
#define PKT_VLAN_PRESENT_OFFSET() offsetof(struct sk_buff, __pkt_vlan_present_offset)/* private: */__u8 __pkt_vlan_present_offset[0];/* public: */__u8 vlan_present:1;__u8 csum_complete_sw:1;__u8 csum_level:2;__u8 csum_not_inet:1;__u8 dst_pending_confirm:1;
#ifdef CONFIG_IPV6_NDISC_NODETYPE__u8 ndisc_nodetype:2;
#endif__u8 ipvs_property:1;__u8 inner_protocol_type:1;__u8 remcsum_offload:1;
#ifdef CONFIG_NET_SWITCHDEV__u8 offload_fwd_mark:1;__u8 offload_l3_fwd_mark:1;
#endif
#ifdef CONFIG_NET_CLS_ACT__u8 tc_skip_classify:1;__u8 tc_at_ingress:1;
#endif
#ifdef CONFIG_NET_REDIRECT__u8 redirected:1;__u8 from_ingress:1;
#endif
#ifdef CONFIG_TLS_DEVICE__u8 decrypted:1;
#endif
//make menuconfig->Networkong support->Networking options->QoS and/or fair queueing->....(选中添加到skb_buff,一般任何改变内核数据结构的选项,都不适合编译成一个模块,在kconfig文件中寻找)
//packet classifier:包分类器
#ifdef CONFIG_NET_SCHED__u16 tc_index; /* traffic control index */
#endifunion { //校验和__wsum csum;struct {__u16 csum_start; //校验开始的地方(当开始计算校验和时从skb->head的偏移)__u16 csum_offset; //校验存放的地方(相对于start)(从csum_start开始的偏移)};};__u32 priority; //数据包的优先与tos配合int skb_iif;__u32 hash;__be16 vlan_proto;__u16 vlan_tci;
#if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS)union {unsigned int napi_id;unsigned int sender_cpu;};
#endif
#ifdef CONFIG_NETWORK_SECMARK__u32 secmark;
#endifunion {__u32 mark;__u32 reserved_tailroom;};//封装协议union {__be16 inner_protocol;__u8 inner_ipproto;};__u16 inner_transport_header; //封装的传输层协议头__u16 inner_network_header;__u16 inner_mac_header;__be16 protocol; //协议__u16 transport_header;//传输层的协议头__u16 network_header; //网络层的协议头__u16 mac_header; //链路层的协议头#ifdef CONFIG_KCOVu64 kcov_handle;
#endif/* private: */__u32 headers_end[0];/* public: *//* 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; //线性数据区的开始unsigned int truesize; //该缓冲区所分配的总内存,包括sk_buff结构大小+lenrefcount_t users; //引用计数#ifdef CONFIG_SKB_EXTENSIONS/* only useable after checking ->active_extensions != 0 */struct skb_ext *extensions;
#endif
};
布局字段
sk_buff_head(布局字段)
skb_buff有些字段只是为了方便搜寻以及组织数据结构本身。内核在一个双向链表中维护所有的sk_buff结构,但是该表的组织比传统的双向链表更为复杂。
像任何双链表一样,通过每个sk_buff结构中的next和prev字段实现联系,next字段指向前,而prev指向后,但是这个表还需要另一个必须需求,每个sk_buff结构必须能够迅速找出整个表的头,为了实现这项必要需求,在表的开端额外增加一个sk_buff_head结构作为一个哑元素。
struct sk_buff_head {/* These two members must be first. *///next与prev两个元素与sk_buff的两个元素相同struct sk_buff *next;struct sk_buff *prev;__u32 qlen; //代表表中元素的数目spinlock_t lock; //用于防止对表的并发访问
};
sk_buff_head与sk_buff相比实在太小,但是还是允许两个结构共同存在于同一个表中,另外,同样的函数也可用于操作sk_buff_head和sk_buff两者。
struct sock *sk
这是一个指针,指向拥有此缓冲区的套接字的sock数据结构。当数据在本地产生或者正由本地进程接收时,就需要这个指针,因为该数据以及套接字相关的信息会由L4(TCP或UDP)以及用户程序使用,当缓存区只是被转发时(也就是说本地机器不是来源地也不是目的地),该指针就是NULL。
unsigned int len
这些指向缓冲区中数据区块的大小,这个长度包括主要缓冲区(由head所指)的数据以及一些片段的数据,当缓冲区从一个网络分层往下一个网络分层时,其值就会变化,因为协议栈中往上时会被丢弃,但是往下移动时添加报头就会添加进来,len也会把协议报头算在内。
unsigned int data_len
与len不同,它只计算片段中的数据大小
__u16 mac_len
MAC报文的大小
refcount_t users;
这是引用计数,或者使用这个sk_buff缓冲区的实例的数目。这个参数的主要用途是避免在某人依然使用此sk_buff结构时,把这个结构释放掉,因此,此缓冲区的每个用户在必要时都要递增和递减字段,此计算器只计算sk_buff数据结构的用户,此缓冲区所包含的实际数据由一个相似的字段(dataref)所包含。
一般在调用skb_get和kfree_skb进行处理。
unsigned int truesize
此字段代表此缓冲区总的大小,包括sk_buff结构本身,当此缓冲区所分配的len个字节的数据请求空间时,此字段的初始化由alloc_skb函数设置成len+sizeof(sk_buff)。
skb->truesize = size + sizeof(struct sk_buff);
当skb->len的值增加时,此字段就会得到更新。
sk_buff_data_t tail; sk_buff_data_t end;unsigned char *head,*data;
这些缓冲区的边界以及其中的数据。当每一个分层为其工作而准备缓冲区时,可能会为一个报文或者更多的数据分配更多的空间。head和end指向已分配缓冲区空间的开端和尾端,而data和tail则指向实际数据的开端和尾端,该分层可以把head和data之间的空隙上一个协议报头,或者以新数据填入tail和end之间的间隙。
void (*destructor)(struct sk_buff *skb)
此函数指针可以被初始化为一个函数,当此缓冲区被删除时,可完成某些工作,当此缓冲区不属于一个套接字时,destructor通常不会被初始化,当缓冲区属于一个套接字,通常设置成sock_rfree或者sock_wfree(分别由skb_set_owner_r和skb_set_owner_w初始化函数设置),这两个sock_xxxx函数可用于更新套接字队列中所持有的内存。
通用字段
ktime_t tstamp
struct net_device *dev
功能专用字段
unsigned long _nfct
管理函数
skb_reserve
skb_reserve只能操作空skb,即在分配了空间,尚未填充数据时调用
/*** skb_reserve - adjust headroom //调整空间的大小* @skb: buffer to alter* @len: bytes to move** Increase the headroom of an empty &sk_buff by reducing the tail* room. This is only allowed for an empty buffer.*///保留头部空间,只能对空的skb使用
static inline void skb_reserve(struct sk_buff *skb, int len)
{skb->data += len; //偏移数据长度 ///* 数据区data指针增加len字节*/skb->tail += len; ///* 数据区tail指针增加len字节 */
}
skb_put
/*** skb_put - add data to a buffer* @skb: buffer to use* @len: amount of data to add** This function extends the used data area of the buffer. If this would* exceed the total buffer size the kernel will panic. A pointer to the* first byte of the extra data is returned.*///向skb尾部添加数据
void *skb_put(struct sk_buff *skb, unsigned int len)
{//获取当前skb->tailvoid *tmp = skb_tail_pointer(skb);//要求skb数据区必须为线性SKB_LINEAR_ASSERT(skb);//skb尾部增加len字节skb->tail += len;//skb数据总长度增加len字节skb->len += len;//如果增加之后的tail>end,则panicif (unlikely(skb->tail > skb->end))skb_over_panic(skb, len, __builtin_return_address(0));//返回添加数据的第一个字节位置return tmp;
}
skb_push
/*** skb_push - add data to the start of a buffer* @skb: buffer to use* @len: amount of data to add** This function extends the used data area of the buffer at the buffer* start. If this would exceed the total buffer headroom the kernel will* panic. A pointer to the first byte of the extra data is returned.*///向skb数据区头部添加数据
void *skb_push(struct sk_buff *skb, unsigned int len)
{//数据区data指针前移len字节skb->data -= len;//数据总长度增加len字节skb->len += len;//添加数据长度溢出过header,panicif (unlikely(skb->data < skb->head))skb_under_panic(skb, len, __builtin_return_address(0));//返回新的data指针return skb->data;
}
/*** skb_pull - remove data from the start of a buffer* @skb: buffer to use* @len: amount of data to remove** This function removes data from the start of a buffer, returning* the memory to the headroom. A pointer to the next data in the buffer* is returned. Once the data has been pulled future pushes will overwrite* the old data.*///从数据区头部移处数据
void *skb_pull(struct sk_buff *skb, unsigned int len)
{return skb_pull_inline(skb, len);
}
//根据移数据长度判断函数调用
static inline void *skb_pull_inline(struct sk_buff *skb, unsigned int len)
{/*移除长度>skb数据总长度,返回NULL否则,继续调用__skb_pull函数*/return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);
}
//从skb数据区头部移除数据
static inline void *__skb_pull(struct sk_buff *skb, unsigned int len)
{//数据总长度减去len字节skb->len -= len;//数据总长度是否有异常BUG_ON(skb->len < skb->data_len);/*data指针移动len字节返回移动除之后新的data指针*/return skb->data += len;
}
SKB的操作函数
dev_kfree_skb
释放skb:kfree_skb和dev_kfree_skb
两个函数会释放一个缓冲区,使其返回缓冲池(缓存)。kfree_skb是直接由dev_kfree_skb调用并启动的。只有当skb->users计数器为1时(该缓冲区已无任何用户时),这个基本函数才会释放一个缓冲区。否则,只是递减该计数器。在sk_buff底端的skb_shared_info数据结构可以持有一些指向其他内存片段的指针。kfree_skb也会释放这些片段所持有的内存。
#define dev_kfree_skb(a) consume_skb(a)
static inline void consume_skb(struct sk_buff *skb)
{return kfree_skb(skb);
}
/*** kfree_skb - free an sk_buff* @skb: buffer to free** Drop a reference to the buffer and free it if the usage count has* hit zero.*/
void kfree_skb(struct sk_buff *skb)
{if (!skb_unref(skb))return;//静态插装点函数trace_kfree_skb(skb, __builtin_return_address(0));__kfree_skb(skb);
}
/*** __kfree_skb - private function* @skb: buffer** Free an sk_buff. Release anything attached to the buffer.* Clean the state. This is an internal helper function. Users should* always call kfree_skb*///很复杂,单独开章节说明
void __kfree_skb(struct sk_buff *skb)
{skb_release_all(skb);kfree_skbmem(skb);
}
skb_trim
skb_trim从缓冲区尾部移走长度为len的数据,即skb->tail指针向前移动skb->tail-len。
/*** skb_trim - remove end from a buffer* @skb: buffer to alter* @len: new length** Cut the length of a buffer down by removing data from the tail. If* the buffer is already under the length specified it is not modified.* The skb must be linear.*/
void skb_trim(struct sk_buff *skb, unsigned int len)
{if (skb->len > len)__skb_trim(skb, len);
}
static inline void __skb_trim(struct sk_buff *skb, unsigned int len)
{__skb_set_length(skb, len);
}
static inline void __skb_set_length(struct sk_buff *skb, unsigned int len)
{if (WARN_ON(skb_is_nonlinear(skb)))return;skb->len = len;skb_set_tail_pointer(skb, len);
}
skb_shared_info
数据缓冲区尾端有个名为skb_shared_info的数据结构,用以保持此数据区块的附加信息。此数据结构紧接在标记数据尾端的end指针之后。
/* This data is invariant across clones and lives at* the end of the header data, ie. at skb->end.*/
struct skb_shared_info {__u8 __unused;__u8 meta_len;__u8 nr_frags; //用于处理IP片段__u8 tx_flags; //用于处理IP片段unsigned short gso_size;/* Warning: this field is not always filled in (UFO)! */unsigned short gso_segs;struct sk_buff *frag_list; //用于处理IP片段struct skb_shared_hwtstamps hwtstamps;unsigned int gso_type;u32 tskey;/** Warning : all fields before dataref are cleared in __alloc_skb()*/atomic_t dataref; //数据块的"用户"数目/* Intermediate layers must ensure that destructor_arg* remains valid until skb destructor */void * destructor_arg;/* must be last field, see pskb_expand_head() */skb_frag_t frags[MAX_SKB_FRAGS];
};
sk_buff结构中没有指向skb_shared_info数据结构的字段。为了访问该结构体,函数必须使用返回end指针的skb_shinfo宏:
#define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))
通过这个指针去访问里面的成员
u32 nr_frags = skb_shinfo(skb)->nr_frags + 1
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固定不变。
重要的长度len的解析
这里要声明两个概念的区别,后续直接用这两个概念,注意区分:
(1)线性数据:head - end。
(2)实际线性数据:data - tail,不包含线性数据中的头空间和尾空间。
skb->data_len: skb中的分片数据(非线性数据)的长度。
skb->len: skb中的数据块的总长度,数据块包括实际线性数据和非线性数据,非线性数据为data_len,所以skb->len= (data - tail) + data_len。
skb->truesize: skb的总长度,包括sk_buff结构和数据部分,skb=sk_buff控制信息 + 线性数据(包括头空间和尾空间) + skb_shared_info控制信息 + 非线性数据,所以skb->truesize = sizeof(struct sk_buff) + (head - end) + sizeof(struct skb_shared_info) + data_len。
skb_clone 与skb_copy
skb_copy是一个深拷贝,skb_clone只是一个浅拷贝
1、skb_clone()
Skb_clone()函数只是复制sk_buff结构,并不复制skb的数据缓冲区。Clone后的sk_buff结构与原始的sk_buff指向同一数据缓冲区。原始的和clone后的skb描述符的cloned值都会被置1,clone的skb描述符的users值置1,同时数据缓冲区的引用计数dataref增加1。
特别说明,skb_clone()函数复制的只是skb描述符,而复制后的skb与原始skb指向的是同一数据缓冲区,由于数据缓冲区并未加什么同步锁机制,因此skb_clone()操作的skb结构的数据缓冲区是不能被修改的。
2、skb_copy是对skb的数据完整复制
/*** skb_clone - duplicate an sk_buff* @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, gfp_t gfp_mask)
{struct sk_buff_fclones *fclones = container_of(skb,struct sk_buff_fclones,skb1);struct sk_buff *n;if (skb_orphan_frags(skb, gfp_mask))return NULL;if (skb->fclone == SKB_FCLONE_ORIG &&refcount_read(&fclones->fclone_ref) == 1) {n = &fclones->skb2;refcount_set(&fclones->fclone_ref, 2);} else {if (skb_pfmemalloc(skb))gfp_mask |= __GFP_MEMALLOC;n = kmem_cache_alloc(skbuff_head_cache, gfp_mask);if (!n)return NULL;n->fclone = SKB_FCLONE_UNAVAILABLE;}return __skb_clone(n, skb);
}
/*** skb_copy - create private copy of an 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, gfp_t gfp_mask)
{int headerlen = skb_headroom(skb);unsigned int size = skb_end_offset(skb) + skb->data_len;struct sk_buff *n = __alloc_skb(size, gfp_mask,skb_alloc_rx_flag(skb), NUMA_NO_NODE);if (!n)return NULL;/* Set the data pointer */skb_reserve(n, headerlen);/* Set the tail pointer and length */skb_put(n, skb->len);BUG_ON(skb_copy_bits(skb, -headerlen, n->head, headerlen + skb->len));skb_copy_header(n, skb);return n;
}
例子
static struct sock *__udp4_lib_err_encap(struct net *net,const struct iphdr *iph,struct udphdr *uh,struct udp_table *udptable,struct sk_buff *skb, u32 info){....................network_offset = skb_network_offset(skb);transport_offset = skb_transport_offset(skb);/* Network header needs to point to the outer IPv4 header inside ICMP */skb_reset_network_header(skb);/* Transport header needs to point to the UDP header */skb_set_transport_header(skb, iph->ihl << 2);..............................
}
内核网络数据结构-SKB相关推荐
- Linux内核--网络栈实现分析(二)--数据包的传递过程--转
转载地址http://blog.csdn.net/yming0221/article/details/7492423 作者:闫明 本文分析基于Linux Kernel 1.2.13 注:标题中的&qu ...
- Linux内核网络栈1.2.13-tcp.c概述
参考资料 <<linux内核网络栈源代码情景分析>> af_inet.c文件中调用函数在协议层的实现 本文主要根据在af_inet.c文件中根据初始化不同的协议,来调用不同的协 ...
- linux 内核网络协议栈
Linux网络协议栈之数据包处理过程 1前言 本来是想翻译<The journey of a packet through the linux 2.4 network stack>这篇文 ...
- Linux内核网络中数据报在协议层的处理
1. 前言 本文主要分析数据报从 IP 协议层进入协议栈,通过udp协议层,到达 socket,最终被用户程序读取的过程.在此过程中,介绍了 IP 协议层和 UDP 协议层中监测数据和网络调优的方法. ...
- Linux内核网络(一)——初探内核网络
本文将从宏观上介绍Linux内核网络协议栈和网络设备驱动程序,介绍了两个很重要的结构(net_device和sk_buff),更深入更详细的内容将在以后的文章中介绍. 首先,我们需要了解网络分层模型. ...
- linux内核网络协议栈--监控和调优:接收数据(十五)
译者序 本文翻译自 2016 年的一篇英文博客 Monitoring and Tuning the Linux Networking Stack: Receiving Data.如果能看懂英文,建议阅 ...
- Linux 内核网络协议栈运行原理
封装:当应用程序用 TCP 协议传送数据时,数据首先进入内核网络协议栈中,然后逐一通过 TCP/IP 协议族的每层直到被当作一串比特流送入网络.对于每一层而言,对收到的数据都会封装相应的协议首部信息( ...
- Linux内核网络数据包处理流程
Linux内核网络数据包处理流程 from kernel-4.9: 0. Linux内核网络数据包处理流程 - 网络硬件 网卡工作在物理层和数据链路层,主要由PHY/MAC芯片.Tx/Rx FIFO. ...
- Linux内核网络栈1.2.13-route.c概述
参考资料 <<linux内核网络栈源代码情景分析>> route路由表概述 在IP协议的实现中,只要发送数据包都要查询路由表,选择合适的路由选项,确定下一站的地址,并构造MAC ...
- Linux内核网络栈1.2.13-icmp.c概述
参考资料 <<linux内核网络栈源代码情景分析>> icmp协议 在实现的过程中, ICMP协议工作再IP协议之上,但又不与TCP协议工作再一级,而是在下一级,在一般ICMP ...
最新文章
- OpenGLES 关于 数学 的分支 - 线性变化量、离散量、随机量
- python字符串去重保持原顺序_python实现文本去重且不打乱原本顺序
- 使用 Android 实现联网
- 一篇网易获奖的产品分析来敲门:『有道云笔记』产品体验报告
- oracle建立dblink
- 玹疯:这些年我走过的弯路
- 石子归并 51Nod - 1021
- 简单工程验收单表格_中铁超大型工程项目-123个精细化管理手册配套表格附件,超全...
- MotifStack:多motif序列比较和可视化
- python安装库pandas_安装python的第三方库 geopandas
- python批量安装第三方库_使用Python批量安装第三方库
- Python 爬虫 —— scrapy
- linux实现快捷键,Linux Bash下如何实现快捷键效果
- idea2018破解码
- 参考文献标号字体_参考文献标号字体 参考文献标准格式字体
- 易灵思FPGA-下载器选择指南
- 云流送技术可以支持多人交互吗?
- IT小白重装系统大全
- (十四)ATP应用测试平台——使用docker-compose一键式安装ATP应用测试平台的依赖服务
- [转贴]杨式太极拳八十五式通释—2—王志远
热门文章
- Sutton reinforcement learning _ Chapter 2 Multi-armed Bandits
- linux目录名乱码,Linux下文件名乱码解决
- SOtime -- JS的时间戳与日期转换操作
- 安卓手机远程连接linux系统,电脑(Linux/Windows)使用SSH远程登录安卓(Android)手机实现无线传输和管理文件(图文详解)-Go语言中文社区...
- 基于java SSM框架的医院体检管理系统
- MYSQL常见命令-Java学习之数据库学习
- 台式机关闭计算机时没有待机,台式机设置休眠的方法
- 求解最大连续子序列和问题———分治法
- P5385 [Cnoi2019]须臾幻境(LCT+主席树,思维题)
- URL中带特殊字符的处理方式