socket和send两个系统调用为例,协议栈是如何工作

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值