原子替换与累积补丁¶
热补丁(livepatches)之间可能存在依赖关系。如果多个补丁需要对同一个(或多个)函数进行不同的修改,那么我们就需要定义补丁安装的顺序。并且,任何较新的热补丁的函数实现必须构建在较旧的补丁之上。
这可能会成为维护上的噩梦。尤其是当多个补丁以不同的方式修改了同一个函数时。
一种优雅的解决方案是引入名为“原子替换”(Atomic Replace)的功能。它允许创建所谓的“累积补丁”(Cumulative Patches)。它们包含了来自所有旧热补丁的所需修改,并在一次过渡中完全替换它们。
用法¶
可以通过在 struct klp_patch 中设置 “replace” 标志来启用原子替换,例如
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
.replace = true,
};
然后,所有进程都会被迁移到仅使用新补丁的代码。一旦过渡完成,所有旧补丁都会自动禁用。
Ftrace 处理程序会从不再由新累积补丁修改的函数中透明地移除。
因此,热补丁作者可能只需要维护一个累积补丁的源代码。这有助于在添加或删除各种修复或功能时保持补丁的一致性。
过渡完成后,用户可以在系统上仅保留最后一个安装的补丁。这有助于清楚地查看当前实际使用的代码。此外,热补丁可以被视为修改内核行为的“普通”模块。唯一的区别在于它可以在运行时更新,而不会破坏其功能。
功能¶
原子替换允许:
在升级其他函数的同时,原子化地还原之前补丁中的某些函数。
消除由于不再修补的函数的核心重定向(core redirection)而可能导致的性能影响。
减少用户对热补丁之间依赖关系的困惑。
限制:¶
一旦操作完成,就没有直接的方法可以撤销它并原子化地恢复被替换的补丁。
一个好的做法是在任何发布的热补丁中设置 .replace 标志。这样,重新添加旧的热补丁就等同于降级到该补丁。只要热补丁没有在 (un)patching 回调或
module_init()或module_exit()函数中进行额外的修改,这样做是安全的,详见下文。还要注意,只有在过渡没有被强制执行时,被替换的补丁才能被移除并重新加载。
仅执行来自_新_累积热补丁的 (un)patching 回调。来自被替换补丁的所有回调都将被忽略。
换句话说,累积补丁负责执行正确替换任何旧补丁所需的任何操作。
因此,用旧的补丁替换较新的累积补丁可能会有危险。旧的热补丁可能无法提供必要的回调。
在某些场景下,这可能被视为一种限制。但在许多其他场景中,它使工作变得更容易。只有新的累积热补丁知道添加/删除了哪些修复/功能,以及平稳过渡需要哪些特殊操作。
无论如何,如果调用所有已启用补丁的回调,考虑各种回调的顺序及其相互作用将是一场噩梦。
没有对影子变量(shadow variables)的特殊处理。热补丁作者必须制定自己的规则,以将它们从一个累积补丁传递到另一个累积补丁。特别要注意的是,他们不应盲目地在
module_exit()函数中移除它们。一个好的做法是在 post-unpatch 回调中移除影子变量。它仅在热补丁被正确禁用时才会被调用。