欢迎光临散文网 会员登陆 & 注册

Linux内核角度分析服务器Listen细节

2022-07-28 15:59 作者:补给站Linux内核  | 我要投稿

Listen功能简述

编写服务器程序时,在Linux中需要调用Listen系统调用,如下所示,Listen系统调用的主要功能就是根据传入的backlog参数创建连接队列,并将套接字的状态迁移至LISTEN状态,最后将监听sock注册到TCP全局的监听套接字哈希表。

Listen系统调用-函数执行流程

系统调用调用的函数执行如下所示:

其中sockfd_lookup_light函数根据fd描述符得到struct socket结构体,并找到当前系统设定的最大可监听连接数somaxconn ,PROC文件系统中somaxconn默认为128,意味着单个套接口队列的长度,可最大监听128个连接 ,如下所示:

somaxconn与Listen系统调用传入的参数backlog进行比较,若当前传入的参数backlog大于somaxconn则使用somaxconn,即backlog最大值不能超过somaxconn。该系统调用核心是执行:sock->ops->listen(sock,backlog) ;也就是说找到服务器的socket后,通过它的协议操作表结构struct proto_ops执行其listen钩子函数,proto_ops协议操作表结构的挂入是在socket创建过程根据协议类型进行设置的,TCP实际挂入的是inet_stream_ops操作表结构,listen在inet_stream_ops表中的赋值如下所示:


【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)       


故sock->ops->listen针对于TCP而言,继续调用inet_listen函数:

inet_listen函数首先是对套接字类型、状态进行检查,类型必须是流式套接字且状态必须是close或者listen状态:

inet_listen函数核心的继续调用inet_csk_start_listen函数:

inet_csk_listen_start函数通过reqsk_queue_alloc创建连接队列,队列结构体如下,队列的最大长度是sk_max_ack_backlog,也就是用户传入的backlog参数值,队列的长度计数是sk_ack_backlog。

其中request_sock结构体是请求队列的节点如下所示,*dl_next将所有的accept请求串起来。

struct request_sock_queue和struct request_sock的关系如下:


inet_csk_listen_start调用的分配并初始化连接队列的函数reqsk_queue_alloc如下所示,其中可以看到queue->rskq_accept_head初始化为NULL

inet_csk_listen_start函数中另一个核心内容就是调用哈希函数:

sk->sk_prot->hash(sk)将监听sock注册到TCP全局的监听套接字哈希表,对于TCP对应的协议栈,hash函数是inet_hash:

继续调用__inet_hash:

关于:

struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;

通过图解,如下所示:


最终得到struct inet_hashinfo:

内核将监听队列分为32个哈希桶bucket:listening_hash[INET_LHTABLE_SIZE],保存处于监听状态的TCP套接口哈希链表,每个哈希桶由独立的保护锁和链表,此结构通过减小锁的粒度,增加并行处理的可能,每个哈希桶如下所示:

哈希桶的选择由函数inet_sk_listen_hashfn的返回值决定

inet_sk_listen_hash函数使用数据包的目的端口号(本地监听端口号)计算的hash值为索引得到具体的哈希桶,如下所示:

内核为处于LISTEN状态的socket分配了大小为32的哈希桶,监听的端口号经过哈希算法运算打散到这些哈希桶中(如果开启了IPV6,并且启用了端口重用,将此套接口添加在监听套接口桶的链表末尾;否则,添加到链表头部,如下代码所示)

如下图所示,哈希链表的组织方式:


当收到客户端的 SYN 握手报文以后,会根据目标端口号的哈希值计算出哈希冲突链表,然后遍历这条哈希链表得到对应的socket。


Linux内核角度分析服务器Listen细节的评论 (共 条)

分享到微博请遵守国家法律