Chinaunix首页 | 论坛 | 博客
  • 博客访问: 112953
  • 博文数量: 24
  • 博客积分: 1335
  • 博客等级: 中尉
  • 技术积分: 415
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-25 10:07
文章分类
文章存档

2011年(22)

2010年(2)

分类:

2011-06-30 00:53:34

转载自: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成员函数,而该函数真正实现了对应协议的数据报文发送工作。

阅读(1435) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~