分类:
2012-03-27 20:25:46
原文地址:深入理解linux内核 作者:jiangsheng84
转载自:http://hi.baidu.com/amy_yeni/blog/item/9dd9b4f5fb8e5b66dcc4744c.html
首先以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被定义(http://hi.baidu.com/linux_kernel/blog/item/b108cc11bca1b110b9127bb0.html )
当前,内核最多支持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成员函数,而该函数真正实现了对应协议的数据报文发送工作。