在内核中为什么要有struct socket结构体呢?

   struct socket结构体的作用是什么?

   下面这个图,我觉得可以回答以上两个问题。  struct socket结构体详解_网络层

 

    由这个图可知,内核中的进程可以通过使用struct socket结构体来访问linux内核中的网络系统中的传输层、网络层、数据链路层。也可以说struct socket是内核中的进程与内核中的网路系统的桥梁。

 




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39


40


41


42


43


44


45




​struct​​ ​​socket​


​{​


​socket_state  state; ​​​​// socket state​


 


​short​​   ​​type ; ​​​​// socket type​


 


​unsigned ​​​​long​​  ​​flags; ​​​​// socket flags​


 


​struct​​ ​​fasync_struct  *fasync_list;​


 


​wait_queue_head_t wait;​


 


​struct​​ ​​file *file;​


 


​struct​​ ​​sock *sock;  ​​​​// socket在网络层的表示;​


 


​const​​ ​​struct​​ ​​proto_ops *ops;​


 


​}​


 


 


​struct​​ ​​socket结构体的类型​


​enum​​ ​​sock_type​


​{​


​SOCK_STREAM = 1, ​​​​// 用于与TCP层中的tcp协议数据的struct socket​


 


​SOCK_DGRAM  = 2, ​​​​//用于与TCP层中的udp协议数据的struct socket​


 


​SOCK_RAW    = 3, ​​​​// raw struct socket​


 


​SOCK_RDM    = 4, ​​​​//可靠传输消息的struct socket​


 


​SOCK_SEQPACKET = 5,​​​​// sequential packet socket​


 


​SOCK_DCCP   = 6,​


 


​SOCK_PACKET = 10, ​​​​//从dev level中获取数据包的socket​


​};​


 


​struct​​ ​​socket 中的flags字段取值:​


​#define SOCK_ASYNC_NOSPACE  0​


​#define SOCK_ASYNC_WAITDATA 1​


​#define SOCK_NOSPACE        2​


​#define SOCK_PASSCRED       3​


​#define SOCK_PASSSEC        4​



   我们知道在TCP层中使用两个协议:tcp协议和udp协议。而在将TCP层中的数据往下传输时,要使用网络层的协议,而网络层的协议很多,不同的网络使用不同的网络层协议。我们常用的因特网中,网络层使用的是IPV4和IPV6协议。

   所以在内核中的进程在使用struct socket提取内核网络系统中的数据时,不光要指明struct socket的类型(用于说明是提取TCP层中tcp协议负载的数据,还是udp层负载的数据),还要指明网络层的协议类型(网络层的协议用于负载TCP层中的数据)。

   linux内核中的网络系统中的网络层的协议,在linux中被称为address family(地址簇,通常以AF_XXX表示)或protocol family(协议簇,通常以PF_XXX表示)。

   struct socket结构体详解_数据_02

 struct socket结构体详解_应用层_03        

struct socket结构体详解_套接字_04

struct socket结构体详解_应用层_05

 

 1.创建一个struct socket结构体:

   int sock_create(int family, int type, int protocol, 

                    struct socket **res);

   int sock_create_kern(int family, int type, int protocol,

                         struct socket **res);

   EXPROT_SYMBOL(sock_create);

   EXPROT_SYMBOL(sock_create_kern);

   family : 指定协议簇的类型,其值为:PF_XXX或 AF_XXX

   type   : 指定要创建的struct socket结构体的类型;

   protocol : 一般为0;

   res    : 中存放创建的struct socket结构体的地址;




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39


40


41


42


43


44


45


46


47


48


49


50


51


52


53


54


55


56


57


58


59


60


61


62




​int​​ ​​sock_create(​​​​int​​ ​​family, ​​​​int​​ ​​type, ​​​​int​​ ​​protocol, ​​​​struct​​ ​​socket **res)​


​{​


​return​​ ​​__sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);​


​}​


 


​int​​ ​​sock_create_kern(​​​​int​​ ​​family, ​​​​int​​ ​​type, ​​​​int​​ ​​protocol, ​​​​struct​​ ​​socket **res)​


​{​


​return​​ ​​__sock_create( &init_net, family, type, protocot, res, 1 );​


​}​


​如果在内核中创建​​​​struct​​ ​​socket时,推荐使用sock_create_kern()函数;​


 


 


​// 网络协议簇结构体​


​struct​​ ​​net_proto_family​


​{​


​int​​ ​​family ; ​​​​// 协议簇​


​int​​ ​​(*create)(​​​​struct​​ ​​net *net, ​​​​struct​​ ​​socket *sock,  ​​​​int​​ ​​protocol);​


​struct​​ ​​module  *owner;​


​};​


 


​内核中的所有的网络协议的响应的网络协议簇结构体都存放在 net_families[]指针数组中;​


​static​​ ​​struct​​ ​​net_proto_family *net_families[NPROTO];​


 


​static​​ ​​int​​ ​​__sock_create(​​​​struct​​ ​​net *net, ​​​​int​​ ​​family, ​​​​int​​ ​​type, ​​​​int​​ ​​protocol, ​


​struct​​ ​​socket **res, ​​​​int​​ ​​kern )​


​{​


​struct​​ ​​socket *sock;​


​struct​​ ​​net_proto_family *pf;​


 


​sock = sock_alloc();​​​​//分配一个struct socket 结构体​


​sock->type = type;​


 


​pf = rcu_dereference(net_families[family]); ​​​​//获取相应的网络协议簇结构体的地址;​


​pf->create(net, sock, protocol); ​​​​// 对struct socket结构体做相应的处理;​


 


​*res = sock; ​​​​// res中保存创建的struct socket结构体的地址;​


​return​​ ​​0;​


​}​


 


 


​struct​​ ​​socket_alloc​


​{​


​struct​​ ​​socket socket ;​


​struct​​ ​​inode vfs_node ;​


​}​


 


​static​​ ​​inline​​ ​​struct​​ ​​socket *SOCKET_I(​​​​struct​​ ​​inode *inode)​


​{​


​return​​ ​​&contain_of(inode, ​​​​struct​​ ​​socket_alloc, vfs->node)->socket;​


​}​


​static​​ ​​struct​​ ​​socket *sock_alloc(​​​​void​​​​)​


​{​


​struct​​ ​​inode *inode;​


​struct​​ ​​socket *sock;​


​inode = new_inode(sock_mnt->mnt_sb);​​​​//分配一个新的struct inode节点​


​sock = SOCKET_I(inode); ​


​inode->i_mode = S_IFSOCK | S_IRWXUGO;​​​​//设置inode节点的权限​


​inode->i_uid = current_fsuid(); ​​​​// 设置节点的UID​


​inode->i_gid = current_fsgid(); ​​​​//设置节点的GID​


 


​return​​ ​​sock; ​


​}​



  有以上的代码可知:linux内核在使用sock_create()、sock_create_kern()

进行struct socket结构体的创建时,其本质是分配了一个struct socket_alloc

结构体,而这个struct socket_alloc结构体中包含了struct socket 和struct

inode(struct inode结构体,是linux内核用来刻画一个存放在内存中的文件的,通过将struct inode 和 struct socket绑定在一起形成struct socket_alloc结构体,来表示内核中的网络文件)。然后对分配的struct socket结构体进行初始化,来定义内核中的网络文件的类型(family, type, protocol).

 

   在linux网络系统中还有两个非常重要的套接字地址结构体:

          struct sockaddr_in

          struct sockaddr;




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27




​typedef​​ ​​unsigned ​​​​short​​ ​​sa_family_t;​


 


​// Internet Address​


​struct​​ ​​in_addr{​


​__b32 s_addr;​


​}​


 


​//struct describing an Internet socket address​


​//sockaddr_in 中存放端口号、网路层中的协议类型(ipv4,ipv6)等,网络层的IP地址;​


​struct​​ ​​sockaddr_in​


​{​


​sa_family_t sin_family ; ​​​​// Address family AF_XXX​


 


​__be16      sin_port   ; ​​​​// 端口号​


 


​struct​​ ​​in_addr sin_addr ; ​​​​// Internet Address ​


 


​/*Pad to size of  'struct sockaddr'*/​​ 


​...........  ​


​};​


 


​//套接字地址结构体。​


​struct​​ ​​sockaddr​


​{​


​sa_family_t sa_family; ​​​​// 存放网络层所使用的协议类型(AF_XXX 或 PF_XXX);​


​char​​ ​​sa_data[14];   ​​​​// 里面存放端口号、网络层地址等信息;​


​}​



 

   从本质上来说,struct sockaddr与struct sockaddr_in是相同的。

   但在,实际的使用过程中,struct sockaddr_in是 Internet环境下的套接字地址形式,而struct sockaddr是通过的套接字地址个形式。在linux内核中struct sockaddr使用的更多,目的是使linux内核代码更为通用。

   struct sockaddr_in 可以与 struct sockaddr 进行自由的转换。

  

   2.将创建的套接字(struct socket)与套接字地址结构体(struct sockaddr or struct sockaddr_in)进行绑定:

     int kernel_bind(struct socket *sock, struct sockaddr *addr,

                     int addrlen)

     EXPROT_SYMBOL(kernel_bind);

     sock : 为通过sock_create()或sock_create_kern()创建的套接字;

     addr : 为套接字地址结构体;

     addrlen:为套接字地址结构体的大小;

 

   3.将一个套接字(struct socket)设置为监听状态:

     int kernel_listen(struct socket *sock, int backlog);

     backlog :一般情况下设置为0;

     EXPORT_SYMBOL(kernel_listen);

 

   4.当把一个套接字设置为监听状态以后,使用这个套接字去监听其它的套接字;

   int kernel_accept(struct socket *sock, struct socket **new_sock,

                      int flags);

   EXPORT_SYMBOL(kernel_accept);

   sock : listening socket 处于监听状态的套接字;

   new_sock : 被监听的套接字;

   flags: struct socket中的flags字段的取值;

 

   5.把一个套接字连接到另一个套接字地址结构体上:

   int kernel_connect(struc socket *sock, struct sockaddr *addr,

                       int addrlen, int flags);

   EXPORT_SYMBOL(kernel_connect);

   sock : struct socket;

   addr : 为另一个新的套接字地址结构体;

   addrlen : 套接字地址结构体的大小;

   flags :file-related flags associated with socket

 

   6.把一个应用层中的数据发送给另一个设备中的进程:

     int kernel_sendmsg(struct socket *sock, struct msghdr *msg,

                         struct kvec *vec, size_t num, size_t size)

     EXPORT_SYMBOL(kernel_sendmsg);

     sock : 为当前进程中的struct socket套接字;

     msg  : 用于接收来自应用层的数据包;

     kvec : 中存放将要发送出去的数据;

     num  : 见代码;

     size : 为将要发送的数据的长度;

 




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27




​struct​​ ​​iovec​


​{​


​void​​ ​​__user *iov_base;​


​__kernel_size_t iov_len;​


​}​


 


​struct​​ ​​msghdr​


​{​


​//用于存放目的进程所使用的套接字地址​


​void​​ ​​*msg_name;  ​​​​// 用于存放目的进程的struct sockaddr_in ​


​int​​   ​​msg_namelen; ​​​​// 目的进程的sizeof(struct sockaddr_in)​


 


 


​//用于来自应用层的数据​


​struct​​ ​​iovec *msg_iov ;​​​​// 指向一个struct iovec的数组,数组中的每个成员表示一个数据块​


​__kernel_size_t  msg_iovlen ; ​​​​//数据块数,即struct iovec数组的大小​


 


 


​//用于存放一些控制信息​


​void​​ ​​*msg_control ;​


​__kernel_size_t msg_controllen; ​​​​//控制信息的长度;​


 


 


​//​


​int​​ ​​msg_flags;     ​


 


​}​



 




1


2


3


4


5




​struct​​ ​​kvec​


​{​


​void​​ ​​*iov_base; ​​​​//用于存放来自应用层的数据;​


​size_t​​ ​​iov_len; ​​​​//来自应用层的数据的长度;​


​}​



 

 struct msghdr中的flags字段的取值为:

struct socket结构体详解_网络层_06

 

int kernel_sendmsg(struct socket *sock, struct msghdr *msg,

           struct kvec *vec, size_t num, size_t size)函数的实现为:

struct socket结构体详解_数据_07

 

   有kernel_sendmsg()的实现代码可知,struct kvec中的数据部分最终还是要放到struct msghdr之中去的。

   kernel_sendmsg()的用法:

struct socket结构体详解_网络层_08

   也可以使用下面这个函数来实现相同的功能:

  int sock_sendmsg(struct socket *sock, struct msghdr *msg,

                    size_t size);

  EXPORT_SYMBOL(sock_sendmsg);

  

  

  7.接受来自另一个网络进程中的数据:

    int kernel_recvmsg(struct socket *sock, struct msghdr *msg,

              struct kvec *vec, size_t num, size_t size, int flags)

    EXPORT_SYMBOL(kernel_recvmsg);

    sock : 为接受进程的套接字;

    msg  : 用于存放接受到的数据;

    vec  : 用于指向本地进程中的缓存区;

    num  : 为数据块的块数;

    size : 缓存区的大小;

    flags: struct msghdr中的flags字段中的取值范围;

  int kernel_recvmsg()的实现:

struct socket结构体详解_套接字_09

  kernel_recvmsg()的用法:

struct socket结构体详解_应用层_10

 

 

  8.关闭一个套接字:

    void sock_release(struct socket *sock);

      用于关闭一个套接字,并且如果一个它struct socket绑定到了一个struct

inode节点上的话,相应的struct inode也会被释放。

 

 

------------------越是喧嚣的世界,越需要宁静的思考------------------ 合抱之木,生于毫末;九层之台,起于垒土;千里之行,始于足下。 积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。故不积跬步,无以至千里;不积小流,无以成江海。骐骥一跃,不能十步;驽马十驾,功在不舍。锲而舍之,朽木不折;锲而不舍,金石可镂。蚓无爪牙之利,筋骨之强,上食埃土,下饮黄泉,用心一也。蟹六跪而二螯,非蛇鳝之穴无可寄托者,用心躁也。