linux内核网络监听哈希表介绍:如何将sk加入表和将sk移除表的过程

2023-11-02 16:48

本文主要是介绍linux内核网络监听哈希表介绍:如何将sk加入表和将sk移除表的过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


http://blog.csdn.net/jccz_zys/article/details/1509832 

以下基于linux内核2.4.0源码(转载请注明出处)

松哥 jccz_zys@tom.com

 

    网络通信过程中,服务器必然提供监听socket响应客户端连接请求,也必然提供连接socket与客户端进行交互。一台主机上有不止一个的socket服务器,如ftptelnet服务器等,他们初始都处于监听状态,等待连接请求的到来。linux中为了管理这两类socket提供了两个哈希链表:tcp_listening_hashtcp_ehash,下面主要分析下监听哈希表,顺带说下连接hash表。

一、链表定义

    include/net/tcp.h中定义了tcp_hashinfo结构,包含了tcp协议所涉及到的一些哈希表信息,这两个哈希表以tcp_hashinfo的成员形式出现,如下所示:

   

    extern struct tcp_hashinfo {

       /* 结构成员用于tcp状态迁移图中的相关状态:

        *          TCP_ESTABLISHED <= sk->state < TCP_CLOSE

        * 前半部份用于非超时状态,后半部份仅用于超时状态

        */

       struct tcp_ehash_bucket *__tcp_ehash;

 

       /* tcp的绑定哈希表,用于快速bind/connect*/

       struct tcp_bind_hashbucket *__tcp_bhash;

 

       int __tcp_bhash_size;

       int __tcp_ehash_size;

 

       /* 所有在监听状态的socket都存放在下面的哈希表中,其中键key为本地监听端口*/

       struct sock *__tcp_listening_hash[TCP_LHTABLE_SIZE];

 

       /*下面的成员缓冲区对齐*/

       rwlock_t __tcp_lhash_lock /*监听哈希表访问锁*/

              __attribute__((__aligned__(SMP_CACHE_BYTES)));

       atomic_t __tcp_lhash_users;

       wait_queue_head_t __tcp_lhash_wait;

       /*上述三个成员主要用于用户通过/proc/ne获取监听套接字(间接访问监听哈希表时)信息时异步处理*/

       spinlock_t __tcp_portalloc_lock;

    } tcp_hashinfo;

    接下来定义了一些宏以简化引用:

    #define tcp_ehash   (tcp_hashinfo.__tcp_ehash) /*连接哈希链表*/

    #define tcp_bhash   (tcp_hashinfo.__tcp_bhash) /*地址bind哈希链表*/

    #define tcp_ehash_size   (tcp_hashinfo.__tcp_ehash_size) /*连接哈希链表长度*/

    #define tcp_bhash_size   (tcp_hashinfo.__tcp_bhash_size) /*bind哈希链表长度*/

    #define tcp_listening_hash (tcp_hashinfo.__tcp_listening_hash) /*监听哈希链表*/

    #define tcp_lhash_lock   (tcp_hashinfo.__tcp_lhash_lock)  /*监听哈希链表访问锁*/

    #define tcp_lhash_users  (tcp_hashinfo.__tcp_lhash_users) /*引用计数*/

    #define tcp_lhash_wait   (tcp_hashinfo.__tcp_lhash_wait) /*异步访问时的等待队列*/

    #define tcp_portalloc_lock (tcp_hashinfo.__tcp_portalloc_lock) /*SMP用途??*/

   

二、链表的初始化

    tcp_listening_hashtcp_ehash的初始化在net/ipv4/tcp_ipv4.c,其中定义了全局变量tcp_hashinfo

    并赋初值,如下:

   

    /*

    * 所有的成员都要初始化,以防止gcc-2.7.2.3编译错误

    */

    struct tcp_hashinfo __cacheline_aligned tcp_hashinfo = {

       __tcp_ehash:          NULL,

       __tcp_bhash:          NULL,

       __tcp_bhash_size:     0, /*初始大小为0*/

       __tcp_ehash_size:     0,

       __tcp_listening_hash: { NULL, },

       __tcp_lhash_lock:     RW_LOCK_UNLOCKED, /*读写锁*/

       __tcp_lhash_users:    ATOMIC_INIT(0),

       __tcp_lhash_wait:

         __WAIT_QUEUE_HEAD_INITIALIZER(tcp_hashinfo.__tcp_lhash_wait), //初始化等待队列

       __tcp_portalloc_lock: SPIN_LOCK_UNLOCKED

    };  

三、链表的元素增加

    服务器监听的函数调用过程如下:

    sys_listen-->inet_listen-->tcp_listen_start-->tcp_v4_hash-->__tcp_v4_hash 

    其中,在tcp_listen_start中,将监听socketaccept_queue队列以及内核sock结构的

    tcp_opt成员tp_pinfo.af_tcp所指向的listen_opt初始化。

    listen_optstruct tcp_listen_opt类型,定义在include/net/tcp.h中:

   

    struct tcp_listen_opt

    {

       u8                  max_qlen_log;       /* SYN包队列的最大长度 */

       int                 qlen;              /*当前实际长度*/

       int                 qlen_young;

       int                 clock_hand;    /**/

       /*syn_table用于tcp三次握手协议时保留SYN包请求,SYN Cookie配合可用于防止SYN flood攻击*/

       struct open_request *syn_table[TCP_SYNQ_HSIZE];

   };

   下面来看看__tcp_v4_hash函数,net/ipv4/tcp_ipv4.c

  

   static __inline__ void __tcp_v4_hash(struct sock *sk)

   {

       struct sock **skp;/*指向哈希表表项地址,其中每个哈希表项为链表.即指向链首指针的地址*/

       rwlock_t *lock;

 

       BUG_TRAP(sk->pprev==NULL);

       if(sk->state == TCP_LISTEN) {/*注意在tcp_listen_start函数中已经将sock状态置为TCP_LISTEN*/

              skp = &tcp_listening_hash[tcp_sk_listen_hashfn(sk)];/*注意:tcp_sk_listen_hashfn封装了tcp_lhashfn*/

              lock = &tcp_lhash_lock;

              tcp_listen_wlock();

       } else {/*否则加入到连接哈希表,如果代码执行到这里,一般此时sockTCP_ESTABLISHED*/

              skp = &tcp_ehash[(sk->hashent = tcp_sk_hashfn(sk))].chain;

              lock = &tcp_ehash[sk->hashent].lock;

              write_lock(lock);

       }

       /*sk->next:每个socketnext都指向后一个有相同hash(冲突)sock结构

       *每个socketpprev都指向前一个有相同hash值的sock结构*/

       if((sk->next = *skp) != NULL) /*注意此时skp是获得的链表头的地址,所以此处是"*skp"来引用*/

              (*skp)->pprev = &sk->next; /*双向链表,pprev指向前一个sock结构(即刚加入的sock结构的next地址)*/

       *skp = sk;/*sk加入到链首*/

       sk->pprev = skp;

       sock_prot_inc_use(sk->prot);/*修改prot->stats[].inuse计数*/

       write_unlock(lock);

       if (sk->state == TCP_LISTEN)

              wake_up(&tcp_lhash_wait); /*唤醒等待队列*/

    }

四、哈希链表元素的删除   

   监听链表的元素的释放是在监听套接子关闭时处理。函数调用链如下:

   close-->(参考《情景阅读》)...-->inet_release-->tcp_close-->tcp_set_state-->tcp_unhash

   tcp_unhash代码在net/ipv4/tcp_ipv4.c,如下:

  

   void tcp_unhash(struct sock *sk)

   {

       rwlock_t *lock;

 

       if (sk->state == TCP_LISTEN) { /*监听套接字*/

              local_bh_disable(); //

              tcp_listen_wlock(); /*循环调度直至外部改变tcp_lhash_users0是才往下执行*/

              lock = &tcp_lhash_lock;

       } else {  /*其他状况时脱离tcp连接哈希桶*/

              struct tcp_ehash_bucket *head = &tcp_ehash[sk->hashent];

              lock = &head->lock;

              write_lock_bh(&head->lock);//加锁处理

       }

 

       if(sk->pprev) {

              if(sk->next)

                     sk->next->pprev = sk->pprev; /*sk脱链*/

              *sk->pprev = sk->next;/*sk->pprev指向前一节点的next地址,所以此处是将前一节点的next指针指向sk节点的后一节点*/

              sk->pprev = NULL;

              sock_prot_dec_use(sk->prot);

       }

       ...

    }

五、哈希函数

    代码在include/net/Tcp.h,如下:

    static __inline__ int tcp_lhashfn(unsigned short num)

    {

       return num & (TCP_LHTABLE_SIZE - 1);

    }

   

    可见此哈希函数仅根据请求的端口来做哈希,监听哈希表的长度是TCP_LHTABLE_SIZE,32,定义如下:

    #define TCP_LHTABLE_SIZE     32    /* Yes, really, this is all you need. */

    注意:tcp_sk_listen_hashfn封装了tcp_lhashfn

   

六、哈希表的作用

    内核中,每建立一个监听套接字,就将套接字挂入监听哈希表的某个表项链表中。则在内核收到连接请求的SYNACK等包传到TCP层时,要根据请求包的请求连接地址与端口号到哈希表中查找对应的服务器监听套接字是否存在,代码在net/ipv4/tcp_ipv4.c,如下:

    int tcp_v4_rcv(struct sk_buff *skb, unsigned short len)

    {

    ...

    /*__tcp_v4_lookup函数就是在监听哈希表和连接哈希桶中查找请求的服务器套接字*/

    sk = __tcp_v4_lookup(skb->nh.iph->saddr, th->source,

                      skb->nh.iph->daddr, ntohs(th->dest), tcp_v4_iif(skb));

    ...

    }

    __tcp_v4_lookup函数在同一文件下,代码如下:

    static inline struct sock *__tcp_v4_lookup(u32 saddr, u16 sport,

                                      u32 daddr, u16 hnum, int dif)

    {

       struct sock *sk;

       /*先在连接哈希桶中找*/

       sk = __tcp_v4_lookup_established(saddr, sport, daddr, hnum, dif);

 

       if (sk)  /*找到则返回*/

              return sk;

       /*否则在监听哈希表中找*/        

       return tcp_v4_lookup_listener(daddr, hnum, dif);

    }

    我们来看tcp_v4_lookup_listener函数,其中入参有目标地址、目标端口

    __inline__ struct sock *tcp_v4_lookup_listener(u32 daddr, unsigned short hnum, int dif)

    {

       struct sock *sk;

 

       read_lock(&tcp_lhash_lock);

       sk = tcp_listening_hash[tcp_lhashfn(hnum)];/*根据目标端口找到哈希表项,为链表首节点指针*/

       if (sk) { /*如果链首不空*/

              if (sk->num == hnum && /*如果端口相等*/

                  sk->next == NULL && /*如果仅此节点*/

                  (!sk->rcv_saddr || sk->rcv_saddr == daddr) && /*套接字绑定了IPv4地址,且此地址为请求的连接地址*/

                  !sk->bound_dev_if) /*如果存在绑定的设备接口*/

                     goto sherry_cache; //则直接返回

              sk = __tcp_v4_lookup_listener(sk, daddr, hnum, dif); /*否则遍历链表,按照地址、端口、设备接口索引查找*/

       }

       if (sk) {/*找到则直接返回,否则sk=NULL*/

    sherry_cache:

              sock_hold(sk);

       }

       read_unlock(&tcp_lhash_lock); //解锁

       return sk;

    }

   

    关于连接的哈希桶的功能也大致如此,主要用于保存建立连接的套接字,用于在接受到网络包后,找到对应

    的处理套接字。

   

七、结束语

        内核中的很多地方都采用了这种简单的哈希表,其中冲突的解决方法就是表项采用链表方式。在tcp的实现中,还引入了其它哈希表,bind哈希表tcp_bhash等等。通过这些哈希表,内核可以简洁、方便、快速地查询这些哈希表中的目标套接字是否存在


这篇关于linux内核网络监听哈希表介绍:如何将sk加入表和将sk移除表的过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/331936

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

哈希leetcode-1

目录 1前言 2.例题  2.1两数之和 2.2判断是否互为字符重排 2.3存在重复元素1 2.4存在重复元素2 2.5字母异位词分组 1前言 哈希表主要是适合于快速查找某个元素(O(1)) 当我们要频繁的查找某个元素,第一哈希表O(1),第二,二分O(log n) 一般可以分为语言自带的容器哈希和用数组模拟的简易哈希。 最简单的比如数组模拟字符存储,只要开26个c

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal