2020-08-07

本文深入探讨了Linux内核中的socket结构体作用及其创建过程,解释了如何通过struct socket访问内核网络系统,包括传输层、网络层和数据链路层。详细介绍了struct socket的类型、标志位以及创建函数sock_create和sock_create_kern的使用,同时讲解了与之相关的套接字地址结构体struct sockaddr和struct sockaddr_in,以及核心网络操作如bind、listen、accept、connect、sendmsg和recvmsg的内核实现。

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

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

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

   下面这个图,我觉得可以回答以上两个问题。  wKioL1R8EgfAF5ZDAAGd1S8RhIA115.jpg

 

    由这个图可知,内核中的进程可以通过使用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表示)。

   wKiom1R8Hx3THEffAAVlvwxDKzk034.jpg

 wKioL1R8H7vSc54pAAN3Efqa5N0077.jpg        

wKiom1R8H9Lz9dl4AAMbicmeYGI140.jpg

wKioL1R8IG2DYTRwAAKqw4_sJrE248.jpg

 

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

wKiom1R9K3zSLJWrAASNJz0QxPc672.jpg

 

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

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

wKioL1R8mqCDxdedAALtXFBqeDk456.jpg

 

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

   kernel_sendmsg()的用法:

wKiom1R9KNyQKTtUAAT07fVKu2A037.jpg

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

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

wKiom1R9KnbxNq6eAAKakSYH0Js684.jpg

  kernel_recvmsg()的用法:

wKioL1R9JlixQrpdAAJD7SWJH2g182.jpg

 

 

  8.关闭一个套接字:

    void sock_release(struct socket *sock);

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

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

 

<think> 我们有一个需求:查询余额高于10000的所有用户的名字余额。除了使用公共表达式(CTE)之外,我们还可以使用多种方法。 根据引用[1]中提到的银行用户表结构(假设表名为“银行用户表”,有“姓名”“余额”字段),我们可以编写SQL语句。 常见的其他方法包括: 1. 直接使用SELECT语句的WHERE子句进行过滤。 2. 使用子查询(虽然公共表达式也是子查询的一种,但这里指其他形式的子查询,比如在FROM子句中的子查询)。 3. 使用视图(但视图并不是一次性的查询,这里我们主要讨论一次性的查询语句)。 4. 使用临时表(但同样,我们主要讨论查询语句,临时表需要创建删除,不太适合作为单一查询)。 因此,最直接的方法就是使用简单的SELECT语句。 根据引用[2]引用[3],我们可以写出基本的SELECT语句,注意引用[3]中使用了别名,引用[5]中使用了ORDER BY排序(虽然这里不需要排序,但我们可以选择是否排序)。 因此,我们可以这样写: 方法1:直接查询 ```sql SELECT 姓名, 余额 FROM 银行用户表 WHERE 余额 > 10000; ``` 但是,我们也可以使用子查询(虽然在这个简单需求中子查询没有必要,但为了展示多种写法): 方法2:使用FROM子句中的子查询 ```sql SELECT 姓名, 余额 FROM ( SELECT 姓名, 余额 FROM 银行用户表 ) AS 用户余额表 WHERE 余额 > 10000; ``` 方法3:我们还可以使用内联视图(实际上就是方法2)。 另外,如果我们需要对结果进行排序(例如按余额降序排列),可以加上ORDER BY子句(引用[5]): ```sql SELECT 姓名, 余额 FROM 银行用户表 WHERE 余额 > 10000 ORDER BY 余额 DESC; ``` 注意:引用[4]提到了索引的问题。如果我们在“余额”列上建立索引,那么查询效率会更高。否则,数据库可能进行全表扫描。 因此,除了公共表达式(CTE)之外,我们可以使用上述的直接查询或子查询的方式。其中,直接查询是最简单高效的。 下面给出一个完整的例子(假设表名为`银行用户表`): ```sql -- 方法1:简单查询 SELECT 姓名, 余额 FROM 银行用户表 WHERE 余额 > 10000; -- 方法2:使用子查询(虽然在这里没有优势) SELECT 姓名, 余额 FROM ( SELECT 姓名, 余额 FROM 银行用户表 ) AS 子查询表 WHERE 余额 > 10000; -- 如果需要排序(降序) SELECT 姓名, 余额 FROM 银行用户表 WHERE 余额 > 10000 ORDER BY 余额 DESC; ``` 总结:对于这个需求,最简单的就是直接使用WHERE子句过滤。公共表达式(CTE)通常用于更复杂的查询,比如需要递归或多次引用同一个子查询结果集时。在简单查询中,直接查询即可。 注意:如果表名或字段名是英文的,那么应该使用英文,比如表名可能是`bank_account`,字段名为`name``balance`。但根据引用[1]中的表名字段名,我们使用了中文。如果实际表结构是英文的,则需要修改。 另外,引用[2]提到,关键字字段名不区分大小写,但数据严格区分大小写。不过在我们这个查询中,余额是数值,不存在大小写问题。 因此,根据用户需求,我们给出了不使用公共表达式的其他写法。 相关问题: 1. 如果余额字段包含小数,那么查询条件中是否需要考虑浮点数精度问题? 2. 当数据量很大时,如何优化这个查询? 3. 在银行系统中,查询账户余额时需要注意哪些事务隔离级别? </think>根据您的需求,我提供了三种不使用公共表达式(CTE)的SQL写法来查询余额高于10000的用户信息: ### 方法1:基础WHERE过滤(最简洁) ```sql SELECT 姓名, 余额 FROM 银行用户表 WHERE 余额 > 10000; ``` **说明**: 直接使用`WHERE`子句过滤条件,这是最高效的写法。当`余额`列有索引时,数据库会使用索引扫描(Index Scan),否则执行全表扫描(Table Scan)[^4]。 --- ### 方法2:派生表子查询(适用于复杂过滤) ```sql SELECT 姓名, 余额 FROM ( SELECT 姓名, 余额 FROM 银行用户表 ) AS 用户余额 WHERE 余额 > 10000; ``` **说明**: 通过FROM子句中的子查询创建派生表,适合需要预先处理数据的场景。注意使用`AS`为派生表命名是必须的[^2]。 --- ### 方法3:视图查询(可复用方案) ```sql -- 第一步:创建视图 CREATE VIEW 高余额用户视图 AS SELECT 姓名, 余额 FROM 银行用户表 WHERE 余额 > 10000; -- 第二步:查询视图 SELECT * FROM 高余额用户视图; ``` **说明**: 视图将查询逻辑持久化,适合需要多次使用的场景。查询视图时等同于执行视图定义的SQL语句[^2]。 --- ### 性能说明 1. **索引优化**: 在`余额`列创建索引可大幅提升查询性能: ```sql CREATE INDEX idx_余额 ON 银行用户表(余额); ``` 有索引时执行**索引范围扫描**,无索引时执行**全表扫描**[^4]。 2. **结果排序**: 如需按余额降序排列,添加: ```sql ORDER BY 余额 DESC; -- DESC表示降序[^5] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值