如果不理解这种分割方法,可以将其分成公用,通用,专用部分来记忆,共用部分是socket结构,通用部分是sock结构,而专用部分是具体协议族结构,比如Inet_sock结构体。假如把上述结构都合在一起,socket的定义中,必然形成一个庞然大物,很多与具体协议相关的结构变量大多数时间都是处于不使用的空闲状态,但是分配结构空间时,还是要同事为他们开辟相应的内存空间,这样的使用方法不灵活还要浪费大量的系统内存。
图2.1 说明了,socket, sock以及inet_sock 结构体中分隔及包含关系,读者清楚了分割结构的原因后,对今后众多不同的结构体就不会感到杂乱无序了。面对结构体时,首先不是立刻分析它,而是先推理其产生的原因,随后在代码的理解中详细体会其作用。这个过程不仅有趣味,而且潜移默化的掌握了结构的使用技巧,碰到巨型的数据结构也会轻松理解。
sk_buff数据包结构体是一个非常重要的数据结构,每个协议都适用该结构体用于封装,载运数据包,也就是说,每一个数据包都要用一个sk_buff,数据结构来表示,这里只列出重要的部分的内容。
代码清单2.3 sk_buff 结构的定义
struct sk_buff {
/*这两个变量必须放在前面*/
struct sk_buff *next;
struct sk_buff *prev; // 队列中的前一个数据包
struct sock* sk;//指向所属的sock结构
ktime_t tstamp;//数据包达到的时间
struct net_device *dev; //接收数据包的网络设备
union {
struct dst_entry *dst;//路由项
STRUCT RTABLE *RTABLE;//路由表
};
struct sec_path *sp; //用于xfrm 的安全路径
//下方的cb是控制数据块,每个层使用,如果想传输自定义的变量内容,则他们放在这个数组中,前提是必须通过skb_clone 函数克隆一个数据包
char cb[48];
unsigned int len;//全部数据块的总长度
data_len //分段,分散数据块的总长度
__u16 mac_len; 物理地址长度
hdr_len;//在克隆数据包时可写的头部长度
union {
__wsum csum;//校验和也可以称作检验和用于验证目的
struct {
__u16 csum_start;//检验和在数据包头部skb->head 中的起始位置
__u16 csum_offset;//校验和保存到csum_start中的位置
};
};
__u32 priority;//数据包在队列中的优先级
__u8 local_df:1//是否允许本地数据分段
cloned:1 是否允许被克隆
ip_summed:2 IP校验和标志
nohdr:1 运载时使用,表示不能修改头部
nfctinfo:3 //数据包链接关系
__u8 pkt_type:3 数据包的类型
fclone:2 数据包克隆的状态
ipvs_property:1 //数据包所属的ipvs
peeked:1 书包是否处于操作状态
nf_trace:1 //netfilter 对数据包的跟踪标志
__be16 protocol;//底层驱动使用的数据包协议
void (*destructor)(struct sk_buff *skb); //数据包销毁的函数
struct nf_bridge_info *nf_bridge ; 关于网桥的数据
sk_buff_data_t transport_header;指向数据块中传输层头部
sk_buff_data_t network_header 指向数据块中网络层头部
sk_buff_data_t mac_header 物理层头部
//下面这些内容必须放在结构末尾,参考alloc_skb 函数的内容
sk_buff_data_t tail;//指向数据块的结束地址
sk_buff_data_t end//指向缓冲块的结束地址
unsigned char *head;//指向缓冲块的开始地址
*data //指向数据块的开始地址
unsigned int truesize;//数据包的实际长度,结构长度与数据块长度之和
atomic_t users;//数据包使用的计数器
}
tcp_sock结构定义非常大,其内容与tcp协议紧密相关,它的重要作用随着后续的分析会越来越清晰,这里只列出部分内容。
代码清单2.4 tcp_sock结构的定义。
struct tcp_sock {
//inet_connection_scok //结构变量必须处于tcp_sock头部,原因后面解释
struct inet_connection_sock inet_conn;
u16 tcp_header_len; //发送的tcp头部字节数
u16 xmit_size_goal;//分段传送的数据包数量
/*
头部的预置位
*/
__be32 pred_flags;
//根据RFC 793 标准的定义的变量,可以参考RFC793和RFC1122了解这些内容
u32 rcv_nxt;//下一个要接收的目标
u32 copied_seq; 代表还没有读取的数据
u32 rcv_wup;//rcv nxt在最后一次窗口更新时内容
u32 snd_nxt; 下一个要发送的目标
u32 snd_una 第一个要ACK的字节
u32 snd_sml; 最近发送数据包的中的末尾字节
u32 rcv_tstamp;//最后一次接收到ACK的时间
u32 lsndtime; 最后一次发送数据包的时间
//直接复制给用户的数据
struct {
struct sk_buff_head prequeue;//预处理队列
struct task_struct *task;//预处理进程
struct iovec *iov;用户程序
int memory;//预处理数据包计数器
int len;//预处理长度
#ifdef CONFIG_NET_DMA
//异步复制内容
struct dma_chan *dma_chan;
struct dma_pinned_list *pinned_list;
dma_cokkie_t dma_cookie;
}ucopy;
u32 snd_wll;//窗口更新的顺序
u32 snd_wnd;//期望接收的窗
u32 max_window; //从对方获得最大窗口
u32 mms_cache;//有效的mss缓存,不包括SACKS
u32 window_clamp;//对外公布的最后窗口
u32 rcv_ssthresh;//当前窗口
u32 frto_highmark;//在RTO时的snd_nxt;
u8 reordering;//预设的数据包数量
u8 frto_counter;//RTO后的ack次数
u8 nonagle;//是否使用Nagle算法
u8 keepalive_probes;//允许持有的数量
u32 packets_out;//处于飞行中的数据包数量
u32 retrans_out;//转发的数据包数量
//接收选项
struct tcp_options_received rx_opt;
//慢启动与阻塞控制
u32 snd_ssthresh;慢启动的起点值
u32 snd_cwnd;//发送的阻塞窗口
u32 snd_cwnd_cnt;//线性计数器
u32 snd_cwnd_clamp;//不允许snd_cwnd 超过的值
u32 snd_cwnd_stamp;
struct sk_buff_head out_of_order_queue;//超出分段规则队列
u32 rcv_wnd;//当前接受窗口
u32write_seq;//tcp发送数据的顺序号
u32 pushed_seq;//最后送出的顺序号,需要通知窗口
//接收队列空间
struct {
int space;
u32 seq;
u32 time;
}rcvq_space;
//TCP指定的MTU检验内容
struct {
u32 probe_seq_start;
u32 probe_seq_end;
}stu_probe;
};