socket函数的domain、type、protocol解析

来源:互联网 发布:java 短链接 编辑:程序博客网 时间:2024/05/23 15:07


socket函数的domaintypeprotocol解析


                                                                                                                                 lxg@2015-04-09


  1. 内核中的socket概览

    图一:socket概览


内核中套接字是一层一层进行抽象展示的,把共性的东西抽取出来,这样对外提供的接口可以尽量的统一。内核中把套接字的定义会抽象出来展示,如struct sock->struct inet_sock->struct tcp_sock从抽象到具体。还会把套接字的操作也会抽象,下面我们会提到怎么进行抽象展示的。


Socket函数中的三个参数其实就是把抽象的socket具体化的条件,domain参数决定了图中所示的第二层通信域,type决定了第三层的通信模式,protocol决定了第四层真正的通信协议。


  1. Domain参数


Domain参数指定了通信的”(在后文中会用family替代domain),我们是在IPv4还是IPv6这个范围内通信,也就决定了我们通信的地址是IPv4格式还是IPv6格式。通常可选的定义如下:


名称

目的

AF_UNIX, AF_LOCAL

本地通信

AF_INET

IPv4网络通信

AF_INET6

IPv6网络通信

AF_PACKET

链路层通信


Linux系统中AF_*PF_*是等价的。


在内核源码中net目录下面有Af_开头的一系列文件(:Af_inet.cAf_inet6.cAf_unix.c),每一个文件分别代表了一种协议族。


Net.h中定义了一个结构体:


//net_proto_family结构体定义了每一个协议族的新建socket句柄

struct net_proto_family {

   int    family;

   int    (*create)(struct net *net,struct socket *sock,

                 int protocol,int kern);

   struct module   *owner;

};

//Af_inet.c中的PF_INET domain的定义

staticconststruct net_proto_family inet_family_ops ={

   .family= PF_INET,

   .create= inet_create,

   .owner = THIS_MODULE,

};


        在协议栈初始化的通过sock_register(conststruct net_proto_family *ops)(socket.c)函数把协议栈支持的协议族family加入net_families数组中。


        当我们通过socket系统调用创建套接字的时候流程走到__sock_create函数(SYSCALL_DEFINE3->sock_create->__sock_create)的时候根据familynet_families数组中取得对应协议族的create句柄,所以对于PF_INET协议族的套接字就是调用inet_create来新建socket


        通过上面分析可知family这个参数决定了调用哪个协议族create函数来新建socket,说得可能不准确点就是决定了你使用net/目录下面的哪个Af_*.c文件中的函数。


  1. Type参数


Type就是socket的类型,对于AF_INET协议族而言有流套接字(SOCK_STREAM)、数据包套接字(SOCK_DGRAM)、原始套接字(SOCK_RAW)


/**

 * enum sock_type - Socket types

 * @SOCK_STREAM: stream (connection) socket

 * @SOCK_DGRAM: datagram (conn.less) socket

 * @SOCK_RAW: raw socket

 * @SOCK_RDM: reliably-delivered message

 * @SOCK_SEQPACKET: sequential packet socket

 * @SOCK_DCCP: Datagram Congestion Control Protocol socket

 * @SOCK_PACKET: linux specific way of getting packets at the dev level.

 *       For writing rarp and other similar things on the user level.

*/

enum sock_type {

   SOCK_STREAM =1,

   SOCK_DGRAM  =2,

   SOCK_RAW    =3,

   SOCK_RDM    =4,

   SOCK_SEQPACKET  =5,

   SOCK_DCCP   =6,

   SOCK_PACKET =10,

};


在内核协议栈中有一个很重要的结构体,定义了每一个协议族中套接字在传输层的操作集合。PS:这里我理解传输层包括了套接字类型(STREAM,DGRAM)和具体的传输层协议(TCP,UDP)


/* This is used to register socket interfaces for IP protocols. */

struct inet_protosw {

   struct list_head list;

 

       /* These two fields form the lookup key. */

   unsignedshort  type;    /* This is the 2nd argument to socket(2). */

   unsignedshort  protocol;/* This is the L4 protocol number. */

 

   struct proto     *prot;

   conststruct proto_ops *ops;

 

   unsignedchar   flags;     /* See INET_PROTOSW_* below. */

};


struct proto_ops结构体定义了每一种套接字类型(SOCK_STREAMSOCK_DGRAMSOCK_RAW)的操作集合,在Af_inet.c中分别定义了inet_stream_opsinet_dgram_opsinet_sockraw_ops这三种类型的proto_ops


conststruct proto_ops inet_stream_ops ={

   .family       = PF_INET,

   .owner        = THIS_MODULE,

   .release      = inet_release,

   .bind         = inet_bind,

   .connect      = inet_stream_connect,

   .socketpair   = sock_no_socketpair,

   .accept       = inet_accept,

   .getname      = inet_getname,

   .poll         = tcp_poll,

   .ioctl        = inet_ioctl,

   .listen       = inet_listen,

   .shutdown     = inet_shutdown,

   .setsockopt   = sock_common_setsockopt,

   .getsockopt   = sock_common_getsockopt,

   .sendmsg      = inet_sendmsg,

   .recvmsg      = inet_recvmsg,

   .mmap         = sock_no_mmap,

   .sendpage     = inet_sendpage,

   .splice_read      = tcp_splice_read,

#ifdef CONFIG_COMPAT

   .compat_setsockopt= compat_sock_common_setsockopt,

   .compat_getsockopt= compat_sock_common_getsockopt,

   .compat_ioctl     = inet_compat_ioctl,

#endif

};


我们新建socket的时候指定了typeSOCK_STREAM那么在后面的套接字操作中(connect)会调用对应的inet_stream_ops中对应的函数(inet_stream_connect)



2inetswinet_protoswproto_opsproto的关系图


  1. Protocol参数


上文中我们通过familytype已经基本确定了新建的socket具体是什么类型的套接字,最后一步通过protocol来确定socket到底支持的哪个协议(TCP?UDP?)


inet_protosw结构体中我们已经解释过proto_ops对应的是每一种套接字类型的操作集合,那么可知structproto对应的就是具体协议的操作集合。在Tcp_ipv4.c中定义TCP协议的struct proto tcp_prot


struct proto tcp_prot ={

   .name          ="TCP",

   .owner         = THIS_MODULE,

   .close         = tcp_close,

   .connect       = tcp_v4_connect,

   .disconnect    = tcp_disconnect,

   .accept        = inet_csk_accept,

   .ioctl         = tcp_ioctl,

   .init          = tcp_v4_init_sock,

   .destroy       = tcp_v4_destroy_sock,

   .shutdown      = tcp_shutdown,

   .setsockopt    = tcp_setsockopt,

   .getsockopt    = tcp_getsockopt,

   .recvmsg       = tcp_recvmsg,

   .sendmsg       = tcp_sendmsg,

   .sendpage      = tcp_sendpage,

   .backlog_rcv       = tcp_v4_do_rcv,

   .release_cb    = tcp_release_cb,

   .hash          = inet_hash,

   .unhash        = inet_unhash,

   .get_port      = inet_csk_get_port,

   .enter_memory_pressure = tcp_enter_memory_pressure,

   .stream_memory_free= tcp_stream_memory_free,

   .sockets_allocated =&tcp_sockets_allocated,

   .orphan_count      =&tcp_orphan_count,

   .memory_allocated  =&tcp_memory_allocated,

   .memory_pressure   =&tcp_memory_pressure,

   .sysctl_mem    = sysctl_tcp_mem,

   .sysctl_wmem       = sysctl_tcp_wmem,

   .sysctl_rmem       = sysctl_tcp_rmem,

   .max_header    = MAX_TCP_HEADER,

   .obj_size      =sizeof(struct tcp_sock),

   .slab_flags    = SLAB_DESTROY_BY_RCU,

   .twsk_prot     =&tcp_timewait_sock_ops,

   .rsk_prot      =&tcp_request_sock_ops,

   .h.hashinfo    =&tcp_hashinfo,

   .no_autobind       = true,

#ifdef CONFIG_COMPAT

   .compat_setsockopt = compat_tcp_setsockopt,

   .compat_getsockopt = compat_tcp_getsockopt,

#endif

#ifdef CONFIG_MEMCG_KMEM

   .init_cgroup       = tcp_init_cgroup,

   .destroy_cgroup    = tcp_destroy_cgroup,

   .proto_cgroup      = tcp_proto_cgroup,

#endif

};


所以对于TCP socket当执行connect连接的时候经过的流程大致是SYSCALL_DEFINE3 connect(系统调用)->inet_stream_connect(inet_stream_ops中定义)-> tcp_v4_connect(tcp_prot中定义)


  1. Family&type&protocol结合


上面分别说明了familytypeprotocol这三个参数代表的意义,接下来我们把这三个参数结合起来一起看一下最后的效果。


structinet_protosw结构体在内核中是如何初始化的呢?


/* Upon startup we insert all the elements in inetsw_array[] into

 * the linked list inetsw.

 */

staticstruct inet_protosw inetsw_array[]=

{

   {

       .type=      SOCK_STREAM,

       .protocol=  IPPROTO_TCP,

       .prot=      &tcp_prot,

       .ops=       &inet_stream_ops,

       .flags=     INET_PROTOSW_PERMANENT |

                 INET_PROTOSW_ICSK,

   },

 

   {

       .type=      SOCK_DGRAM,

       .protocol=  IPPROTO_UDP,

       .prot=      &udp_prot,

       .ops=       &inet_dgram_ops,

       .flags=     INET_PROTOSW_PERMANENT,

      },

 

      {

       .type=      SOCK_DGRAM,

       .protocol=  IPPROTO_ICMP,

       .prot=      &ping_prot,

       .ops=       &inet_dgram_ops,

       .flags=     INET_PROTOSW_REUSE,

      },

 

      {

          .type=      SOCK_RAW,

          .protocol=  IPPROTO_IP,   /* wild card */

          .prot=      &raw_prot,

          .ops=       &inet_sockraw_ops,

          .flags=     INET_PROTOSW_REUSE,

      }

};


Af_inet.c中定义了PF_INET协议族的四个初始化的inet_protosw结构体,在内核协议栈初始化的时候通过inet_register_protosw函数将这些结构体按照类型(type)hash到全局的inetsw数组中。


 


3socket调用流程


上面是socket系统调用的一个主要的流程图,左半部分在前文中已经提到过了。当流程走到inet_create函数的时候根据typeinetsw数组中找到对应类型套接字的inet_protosw结构体,我们前面提到协议栈中已经定义了PF_INET协议族支持的inet_protosw结构体,总共有4个。


找到inet_protosw结构体以后还需要进一步判断protocolinet_protosw中定义的protocol是否是一致的。内核中定义支持的protocol有一个特殊的值IPPROTO_IP(IPPROTO_IP0),可以理解为一个通配符也可以理解为一个默认值,就是说我不指定protocol,由内核自己决定使用哪一个protocol


那么内核根据什么来选择protocol?就是根据内核定义的全局inetsw中对应类型的inet_protosw中的protocol


说起来可能比较拗口,直接看一下代码就很清楚了。


   list_for_each_entry_rcu(answer,&inetsw[sock->type], list){

 

       err =0;

       /* Check the non-wild match. */

       if(protocol== answer->protocol){

           if(protocol!= IPPROTO_IP)

               break;

       }else{

           /* Check for the two wild cases. */

           if(IPPROTO_IP== protocol){

               protocol = answer->protocol;

               break;

           }

           if(IPPROTO_IP== answer->protocol)

               break;

       }

       err =-EPROTONOSUPPORT;

   }


所以如果我们在新建套接字的时候使用socket(PF_INET,SOCK_STREAM,0),那么内核就会默认给你把protocol修正为IPPROTO_TCP


好吧,其实整篇文章我就是想搞清楚这最后的一句话。



1 0
原创粉丝点击