路由表的结构与算法分析--trie查找

Linux路由表的结构与算法分析 Linux路由表的结构与算法分析 Linux路由表的结构与算法分析 Linux路由表的结构与算法分析 立即下载

  linux 中的路由查找算法一点也不比那些大型的专业路由器的查找算法差,所谓的专业路由器就是在很大程度上用硬件实现了一些常用的软件功能,比如思科的 路由器竟然采用过什么256 叉树,这种疯狂的以空间换时间的做法在通用的计算机操作系统---linux 上实现是不现实的,但是确实是可能的。linux 的路由表具有高度的可扩展性,内置了256 张路由表,对于策略路有的实现相当方便,缺省使用哈希表查找算法,那种方法在我提到的另一篇名为《路由表的结构 与算法分析》里面已经解释得很详细了,因此我这里主要说说trie 查找算法。这个查找算法是基于树的,首先熟悉一下数据结构。
  trie
算法中将路由表抽象成一个trie 结构:

struct trie {

         struct node *trie;// 一切的查找从这里开始。

 #ifdef CONFIG_IP_FIB_TRIE_STATS

         struct trie_use_stats stats;

 #endif

 };

结构很紧凑,几乎没有什么没有用的东西,下面看一下node :

 struct node {

         unsigned long parent;

         t_key key;

 };

有人就要问了,这个东西能做什么呢?这不一切断了吗?其实,linux 在内核中大量使用了面向对象的特性,这里给出的node 结构作为“ 基类” ,真正管事情的是tnode (后面说插入的时候我会给出它们是怎么联系起来的):

 struct tnode {

         unsigned long parent;

         t_key key;

         unsigned char pos;              // 这个字段指出本node 要比较的位在32 位ip 中的偏移

         unsigned char bits;             // 这个字段指出表示这个节点的孩子节点的位数,比如如果有2 个孩子,那么需要1 位,0 表示第一个孩子,1 表示第二个孩子;如果有4 个孩子就需要2 位,以此类推。

         unsigned int full_children;     // 这个字段表示孩子中有几个是full 的,所谓的full 就是在插入操作的时候不能把这个孩子作为新插入的孩子的孩子从而扩展了。

         unsigned int empty_children;    // 这个表示空孩子的个数

         union {

                 struct rcu_head rcu;

                 struct work_struct work;

         };

         struct node *child[0];// 为了一个定长的结构

 };

上面的注释都很拗口,我开始读代码的时候也是费尽周折才搞明白的,还作了n 多个实现,写了n 多个测试代码,我会尽量在下面的叙述中把问题理清,但是真正的理解还是得做实验。

 struct leaf {     // 此为一个叶子节点,表示一条路由

         t_key key;// 节点健值

         unsigned long parent;

         struct hlist_head list;

         struct rcu_head rcu;

 }; 

 struct leaf_info {   // 此处存放具体的路由

         struct hlist_node hlist;

         struct rcu_head rcu;

         int plen;

         struct list_head falh;

 };

从这些数据结构可以看出,linux 的数据结构相当精巧,善于组合小对象来形成一个可管理的大系统,这就是内核里面的面向对象的思想,这些小小的数据结构 起到的作用就是管理数据,让数据具有联系,具有层次感,从而组成的大系统也就有了可管理的结构,如果不说这些结构就是路由,那么我完全可以将它们用于文件 系统,另一个例子可参见linux2.6 内核里的kobject 。当然最普遍的例子就是著名的list_head 了。
     
好了,基础设施就是这么多,下面就开始利用这些数据结构来进行优美的查找了。
      linux
的trie 树是动态调整的,它的插入算法解释动态调整的动作,对比一下传统bsd 内核的radix 树查找,也是动态调整的,但是bsd 的 radix 查找算法中的树节点只有最多2 个,是一颗二叉树,最新的bsd 内核实现了trie 查找,正如我前面文章说的,但是它将ip 地址分为等长的四个部 分,然后每个部分分别进行匹配,很明确,就是4 个部分,树的叉数也就确定了,就是2 的8 次方叉树,比较适合大型机器上的并行流水计算,事实证明,很多硬件 路有器的硬件设计所用的算法正是和新版bsd 一样的算法。而linux 的trie 算法完全是动态的,可能在有的时候是二叉树,有的时候是2 的32 次方叉 数,视当时情况而定。下面先列出一个从内核源码中弄出来的图:

   Example:   n 是一个内部节点而tp 是它的父亲。

   _________________________________________________________________

   | i | i | i | i | i | i | i | N | N | N | S | S | S | S | S | C |

    -----------------------------------------------------------------

     0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15

   _________________________________________________________________

   | C | C | C | u | u | u | u | u | u | u | u | u | u | u | u | u |

    -----------------------------------------------------------------

    16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31

   tp->pos = 7

   tp->bits = 3

   n->pos = 15

   n->bits = 4

这 个图非常直观,就是说对于tp ,它的第7 位(从0 开始)往后开始不同,就是说它的孩子们的前7 位都是一样的,查找的时候如果一个键值的前7 位和tp 一样, 那么下一步应该取tp 的哪个孩子完全由该键值的7 ,8 ,9 位3 位决定,为什么,因为tp 的孩子节点就3 位,为7 ,8 ,9 ;到了该孩子节点后就该重复前面的 过程了,该孩子节点从15 位往后不同,具有2 的4 次方个孩子,下一步该走向何方由查找键值的15 ,16 ,17 ,18 决定,决定该向哪个tp 的孙子前进,但 是如果查找键的15 位之前和n 的键不同怎么办,还要继续往下去吗?当然不必了,往下去是没有意义的,但是如果n 的键值在和查找键值不同的那一位以及那一位 往后都是0 的话,那么一直可以到一个叶子节点,它就是该次查找的结果,可能是一条普适路由,于是在这种情况下还是要向下的,直到不符合全为0 的条件后开始 回溯,回溯就有可能找到吗?实际上回溯根本不可能找到一条精确路由,这个结果在你找到一个和n 在n->pos 前不同位的那一刻就决定了,那回溯干什 么,回溯是为了找到一条普适路由,仅此而已,下面用代码说明上述过程:

static int fn_trie_lookup(struct fib_table *tb, const struct flowi *flp, struct fib_result *res)

 {

         struct trie *t = (struct trie *) tb->tb_data;

         int plen, ret = 0;

         struct node *n;

         struct tnode *pn;

         int pos, bits;

         t_key key = ntohl(flp->fl4_dst);

         int chopped_off;

         t_key cindex = 0;

         int current_prefix_length = KEYLENGTH;

         struct tnode *cn;

         t_key node_prefix, key_prefix, pref_mismatch;

         int mp;

         rcu_read_lock(); 

         n = rcu_dereference(t->trie);     // 从根开始

         if (!n)// 还没有路由信息

                 goto failed;

         if (IS_LEAF(n)) { // 如果是叶子节点就检查看是不是咱要的,仅此一步定乾坤。

                 if ((ret = check_leaf(t, (struct leaf *)n, key, &plen, flp, res)) <= 0)

                         goto found;

                 goto failed;

         }

         pn = (struct tnode *) n;// 开始正规的trie 树的遍历查找,第一次查找的实际上是根节点,偏移为0

         chopped_off = 0; 

         while (pn) {

                 pos = pn->pos;// 得到当前节点的比较位偏移量,指示此位后不同。

                 bits = pn->bits;

                 if (!chopped_off)// 以下寻找孩子,没有回溯的情况下chopped_off 始终为0

                         // 一会解释以下这个函数           

                         cindex = tkey_extract_bits(MASK_PFX(key, current_prefix_length), pos, bits);

                 n = tnode_get_child(pn, cindex);

                 if (n == NULL) {

                         goto backtrace;

                 }

                 if (IS_LEAF(n)) {// 如果得到的孩子是叶子,那么定乾坤的时候到了。

                         if ((ret = check_leaf(t, (struct leaf *)n, key, &plen, flp, res)) <= 0)

                                 goto found;

                         else

                                 goto backtrace;

                 }

                 cn = (struct tnode *)n;// 开始和此孩子比较

                 if (current_prefix_length < pos+bits) {// 在寻找普适路由的情况下会出现这种情况,也就是说,下面的操作意义在于一旦发现这条路由有一位不是0 那么就不可能是普适路由,于是回溯。

                      if (tkey_extract_bits(cn->key, current_prefix_length,

                             cn->pos - current_prefix_length) != 0 ||

                             !(cn->child[0]))

                                 goto backtrace;

                 }

                 node_prefix = MASK_PFX(cn->key, cn->pos);// 得到孩子节点到它的pos 为止的前缀

                 key_prefix = MASK_PFX(key, cn->pos);     // 得到查找键到当前孩子节点的pos 为止的前缀

                 pref_mismatch = key_prefix^node_prefix;// 比较两个前缀

                 mp = 0;// 为了找到查找键和当前孩子节点从左边数第一个不同的位置而设置的一个变量

                 if (pref_mismatch) {// 如果有不同的位,那么就:1. 可能回溯;2. 可能是一条普适路由。

                         while (!(pref_mismatch & (1<<(KEYLENGTH-1)))) {// 此循环找到二者从左边数第一个不同的位的偏移。

                                 mp++;

                                 pref_mismatch = pref_mismatch <<1;

                         }

                         key_prefix = tkey_extract_bits(cn-& gt;key, mp, cn->pos-mp);// 此操作就是看看该孩子节点从和查找建不同的位开始一直到它的pos 是不是全0 ,若是,那么它 有可能是一条普适路由的一部分,若否,则只有回溯去查找更大范围的普适路由了

                         if (key_prefix != 0)

                                 goto backtrace;

                         if (current_prefix_length >= cn->pos)

                                 current_prefix_length = mp;// 注意,非回溯的情况下只在这里更新current_prefix_length ,它的目的就是查找普适路由,注意,事故已经发生了,我们在挽救。

                 }

                 pn = (struct tnode *)n; /* Descend */

                 chopped_off = 0;

                 continue; 

 backtrace:

                 chopped_off++;// 将chopped_off 递增,表示要回溯,回溯就不要找孩子节点了,因为在chopped_off 大于的情况下,表示孩子都已经测试过了,不匹配,需要再向上寻找更大范围的普适路由。

                 /* As zero don't change the child key (cindex) */

                 while ((chopped_off <= pn->bits) && !(cindex & (1<<(chopped_off-1))))

                         chopped_off++;

                // 以下就是回溯的具体实施了,就是一些位运算了。

                 if (current_prefix_length > pn->pos + pn->bits - chopped_off)

                         current_prefix_length = pn->pos + pn->bits - chopped_off; 

                 if (chopped_off <= pn-& gt;bits) {// 得到可能的“ 下一个” 孩子节点,在33 行选择的孩子已经失败,那么要选择下一个了,有下一个吗?实际上可能有的,比如这次查找失 败的孩子在pn 的bits 是二进制1101 ,那么如果有一个孩子的相应位是1100 或1000 就是回溯的对象,这也是这里位运算的目的。

                         cindex &= ~(1 << (chopped_off-1));

                 } else {// 如果没有,那么也就只能回到更上一级的爷爷那里了。

                         if (NODE_PARENT(pn) == NULL)

                                 goto failed;

                         /* Get Child's index */

                         cindex = tkey_extract_bits(pn->key, NODE_PARENT(pn)->pos, NODE_PARENT(pn)->bits);

                         pn = NODE_PARENT(pn);

                         chopped_off = 0;

                         goto backtrace;

                 }

         }

 failed:

         ret = 1;

 found:

         rcu_read_unlock();

         return ret;

 }

  最后看看那个被忽略的函数:

 static inline t_key tkey_extract_bits(t_key a, int offset, int bits)

 {// 这个函数的本质就是混略掉我们当前考虑的以低的位和以高的位从而得到一个索引一样的数据,毕竟那些高位以前已经考虑过了,而低位暂时还用不到。

         if (offset < KEYLENGTH)

                 return ((t_key)(a << offset)) >> (KEYLENGTH - bits);

         else

                 return 0;

 }

efine MASK_PFX(k, l) (((l)==0)?0:(k >> (KEYLENGTH-l)) << (KEYLENGTH-l))


以上就是查找算法的全部了,至于说为何这么简单,还是要看另一头的操作,就是插入操作,那个操作就复杂多了,这就叫做起来难,用起来容易。在查找算法中,父辈们往往把责任往子孙们身上推卸,等到子孙解决不了了,再回溯给父辈们,真是有意思。

 

国密证书实战:在CentOS 7上构建GmSSL私有CA与完整证书链 本文详细介绍了在CentOS 7操作系统上,使用GmSSL工具构建私有国密证书颁发机构(CA)与完整证书链的实战指南。内容涵盖从部署GmSSL、创建国密根CA、签发服务器与客户端证书,到生产环境安全加固与最佳实践的全流程,旨在帮助系统管理员和安全工程师建立统一、可控的内部国密安全通信体系。 阅读详情

相关推荐

VMware ESXi 7.0下Intel网卡SR-IOV配置实战:解决虚拟化网络延迟问题

本文详细介绍了在VMware ESXi 7.0环境下为Intel网卡配置SR-IOV(单根I/O虚拟化)的完整实战流程。通过硬件辅助直通技术,虚拟机可直接访问物理网卡资源,有效绕过虚拟化层瓶颈,从而显著降低网络延迟和CPU开销,适用于金融交易、实时计算等对性能敏感的场景。

embedding5hiker的博客 552

linux网络协议栈源码分析 - 路由查找树

linux内核路由查找树

arm7star的博客 2734

RDMA网络通信技术、NCCL集合通讯(GPU)

NCCL 是 NVIDIA 专为 GPU 设计的高性能集合通信库,用于优化多 GPU、多节点间的集体操作(如 AllReduce、Broadcast、AllGather 等),是深度学习分布式训练的核心组件。RDMA 允许计算机直接访问远程内存,无需 CPU 干预,大幅降低了网络通信的延迟和 CPU 开销。常见的 RDMA 实现包括。是两项关键技术,用于优化节点间数据传输效率和大规模并行计算性能。在高性能计算(HPC)、人工智能训练和数据密集型场景中,

weixin_42795092的博客 2422

kernel中路由表的实现

TCP/IP学习(41)——Kernel中路由表的实现(4) 2013-10-17 09:11:18 分类: LINUX 原文地址:TCP/IP学习(41)——Kernel中路由表的实现(4) 作者:GFree_Wind 本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作

H002399的专栏 1586

三维视觉智能焊接系统:免示教技术的工业应用实践

本文深入探讨了三维视觉智能焊接系统中免示教技术的工业应用实践。该系统通过三维视觉相机实时扫描工件,自动识别焊缝并生成轨迹,将产线换型时间从数小时缩短至几分钟,显著提升了生产柔性。文章详细解析了其核心硬件、算法及工厂部署流程,并展望了与数字孪生、工艺智能化融合的未来趋势。

weixin_29091105的博客 432

Unity接入Google登录:Credential Manager完整集成指南与避坑实践

在移动应用开发中,第三方登录是提升用户体验和降低注册门槛的关键技术。其核心原理是通过OAuth 2.0等授权协议,安全地获取用户在平台的身份凭证。对于面向海外市场的Unity应用,Google登录因其庞大的用户基数成为首选方案。然而,随着Android系统更新和隐私政策收紧,传统的WebView登录方式面临兼容性挑战。Credential Manager作为Google推出的现代化凭据管理API,通过系统级集成解决了这一问题,它统一了密码、通行密钥和第三方登录的调用接口,显著提升了登录成功率和原生体验。在U

weixin_28025327的博客 318

FIB统计信息文件fib_triestat

如下fib_triestat文件的内容。 # cat /proc/net/fib_triestat Basic info: size of leaf: 48 bytes, size of tnode: 40 bytes. Id 10: Aver depth: 1.66 Max depth: 2 Leaves: 3 Prefixes: 3 Internal nodes: 2

redwingz的博客 1179

GEOSHIELD:分布式CPS的拜占庭容错与安全防护技术

分布式系统在关键基础设施(如轨道交通、智能电网)中面临网络延迟和恶意节点的双重挑战。拜占庭容错(BFT)技术通过算法保证系统在存在故障节点时仍能达成一致,是保障系统安全的核心机制。GEOSHIELD创新性地结合概率化网络延迟测量和TGS动态信任评分,实现了不依赖可信硬件的拜占庭 resilience。该技术通过建立节点行为的信用档案,动态识别恶意行为,在保证安全性的同时最大化系统可用性。实验证明,即使在自适应攻击策略下,GEOSHIELD仍能保持系统95.6%的正常运行时间,为CPS系统提供了可靠的安全防护

weixin_31766003的博客 346

IPv4路由信息查看fib_trie

创建路由表10,并在其中添加三条路由项: # sudo ip route add 1.1.1.0/24 via 192.168.2.2 table 10 # sudo ip route add 1.1.0.0/16 via 192.168.2.3 table 10 # sudo ip route add 1.0.0.0/8 via 192.168.2.4 table 10 # # ip -d route show table 10 unicast 1.0.0.0/8 via 192.16

redwingz的博客 1599

PotPlayer 史诗级更新!实时字幕生成+实时翻译,看片真的无敌了!

potplayer已经更新实时字幕生成+实时字幕翻译功能

阿虚同学的博客 1万+

3D视觉入门必看:深度图、点云图、IR图、RGB图像到底有什么区别?

本文深入解析了3D视觉中深度图、点云图、IR图与RGB图像的核心区别。深度图记录像素距离,是连接2D与3D的桥梁;点云图是显式的三维坐标集合;IR图感知红外光强度;RGB图像则提供颜色纹理。理解其信息维度和应用场景,是进行机器人导航、三维重建等任务的基础。

year5的博客 342

路由表的结构与算法分析--trie插入

Normal 0 7.8 磅 0 2 false false false MicrosoftInternetExplorer4 <!-- /* Sty

linuxcode_turing的专栏 767

手把手教你用RSoft BPM仿真一个1x2光分束器:从参数设置到结果分析全记录

本文详细介绍了使用RSoft BPM模块设计和仿真1×2光分束器的完整流程,包括参数设置、结构建模、仿真配置和结果分析。通过实战指南,帮助读者掌握光波导器件的仿真技术,实现50:50功率分配的设计目标,适用于光通信和集成光学领域的研究与应用。

weixin_30545945的博客 545

linux 路由算法 trie,路由表的结构与算法分析--trie插入

在linux的trie树中插入一条路由是很复杂的,远比查找要复杂的多,因为每插入一条路由就要看看是否要动态调整trie树,不是还好,如果要动态调 整,那活儿就大了,正是因为这一点,trie路由表往往在大型的机器上用到,那些机器不吝啬动态调整trie树的时候的空间损失,如果是性能不那么靠铺的 机器,还是老老实实用哈希路由表吧,只要不是疯狂插入很多路由表导致很多哈希碰撞,性能是很棒的,我在pc上装过t...

weixin_39555715的博客 401

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

上一篇: 对BSD的新路由查找算法的理解
下一篇: 路由表的结构与算法分析--trie插入
linuxcode_turing
博客等级 码龄17年 0粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值