大家好,我是极客范的本期栏目编辑小友,现在为大家讲解Linux内核中的jump label原理与逻辑及运行过程问题。
跳转标签机制在Linux内核中已经存在多年,其目的是消除分支。为了实现这个目标,跳转标签意味着在分支修改代码。
~代码被视为数据,代码和数据在冯诺依曼计算机中统一~
本质上,跳转标签遵循以下逻辑:
静态分为以下两种逻辑,其中一种是:
或者,第二个是:
但它们不能同时共存。显然,这破坏了通用性和灵活性,带来了效率!
这相当于一个硬导火索。有关详细信息,请参见:
在本文中,我们将展示带有可见内容的真实跳转标签静态键。
先看下面的c代码:
很简单的代码,也很正确。但是,如果主函数是一个高频调用的函数,并且E1和E2不随代码的逻辑而变化,而只是设置了参数,那么if语句就应该尽量消除,以消除不必要的分支预测,这就是跳转标签的用武之地!
让我们用跳转标签机制重写上面的代码,请看:
定义JUMP_LABEL宏,并编译它以查看效果:
怎么做?static_branch_true内联函数如何判断真假?
实际上,跳转标签逻辑修改了代码段,取消了条件判断!这一切都发生在update_branch。让我们在update_branch调用之前看一下主函数的汇编代码:
执行update_branch后,主要功能发生了变化:
似乎就是这么回事!
多亏了一个新的部分,__jump_table,之所以如此容易发生这种情况,是因为我们可以通过objdump查看__jump_table的内容:
通过jump_label_demo.c的结构入口结构,我们知道这个部分包含许多3元组,并且包含3个字段:
要修改的代码地址。
需要jmp to的代码地址。
匹配健康。
我们看到67064000 0000000按照小端是0x400667,是需要修改的代码地址,而6E064000 000000按照小端是0x40066e。
看来__jump_table的这一项会把jmpq 40066c修改成jmpq 40066e,从而实现永久静态分支。
最后,在每个内联static_branch_true函数中填充__jump_table的内容。此参数的参数是一个键,它指示branchnentry三元组中的最后一个字段。
static_branch_true函数的内联非常重要,它实现了分支的功能。
entry三元组数据直接插入到__jump_table section,而不是共享同一个函数体。总之,如果你看代码还是觉得别扭,手敲一遍我上面的示例程序,就理解了,内核里面的也就这么回事,总结一句话:
依靠运行时修改代码而不是依靠状态数据来控制执行流。
我不知道这对于所谓的 通用计算机程序设计 是不是反其道而行之,但在效果上,它确实是一匹好马。不禁感叹, 硬编码读起来是丑陋的,但执行起来却是高效的!
灵活性换高效率,得不偿失,我是这样以为。jump label的本质在于, 将同时刻存在的一套代码沿着时间线在可预期的固定时间点上分割成逻辑相反的两套代码。
硬件性能的提升将会证明jump label就是个笑话。
说两句好话,Linux内核参数,sysctl变量基本上就可以通过jump label来运作,从而替代if判断。
原文链接:https://blog.csdn.net/dog250/article/details/106715700编辑:lyn
jump label机制进入Linux内核已经很多很多年了,它的目的是 消除分支。 为了达到这个目的,jump label的手段是 修改分支处的代码。
~把代码当做数据,代码和数据在冯诺伊曼计算机中得到了统一~
本质上,jump label作用于下面的逻辑:
静态拆分成了下面的两个逻辑,其一是:
或者,其二是:
但二者不能同时共存。显然,这破坏了通用性和灵活性,带来了高效!
这相当于一个硬熔断,具体详情参见:
本文来一点可以看得见的东西,演示一下真实的jump label & static key。
先看下面的C代码:
很简单的代码,也很正确。然而, 如果main函数是一个高频调用的函数,并且在E1,E2是不随着代码逻辑而发生变化,仅仅参数设定的情况下, 那么if语句尽量消除以消除不必要的分支预测,而这正是jump label的用武之地!
我们下面用jump label机制来重写上面的代码,请看:
定义JUMP_LABEL宏编译之,看看效果:
如何做到的呢?static_branch_true内联函数是如何判断true or false的呢?
事实上,jump label逻辑修改了代码段,取消了条件判断!这一切都是在update_branch中发生的。我们看下update_branch调用之前,main函数的汇编码:
在执行了update_branch之后,main函数发生了变化:
看样子就是这么回事!
之所以这件事可以发生得如此简单,多亏了一个新的section,即__jump_table,我们通过objdump看看__jump_table的内容:
通过jump_label_demo.c的struct entry结构体,我们直到这个section中包含了多个3元组,包含3个字段:
需要修改的代码地址。
需要jmp到的代码地址。
匹配健。
我们看67064000 00000000按照小端就是0x400667,它就是需要修改的代码地址,而6e064000 00000000按照小端则是0x40066e:
看来,这个__jump_table的item会将jmpq 40066c修改为jmpq 40066e,从而实现了 永久静态分支。
最后,__jump_table的内容就是在每一个内联的static_branch_true函数中被填充的,该参数的参数是一个key,它指示了branch entry三元组中的最后一个字段。
static_branch_true函数的内联非常重要,它实现了将branch entry三元组数据直接插入到__jump_table section,而不是共享同一个函数体。
总之,如果你看代码还是觉得别扭,手敲一遍我上面的示例程序,就理解了,内核里面的也就这么回事,总结一句话:
依靠运行时修改代码而不是依靠状态数据来控制执行流。
我不知道这对于所谓的 通用计算机程序设计 是不是反其道而行之,但在效果上,它确实是一匹好马。不禁感叹, 硬编码读起来是丑陋的,但执行起来却是高效的!
灵活性换高效率,得不偿失,我是这样以为。jump label的本质在于, 将同时刻存在的一套代码沿着时间线在可预期的固定时间点上分割成逻辑相反的两套代码。
硬件性能的提升将会证明jump label就是个笑话。
说两句好话,Linux内核参数,sysctl变量基本上就可以通过jump label来运作,从而替代if判断。
原文链接:https://blog.csdn.net/dog250/article/details/106715700编辑:lyn
.dfma { position: relative; width: 1000px; margin: 0 auto; } .dfma a::after { position: absolute; left: 0; bottom: 0; width: 30px; line-height: 1.4; text-align: center; background-color: rgba(0, 0, 0, .5); color: #fff; font-size: 12px; content:"广告"; } .dfma img { display: block; }