原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://weiguozhihui.blog.51cto.com/3060615/1585297

在内核中为什么要有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表示)。

   

         

 

 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字段的取值为:

 

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

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

 

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

   kernel_sendmsg()的用法:

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

  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()的实现:

  kernel_recvmsg()的用法:

 

 

  8.关闭一个套接字:

    void sock_release(struct socket *sock);

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

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

 

  

    以上这些函数位于linux源代码包中的/net/socket.c之中。

本文出自 “阿辉仔” 博客,请务必保留此出处http://weiguozhihui.blog.51cto.com/3060615/1585297

最新文章

  1. win7里边使用telnet命令为什么提示telnet不是内部或外部命令,也不是可运行的程序或批处理文件
  2. asp.net MVC 回顾 Html.ActionLink
  3. UILabel笔记(待完善)
  4. python——threading模块
  5. Linux下查看某个软件安装路径
  6. Magento开发常用方法
  7. Unity3D UGUI学习系列索引(暂未完成)
  8. 关于VS 中 HttpHandler 的设置 500.23
  9. No suitable driver found for jdbc:mysql://localhost/dbname
  10. POI按照源单元格设置目标单元格格式
  11. iOS动画——文字晃动
  12. An endpoint configuration section for contract "serviceReferenc.service" could not be loaded
  13. 【单调栈】Vijos P1926 紫色的手链
  14. Java面向对象编辑
  15. 完整的拆分nginx访问日志
  16. Linux chgrp
  17. Java 学习笔记 Junit4单元测试使用
  18. 垃圾回收(GC Garbage collection)
  19. 【LOJ】#2536. 「CQOI2018」解锁屏幕
  20. 专访TK教主于旸:原来那些搞安全的说的都是真的(图灵访谈)

热门文章

  1. JavaScript中解析JSON --- json.js 、 json2.js 以及 json3.js的使用区别
  2. Filebeat自定义索引 && 多output过滤
  3. 2019-9-25:渗透测试,基础学习,Hydra BP爆破,js基本知识,banner信息收集笔记
  4. css优先级之important
  5. Tensorflow常用函数说明
  6. MySQL 高可用架构 之 MHA (Centos 7.5 MySQL 5.7.18 MHA 0.58)
  7. SpringCloud Alibaba微服务实战三 - 服务调用
  8. Dropzone.js拖拽上传(简单示例)
  9. c 程序之 最大公约数 和 最小公倍数
  10. linux下svn服务器端的操作