概述
前言
今天来聊下sk_buff
。如果把内核网络协议栈比作一个人,那么sk_buff
就是流淌在他体内血管里红血球,它运输养分(数据)走遍全身(协议栈每一层)。一个sk_buff
就是一个报文。
报文的表示方式
在协议栈层次模型中,我们提到过,报文在协议栈各层之间穿梭,发送方向不断加上Header
,接收方向不断去掉Header
。自然而然,我们要设计数据结构去表示在各个层上形态各异的报文
我们可以这么设计IP
报文:
struct ip_pdu{
struct ip_hdr ip_hdr;
char* payload;
}
那TCP
报文呢, 就像下面这样
struct tcp_pdu{
struct tcp_hdr tcp_hdr;
char* payload;
}
然后层与层之间进行报文数据结构的拷贝和转换。
等等!网络协议那么多,难道要每种协议设计一个新的数据结构?!另外,像这样每个报文都要拷贝多次的话效率也太低了吧?!
所以,linux采用的报文层间传递的方式就是一个结构,传递指针,这个结构就是sk_buff
, 也就是说, 无论是哪个层次的报文, 在内核中始终都以sk_buff
表示(sk_buff
是 socket buffer
的简称)
下面是sk_buff
结构的构成(精简后)
<skbuff.h>
typedef unsigned char *sk_buffer_data_t;
struct sk_buff{
struct sock *sk;
struct net_device *dev;
char cb[40];
sk_buff_data_t transport_header;
sk_buff_data_t network_header;
sk_buff_data_t mac_header;
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head;
*data;
}
重点关注最后四个字段,准确的说是四个指针,
如上图所示,这四个指针的外面两个限定了一个大的缓冲区,而中间两个则是包裹了有效数据,所谓有效数据便是报文。这么设计的原因就是避免拷贝! 以发送方向为例,我们知道发送过程是一层一层的在报文前方贴Header
,因此,既然我们知道随着报文向下传递时,长度会增加。那么我们不如就在最初报文(应用层)的前面预留一定的空间,就可以避免向下传递时还要重新申请内存再拷贝了。
上图描述了在接收方向报文上送时的指针移动方向,之前说的去掉Header
就是指这个。
控制字段cb
是一个很有意思的字段,它是一个杂货间,没有标准的格式,因此可以存放各种数据,最多40个字节。当报文在协议栈中流动时,流到哪一层,哪一层就将自己的私有数据放到这个区域。比如TCP
用这个区域储存收到的TCP
报文头中的一些字段。
struct tcp_skb_cb{
......
u32 seq;
/* Starting sequence number
*/
u32 end_seq; /* SEQ + FIN + SYN + dalalen */
u32 when;
/* used to compute rtt
*/
u32 flags
/* TCP header flags
*/
}
而struct sock *sk
和struct net_device *dev
则分别代表了sk_buff
的起点和终点。对于发送方向,报文由应用层产生,自然是通过一个套接字到内核,最终从一个设备发送出去,这时sk
是起点,dev
是终点。而对于接收方向,报文由一个设备接收,最终被应用层通过一个套接字拿到,这时,这时dev
是起点,sk
是终点.
总结
- 协议栈使用
sk_buff
表示所有报文,sk_buff
在协议栈中传递时以指针传递并且不会拷贝 cb
是一个杂货间,每一层都可以使用sk
和dev
是报文的起点和终点,对发送方向和接收方向来说正好相反。
最后
以上就是秀丽月亮为你收集整理的走进Linux内核网络 报文是什么—sk_buff的全部内容,希望文章能够帮你解决走进Linux内核网络 报文是什么—sk_buff所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复