Linux内核jump label与static key的原理与示例
共 1698字,需浏览 4分钟
·
2021-04-09 09:37
转载:CSDN博主「dog250」
链接:https://blog.csdn.net/dog250/article/details/106715700
jump label机制进入Linux内核已经很多很多年了,它的目的是 消除分支。 为了达到这个目的,jump label的手段是 修改分支处的代码。
~把代码当做数据,代码和数据在冯诺伊曼计算机中得到了统一~
本质上,jump label作用于下面的逻辑:
静态拆分成了下面的两个逻辑,其一是:
或者,其二是:
但二者不能同时共存。显然,这破坏了通用性和灵活性,带来了高效!
这相当于一个硬熔断,具体详情参见:
https://blog.csdn.net/dog250/article/details/6123517
【PS:这篇文章是我上周找到的,看完了才发现,竟然是我自己写的】
本文来一点可以看得见的东西,演示一下真实的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判断。