根据docs,kprobes禁用抢占:
从commit 9a09f261a中,我们可以清楚地看到优化的kprobes用于在启用抢占的情况下运行。
为什么会这样?我了解kprobes是一种在内核中的特定地址注入(inject)一些代码的方式,并且了解到任何代码都可以。
最佳答案
至少在x86上,Kprobes的实现依赖于这样的事实,即在Kprobe处理程序运行时禁用了抢占。
当您将普通的(不是基于Ftrace的)Kprobe放在一条指令上时,该指令的第一个字节将被0xcc(int3,“软件断点”)覆盖。如果内核尝试执行该指令,则会发生陷阱并调用kprobe_int3_handler()
(请参阅do_int3()的实现)。
要调用您的Kprobe处理程序,kprobe_int3_handler()查找哪个Kprobe命中,将其保存为percpu变量current_kprobe
并调用您的预处理程序。之后,它准备一切以单步执行原始指令。单步执行后,将调用您的后处理程序,然后执行一些清除操作。 current_kprobe
和其他每CPU数据用于完成所有这些操作。只有在那之后才启用抢占。
现在,想象一下预处理程序已启用抢占,立即抢占并在其他CPU上恢复。如果Kprobes的实现尝试访问current_kprobe
或其他每CPU数据,则内核可能会崩溃(如果该CPU上目前没有current_kprobe,则NULL指针取消引用)或更糟。
或者,被抢占的处理程序可以在同一CPU上继续运行,但另一个Kprobe可能在睡眠时到达该处理器-current_kprobe
等将被覆盖,并且很可能发生灾难。
在Kprobe处理程序中重新启用抢占可能会导致难以调试的内核崩溃和其他问题。
简而言之,这是因为Kprobes至少在x86上是这样设计的。关于它们在其他体系结构上的实现,我不能说太多。
根据您要完成的工作,其他内核功能可能会有所帮助。
例如,如果只需要在某些函数的开头运行代码,请查看Ftrace。然后,您的代码将在与挂钩函数相同的条件下运行。
话虽如此,实际上我的一个项目中确实需要使用Kprobes,以便处理程序在相同的条件下运行。抢占作为探测的指令。您可以找到实现here。但是,它必须跳过障碍才能实现这一目标,而不会破坏任何东西。到目前为止,它一直可以正常工作,但是它比我想的要复杂得多,并且也存在可移植性问题。