socket和send系统调用协议栈工作流程

3087阅读 0评论2013-01-08 chenmeng11
分类:系统运维

 首先以socket和send两个系统调用为例,来回顾一下协议栈是如何工作的,在这过程中可以找到如何在协议栈中增加对UDP协议的支持。socket系统调用的原型是
int socket(int domain, int type, int protocol);
domain是协议域,对于ipv4协议来说,其值是PF_INET(ipv4因特网协议),对于我们自己实现的ipv4协议模块,我们为其新增MY_PF_INET。所有的协议域在include/linux/socket.h被定义,如下:
#define AF_UNSPEC 0
#define AF_UNIX 1 // Unix域的socket
#define AF_LOCAL 1 // AF_UNIX的POSIX命名
#define AF_INET 2 // 因特网IP协议
#define AF_AX25 3 // Amateur Radio AX.25
#define AF_IPX 4 // Novell IPX
#define AF_APPLETALK 5 // AppleTalk DDP
#define AF_NETROM 6 // Amateur Radio NET/ROM
#define AF_BRIDGE 7 // Multiprotocol bridge
#define AF_ATMPVC 8 // ATM PVCs
#define AF_X25 9 // Reserved for X.25 project
#define AF_INET6 10 // IP version 6
#define AF_ROSE 11 // Amateur Radio X.25 PLP
#define AF_DECnet 12 // Reserved for DECnet project
#define AF_NETBEUI 13 // Reserved for 802.2LLC project
#define AF_SECURITY 14 // Security callback pseudo AF
#define AF_KEY 15 // PF_KEY key management API
#define AF_NETLINK 16
#define AF_ROUTE AF_NETLINK // Alias to emulate 4.4BSD
#define AF_PACKET 17 // Packet family
#define AF_ASH 18 // Ash
#define AF_ECONET 19 // Acorn Econet
#define AF_ATMSVC 20 // ATM SVCs
#define AF_SNA 22 // Linux SNA Project (nutters!)
#define AF_IRDA 23 // IRDA sockets
#define AF_PPPOX 24 // PPPoX sockets
#define AF_WANPIPE 25 // Wanpipe API Sockets
#define AF_LLC 26 // Linux LLC
#define AF_BLUETOOTH 31 // Bluetooth sockets
#define AF_MAX 32 // For now..
可以看到,当前,内核最多支持31个协议域(0为未指定,32为MAX)。而当前的定义中还有27,28,30为空,所以我们定义了MY_PF_INET为28。
在内核中,结构体struct net_proto_family用于表示一个协议域,而全局数组变量static struct net_proto_family *net_families[NPROTO]是一个有32项的数组,用于保存当前内核中所有已注册的协议域,函数sock_register用于把一个协议域注册到内核中,即把一个协议域跟net_families数组
中的某一项相关联。struct net_proto_family的完整定义如下:
struct net_proto_family {
int family;
int (*create)(struct socket *sock, int protocol);
short authentication;
short encryption;
short encrypt_net;
struct module *owner;
};
其中,family为域编号,对于我们的模块即为MY_PF_INET。通过sock_register函数,使net_families[MY_PF_INET]指向需要注册的域。create是该域的socket的创建函数,我们的MY_PF_INET域定义如下:
static struct net_proto_family myinet_family_ops = {
.family = MY_PF_INET,
.create = myinet_create,
.owner = THIS_MODULE,
};
现在回到socket系统调用上来,内核实现socket系统调用的函数是sys_socket。该函数通过调用sock_create进行创建,sock_create调用__sock_create。__sock_create要创建一个struct socket,这是一个普通BSD socket的结构体,其定义如下:
struct socket {
socket_state state;
unsigned long flags;
struct proto_ops *ops;
struct fasync_struct *fasync_list;
struct file *file;
struct sock *sk;
wait_queue_head_t wait;
short type;
};
__sock_create创建的时候,为其type赋上socket系统调用的第二个参数type,最后通过调用net_families[family]->create(sock, protocol)完成socket的创建。对于MY_PF_INET域来说,该create函数即myinet_create。MY_PF_INET域支持的网络层协议是IP协议,在该协议上支持的套接字接口有流套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)和原始套接字(SOCK_RAW)。在IP协议上注册一个套接字接口,也即创建一个套接字,需要知道该类型的套接字必需的一些相关信息。结构体struct inet_protosw就是用于在IP协议上注册套接字接口,其完整定义如下:
struct inet_protosw {
struct list_head list;
unsigned short type; //套接字类型,即socket系统调用的第二个参数。
int protocol; //第4层(传输层)协议号
struct proto *prot; //第4层协议的操作函数集
struct proto_ops *ops; //该类型的套接字的操作函数集
int capability;
char no_check;
unsigned char flags;
};
myinet_create函数注册套接字的过程本质上就是为指定套接字类型和第4层协议号的一个socket找到对应的操作函数集,使这个socket随后能真正被操作。全局数组inetsw_array包含了系统当前支持的所有在IP协议上能够注册的套接字接口,在系统初始化的时候,这些结构体以type作为依据,被组织到
static struct list_head inetsw[SOCK_MAX]中。当在inetsw数组中找到对应的socket类型和第4层协议号后,令struct socket->ops的值为struct inet_protosw->ops,即为该类型的套接字指定操作函数集。而struct socket->sk是网络层的套接字接口,其成员sk_prot的值为struct inet_protosw->prot,即为该类型的第4层协议指定操作函数集。套接字的创建工作大致如此。
接下来,再来看send系统调用,它的原型如下:
ssize_t send(int s, const void *buf, size_t len, int flags);
s是文件描述符,在内核中跟一个struct socket结构体建立一一对应的映射关系。buf和len分别为待发送数据的内容和长度,flag是一些标志位。内核实现该系统调用的函数是sys_send。sys_send直接调用sys_sendto,把sys_sendto的最后两个参数addr和addr_len置空。sys_sendto根据文件描述符s找到对应的struct socket,然后建立一个结构体struct msghdr msg用于发送数据内容,该结构体的定义如下:
struct msghdr {
void * msg_name; /* Socket 的名字 */
int msg_namelen; /* 名字的长度 */
struct iovec * msg_iov; /* 数据块 */
__kernel_size_t msg_iovlen; /* 数据块的数量 */
void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned msg_flags;
};
然后,sys_sendto调用sock_sendmsg发送数据,sock_sendmsg调用__sock_sendmsg,__sock_sendmsg调用struct socket->ops->sendmsg,即调用特定套接字类型的操作函数集中的sendmsg成员函数。比如,SOCK_RAW类型的套接字的sendmsg成员函数的实现如下(实际上SOCK_DGRAM类型的套接字的sendmsg成员函数也是这个):
int inet_sendmsg(struct kiocb *iocb, struct socket *sock,
struct msghdr *msg, size_t size)
{
struct sock *sk = sock->sk;
if (!inet_sk(sk)->num && inet_autobind(sk))
return -EAGAIN;
return sk->sk_prot->sendmsg(iocb, sk, msg, size);
}
可以看到,在该函数中,调用了具体的第4层协议的操作函数集中的sendmsg成员函数,而该函数真正实现了对应协议的数据报文发送工作。
上一篇:typedef的用法
下一篇:TCP流量控制