------------------------------------------------------------------------------------------ |----------------------------------|
应用层 |----------------------------------|
BSD Socket层 |----------------------------------|
Inet Socket层 |----------------------------------|
IP层 |----------------------------------|
数据链路/硬件层 |----------------------------------| IP层:
IP协议栈的实现,完成路由的查找过程(主要处理skb) Inet Socket层: 对IP包进行分组排序,实现QoS,传输层协议TCP/UDP协议栈的实现
使用sock{}类型数据来管理会话,数据主要放在sk_buff结构中 BSD Socket:
对于BSD Socket相关调用的实现,主要使用socket{}结构来存放连接
数据主要是放在msghdr{}结构中 msghdr
结构
struct msghdr {
void *
msg_name;
int
msg_namelen;
struct iovec *
msg_iov;
__kernel_size_t msg_iovlen;
void
*msg_control;
__kernel_size_t
msg_controllen;
unsigned
msg_flags; }; msg_name:
socket的名字,通常用NULL初始化 msg_iov:
发送或接收缓冲区地址数据 msg_iovlen:
msg_iov中保存的数据包的个数 msg_flags:
保存数据接收时的控制标志,可以由应用 层控制,如果设置为MSG_DONTWAIT,则表示使用非阻塞方式收取数据包
struct iovec
{
void __user *iov_base;
__kernel_size_t iov_len; }; 实际上,msghdr中存储的iovec是一个数组,每个iovec都有自己单独的数据起始地址和 数据长度,以下的代码将skb->data(kdata)用copy_to_user传到用户态,用户态的BSD Socket接口将分段的数据进行合并,组成一段连续的内存buffer. int memcpy_toiovec(struct iovec *iov, unsigned char *kdata, int len) {
while (len > 0) {
if (iov->iov_len) {
int copy = min_t(unsigned int, iov->iov_len, len);
if (copy_to_user(iov->iov_base, kdata, copy))
return -EFAULT;
kdata += copy;
len -= copy;
iov->iov_len -= copy;
iov->iov_base += copy;
}
iov++;
}
return 0; } 但是,我发现linux2.6.23中出现了新的函数dma_memcpy_to_iovec,具体功能有可能是 通过DMA控制器将数据包从内核拷贝到用户态,这将会对网络收包的效率有一定的改善. linux内核2.6.14版本却没有相关的代码支持,很可能是一个新的标准出现了. 关于SKB
SKB结构中最大的变化莫过于将union舍弃,也就是说,由
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; 变成了 sk_buff_data_t
transport_header;
sk_buff_data_t
network_header;
sk_buff_data_t
mac_header; 结构变得更加的简单了,对于获取IP头的操作,发生了如下 变化 在linux 2.4~linux 2.6.20下: struct iphdr *ip = skb->nh.iph; 到了linux 2.6.22下,就变成了: struct iphdr *ip = ip_hdr(skb); 这里是函数ip_hdr的定义,调用了skb_network_header 函数用以获取IP头:
static inline struct iphdr *ip_hdr(const struct sk_buff *skb) {
return (struct iphdr *)skb_network_header(skb); } 但是函数skb_network_header又有不同的实现,依赖于宏NET_SKBUFF_DATA_USES_OFFSET是否进行了定义 #if BITS_PER_LONG > 32 #define NET_SKBUFF_DATA_USES_OFFSET 1 #endif 这里完成了NET_SKBUFF_DATA_USES_OFFSET宏的定义,其实很简单,只是看目前 的系统是32位系统还是64位系统 #ifdef NET_SKBUFF_DATA_USES_OFFSET static inline unsigned char *skb_transport_header(const struct sk_buff *skb) {
return skb->head + skb->transport_header; } #else static inline unsigned char *skb_network_header(const struct sk_buff *skb) {
return skb->network_header; } 以上是*skb_network_header函数的实现,可以看到,对于32位系统和64位系统,skb_network_header 函数有不同的实现,所以在编程中,直接调用ip_hdr函数是好的,而不要直接通过skb->network_header 获取IP Header 对于传输层协议,也应该调用tcp_hdr或者udp_hdr来进行解析,而不要调用skb中的transport_header static inline struct tcphdr *tcp_hdr(const struct sk_buff *skb) {
return (struct tcphdr *)skb_transport_header(skb); } static inline struct udphdr *udp_hdr(const struct sk_buff *skb) {
return (struct udphdr *)skb_transport_header(skb); } #ifdef NET_SKBUFF_DATA_USES_OFFSET static inline unsigned char *skb_transport_header(const struct sk_buff *skb) {
return skb->head + skb->transport_header; } #else static inline unsigned char *skb_transport_header(const struct sk_buff *skb) {
return skb->transport_header; } 如果tcph没有解析出来的话,依然可以用 ip_hdr(skb)->ihl*4找到tcp header的偏移 skb
常用的操作
实际上在head和data,tail和end之间,存在着空洞,所以SKB可以向上或者向下进行扩展 skb_put() 将数据添加到现有数据的尾部 skb_push() 将数据添加到现有数据的头部 skb_pull() 从数据头部开始缩减 skb_trim() 从数据尾部开始缩减 socket
结构
socket结构主要用于BSD Socket层存储连接信息,应用程序需要一个文件描述符来和socket结构 进行对应 struct socket {
socket_state
state;
unsigned long
flags;
const struct proto_ops
*ops;
struct fasync_struct
*fasync_list;
struct file
*file;
struct sock
*sk;
wait_queue_head_t wait;
short
type; }; 这个结构在2.6.14和2.6.22中没有变化 socket_state描述了连接的状态,可以是以下枚举类型 typedef enum {
SS_FREE = 0,
SS_UNCONNECTED,
SS_CONNECTING,
SS_CONNECTED,
SS_DISCONNECTING } socket_state; flags:用户层的一些控制信息 在2.4内核中,使用一个inode结构来进行文件描述符和一个socket之间的对应,所以在socket 结构中有一个inode成员,2.6内核中舍弃了这一成员,在socket创建函数sock_alloc中,使用 SOCKET_I函数根据新生成的inode结构返回相对应的socket结构,以后inode结构就再也用不着了. file:用于垃圾收集 sk:指定了Inet socket层中sock结构的指针,同时在sock结构中也包含了socket函数的指针,
所以sock和socket结构是一一对应的关系 sock结构
struct sock {
struct sock_common
__sk_common;
#define sk_family
__sk_common.skc_family
#define sk_state
__sk_common.skc_state
#define sk_reuse
__sk_common.skc_reuse
#define sk_bound_dev_if
__sk_common.skc_bound_dev_if
#define sk_node
__sk_common.skc_node
#define sk_bind_node
__sk_common.skc_bind_node
#define sk_refcnt
__sk_common.skc_refcnt
#define sk_hash
__sk_common.skc_hash
#define sk_prot
__sk_common.skc_prot
unsigned char
sk_shutdown : 2,
sk_no_check : 2,
sk_userlocks : 4;
unsigned char
sk_protocol;
unsigned short
sk_type;
int
sk_rcvbuf;
socket_lock_t
sk_lock;
struct {
struct sk_buff *head;
struct sk_buff *tail;
} sk_backlog;
wait_queue_head_t *sk_sleep;
struct dst_entry
*sk_dst_cache;
struct xfrm_policy
*sk_policy[2];
rwlock_t
sk_dst_lock;
atomic_t
sk_rmem_alloc;
atomic_t
sk_wmem_alloc;
atomic_t
sk_omem_alloc;
int
sk_sndbuf;
struct sk_buff_head
sk_receive_queue;
struct sk_buff_head
sk_write_queue;
struct sk_buff_head
sk_async_wait_queue;
int
sk_wmem_queued;
int
sk_forward_alloc;
gfp_t
sk_allocation;
int
sk_route_caps;
int
sk_gso_type;
int
sk_rcvlowat;
unsigned long
sk_flags;
unsigned long
sk_lingertime;
struct sk_buff_head
sk_error_queue;
struct proto
*sk_prot_creator;
rwlock_t
sk_callback_lock;
int
sk_err,
sk_err_soft;
unsigned short
sk_ack_backlog;
unsigned short
sk_max_ack_backlog;
__u32
sk_priority;
struct ucred
sk_peercred;
long
sk_rcvtimeo;
long
sk_sndtimeo;
struct sk_filter
*sk_filter;
void
*sk_protinfo;
struct timer_list
sk_timer;
ktime_t
sk_stamp;
struct socket
*sk_socket;
void
*sk_user_data;
struct page
*sk_sndmsg_page;
struct sk_buff
*sk_send_head;
__u32
sk_sndmsg_off;
int
sk_write_pending;
void
*sk_security;
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); }; sk_rcvbuf:接收缓冲区的长度限制 sk_sndbuf:发送缓冲区的长度限制 sk_rmem_alloc:已经申请的接收缓冲区长度 sk_wmem_alloc:已经申请的发送缓冲区长度 sk_receive_queue:等待接收的数据包队列头 sk_write_queue:等待发送的数据包队列头 sk_forward_alloc:为这个sock结构提前申请的数据区大小 sk_omem_alloc:当前申请的sock选项的空间大小 sk_dst_cache:dst_entry结构的链表,作为路由地址链表 sk_filter:这个结构比较有趣,关联到内核中BPF的实现,BPF规则将通过这一结构进行 下发