本文共 6626 字,大约阅读时间需要 22 分钟。
------------------------------------------------------------------------------------------
|----------------------------------|
应用层
|----------------------------------|
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规则将通过这一结构进行
下发
转载地址:http://dwhbi.baihongyu.com/