第三章 套接字相关数据结构--基于Linux3.10

本章是对socket通信过程中使用到的比较重要的据结构罗列和意义的阐述,在阅读其它层的代码前,先来看几个重要的数据结构,这几个数据结构贯串四层模型。

 3.1 socket对应的内核结构体

在用户空间使用socket()函数创建一个套接字。对应的系统调用就是:

asmlinkagelong sys_socketcall(int call, unsigned long __user *args);该系统调用的定义在net/socket.c文件的2436行,调用流程中比较重要的一个函数是:

int __sock_create(struct net *net, int family, int type, int protocol, 
struct socket **res, int kern)
{
struct socket*sock;
const struct net_proto_family *pf;

sock = sock_alloc(); //内存空间分配
//根据对应的协议族(protocol family)创建对应的sock。
err = pf->create(net, sock, protocol, kern);
if (err < 0)
goto out_module_put;
}

该函数首先创建一个structsocket的类型结构体,该结构体对应于用户空间的socket,socket的参数之一是协议族,对于Internet协议,create的函数原型是inet_create,internet对应协议族在内核中的表示如下:

static const struct net_proto_family inet_family_ops = {
.family = PF_INET,
.create = inet_create,
.owner  = THIS_MODULE,
};

这里的inet_create函数作用是创建一个inet协议族下的套接字,并且初始化其中的一些成员。

该套接字传递到内核后,内核会创建structsocket存储来该数据结构:

struct socket {
socket_state  state;           //标记sock状态,如SS_CONNECTED、SS_CONNECTING等
short type;                        //socket类型SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等。
unsigned long  flags;              //socket flag 如SOCK_ASYNC_NOSPACE
struct socket_wq __rcu*wq;      
struct file  *file;                 //垃圾回收的文件指针
struct sock  *sk;                 //因特网内部协议的socket表示,对于PF_INET协议,inet_create会创建该成员的各个字段。
const struct proto_ops*ops;              //协议族相关的操作函数集
};

在应用层socket表示套接字,在网络层(IP层)structsock对应应用层中的套接字。

<include/net/sock.h>

struct sock {
socket_lock_t  sk_lock;  //该sock的访问锁
struct sk_buff_headsk_receive_queue;  //接收到的数据包都放在这个sk_buff_head所指向的队列的头上。
struct {
atomic_t  rmem_alloc;
int len;
struct sk_buff   *head;
struct sk_buff   *tail;
} sk_backlog; //对于接收的frame,其由IP层存放在backlog上,后通过tcp的函数进行接收。
#define sk_rmem_alloc sk_backlog.rmem_alloc
int sk_forward_alloc;  //对于到达的frame非本机,允许forward将会被发送出去
#ifdef CONFIG_RPS //网卡新特性,下篇涉及
__u32 sk_rxhash;
#endif
atomic_t  sk_drops; //丢弃的sock计数器
int sk_rcvbuf;

#ifdef CONFIG_XFRM
struct xfrm_policy  *sk_policy[2];   ///流控策略,属于安全机制
#endif
unsigned long 
sk_flags;
struct dst_entry  *sk_rx_dst; //接收流向的
struct dst_entry __rcu  *sk_dst_cache; //路由项的cache
spinlock_t  sk_dst_lock; //路由锁
int sk_sndbuf;
struct sk_buff_headsk_write_queue;    //发送队列
/*sock 信息、状态的一些标志*/
unsigned int  sk_shutdown  : 2,
sk_no_check  : 2,
sk_userlocks : 4,
sk_protocol  : 8,
sk_type      : 16;
gfp_t sk_allocation; //sock动态获申请内存的Flag标志。
/*网卡的一些信息也记录到这里了*/
netdev_features_tsk_route_caps;
netdev_features_tsk_route_nocaps;
int sk_gso_type;
unsigned int  sk_gso_max_size;
u16 sk_gso_max_segs;
/*sock 的一些错误统计信息在此处*/
struct sk_buff_headsk_error_queue;
struct proto  *sk_prot_creator;
rwlock_t  sk_callback_lock;
int sk_err,
sk_err_soft;
unsigned shortsk_ack_backlog;
unsigned shortsk_max_ack_backlog;
__u32 sk_priority;
/*接收和发送的时间戳*/
long  sk_rcvtimeo;
long  sk_sndtimeo;
void  *sk_protinfo;
struct timer_listsk_timer;
ktime_t  sk_stamp;
struct socket  *sk_socket;
/*分片信息*/
struct page_fragsk_frag;
struct sk_buff   *sk_send_head;// /*分片头信息*/
/*sock自带的一些函数指针集*/
void (*sk_state_change)(struct sock *sk);
void (*sk_data_ready)(struct sock *sk, int bytes);
void (*sk_write_space)(struct sock *sk);
void (*sk_error_report)(struct sock *sk);
int (*sk_backlog_rcv)(struct sock *sk, struct sk_buff *skb);
void       (*sk_destruct)(struct sock *sk);
};

3.2 struct proto_ops

<net/ipv4/af_inet.c>

const struct proto_ops inet_stream_ops = {
.family    = PF_INET,//协议族
.owner    = THIS_MODULE,
.release    = inet_release,
.bind   = inet_bind,
.connect    = inet_stream_connect,
.socketpair    = sock_no_socketpair,
.accept    = inet_accept,
.getname    = inet_getname,
.poll   = tcp_poll,
.ioctl    = inet_ioctl,
.listen    = inet_listen,
.shutdown    = inet_shutdown,
.setsockopt    = sock_common_setsockopt,
.getsockopt    = sock_common_getsockopt,
.sendmsg    = inet_sendmsg,
.recvmsg    = inet_recvmsg,
};

上面函数的指针用户空间时常会用到,sendmsg  和recvmsg   是介于应用层和传输层之间的收发函数。

3.3 structproto

<net/ipv4/tcp_ipv4.c>

struct proto tcp_prot = {
.name = "TCP",
.owner  = THIS_MODULE,
.close  = tcp_close,
.connect  = tcp_v4_connect, //建立连接使用到的函数,对应于用户空间的connect函数
.disconnect  = tcp_disconnect,
.accept  = inet_csk_accept,
.ioctl  = tcp_ioctl,
.init = tcp_v4_init_sock,
.destroy  = tcp_v4_destroy_sock,
.shutdown  = tcp_shutdown,
.setsockopt  = tcp_setsockopt, 
}
该结构体描述的是tcp处理各种任务的若干函数,这些任务包括tcp链接的建立、控制等,这些数据都是由sk_buff_head(用于描述套接字缓存区头)结构体管理,数据本身会存在2.4节描述的sk_buff里。这个buffer使用通过proc的slabinfo可以看到,曾在一个嵌入式视频监控设备上就遇到过由于WiFi导致的sk_buff_head和sk_buff不定时异常增大的情况。

struct sk_buff_head {
/* These two members must be first. */
struct sk_buff*next; //下一个数据存放指针
struct sk_buff*prev;//前一个数据存放指针,next和prev会串接成一个双链表,qlen用于标记双链表的长度
__u32 qlen;//标记
spinlock_t  lock; //保护该结构体的锁
};

3.4 sk_buff(SKB)

SKB存储了用户要求传递的数据,这些数据可能源于视频、图像、文本等,应用层传递到TCP/IP协议栈的数据会保存在sk_buff,不论是http还是rtsp,数据会一直存在sk_buff的结构成员中直到从网卡发送出去,接收也是类似的。网络数据包收发如此频繁,可以想象该结构体必然针对协议实现特点、处理流程以及内存等方面做了一些优化。

<include/linux/skbuff.h>

struct sk_buff {
/* These two members must be first. */
struct sk_buff*next; //指向该SKB的后一个SKB,其头就是上面sk_buff_head 指定的成员。
struct sk_buff*prev; //指向该SKB的前一个SKB
ktime_t  tstamp; //数据包到达的时间戳
struct sock  *sk;  //对应的sock成员,即应用程序的socket在内核的代表,
struct net_device*dev;   //网络设备,数据到达的网络设备或者数据离开的网络设备
//control buffer,协议栈很多地方都使用到了这个字段来存储一些会使用到的信息。
char cb[48] __aligned(8);   
unsigned long  _skb_refdst; //目的入口项
#ifdef CONFIG_XFRM
struct  sec_path *sp; //xfrm安全机制使用,Security path。
#endif
unsigned int  len,  //数据实际长度值
data_len;  //数据的长度,和真实长度的区别在于可能有padding
__u16 mac_len, //MAC的长度
hdr_len; //拷贝skb时,可更改的头长度
union {
__wsum  csum;  //校验和
struct {
__u16 csum_start; //校验和计算起始地址
__u16 csum_offset;//从csum_start开始的校验,这部分校验和会被存储。
};
};
__u32 priority; //packet排队的优先级
kmemcheck_bitfield_begin(flags1);
__u8 local_df:1,  //允许本地分片的标志
cloned:1, //标记头是否可能拷贝,如果不对数据执行更改操作,则只会拷贝头。
ip_summed:2, //驱动程序填写的IP层校验和标志。
nohdr:1, //负载使用
nfctinfo:3;//SKB和tcp连接的关系
__u8 pkt_type:3, //packet所属的类
fclone:2, //复制状态标志,标识该SKB是复制的。
ipvs_property:1,//该SKB为ipvs所有。IP virtual Server,负载均衡,netfilter框架调用
peeked:1, //标志标识统计信息是否还要更新
nf_trace:1;//netfilter 包跟踪标志
kmemcheck_bitfield_end(flags1);
__be16  protocol;  //packet所属的协议
void (*destructor)(struct sk_buff *skb);//解析函数
int skb_iif; //该packet所在设备的接口索引
__u32 rxhash; //接收数据包的哈希标志
__u16 queue_mapping; //支持多队列网卡设备的队列映射
kmemcheck_bitfield_begin(flags2);
__u8 pfmemalloc:1;
__u8 ooo_okay:1;
__u8 l4_rxhash:1;
__u8 wifi_acked_valid:1;
__u8 wifi_acked:1;
__u8 no_fcs:1;
__u8 head_frag:1;
sk_buff_data_tinner_transport_header;   //MAC头、IP头、tcp头。前三个是指封装过的。
sk_buff_data_tinner_network_header;
sk_buff_data_tinner_mac_header;
sk_buff_data_ttransport_header;
sk_buff_data_tnetwork_header;
sk_buff_data_tmac_header;
/* These elements must be at the end, see alloc_skb() for details.  */
sk_buff_data_ttail;
sk_buff_data_tend; //数据的相关指针
unsigned char  *head, *data;
unsigned int  truesize;
atomic_t  users;
};

3.5  softnet_data

softnet_data是一个per-CPU变量,即每个CPU都有一个自己的softnet_data结构体,相比只有一个该结构体由多个CPU共享的变量,每个CPU都有一个队列可以减少锁操作。该结构体管理接收和发送的数据。定义于include/linux/netdevice.h文件。

struct softnet_data {
struct Qdisc  *output_queue;  //有数据包要发送的设备。
struct Qdisc  **output_queue_tailp; //上述结构体的待处理的最后一个元素的指针。
struct list_head  poll_list;
struct sk_buff  *completion_queue; //已经成功发送,占用的空间可以释放了。
struct sk_buff_head   process_queue;
/* stats */
unsigned int  processed;   //每一个处理该数据包的进程会将这里的计数器加1,以标记有多少个进程在其sk_buff。
unsigned int  time_squeeze;
unsigned int  cpu_collision;
unsigned int  received_rps;
#ifdef CONFIG_RPS //网卡多队列,Receive Packet Steering,网卡的硬件特性。
struct softnet_data*rps_ipi_list;
/* Elements below can be accessed between CPUs for RPS */
struct call_single_datacsd ____cacheline_aligned_in_smp;
struct softnet_data*rps_ipi_next;
unsigned int  cpu;
unsigned int  input_queue_head;
unsigned int  input_queue_tail;
#endif
unsigned int  dropped;
struct sk_buff_headinput_pkt_queue; //在net_dev_initz中初始化,在网卡驱动程序处理以前,sk_buff链接到该链表上。
struct napi_structbacklog; //NAPI 处理最开始的那两个元素。
};

3.6 struct packet_type

struct packet_type {
__be16  type; /* This is really htons(ether_type). 标记类型,对于IP而言是 cpu_to_be16(ETH_P_IP),*/ 
struct net_device*dev;/* NULL is wildcarded here    */
//该函数是四层网络模型中的网络层的函数,对于ipv4是ip_rcv,这是在之三文章中tcp/ip协议栈的网络层从网络到主机层接收数据包的函数。
int (*func) (struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *);   
bool (*id_match)(struct packet_type *ptype,  struct sock *sk);
void *af_packet_priv;
struct list_headlist;
};

netif_receive_skb函数会从ptype_base协议链表上查找和数据包的type对应的func处理程序,对于IP数据包,其类型是ETH_P_IP,服务函数是ip_rcv。此外还有和TCP/IP相关的一些重要数据结构。

3.7 一些名词简称

csk ---connection sock
icsk--- inet connection sock
ca –congestion avoid
cwr congestion window reduction(cwnd reduction)
ECN: Explicit Congestion Notification
SACK:selective ACK
PSH (1 bit) – Push function. Asks to push the buffered data to the receiving application
TIME-WAIT :
(either server or client) represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.







  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shichaog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值