系统状态变更¶
有些用户非常不愿意重启系统。这导致需要提供更多的热补丁,并维护它们之间的一些兼容性。
使用累积热补丁可以更容易地维护更多的热补丁。每个新的热补丁都会完全替换任何旧的热补丁。它可以保留、添加甚至删除修复。并且由于原子替换功能,通常可以安全地将任何版本的热补丁替换为任何其他版本。
问题可能来自影子变量和回调。它们可能会改变系统行为或状态,以至于返回并使用旧的热补丁或原始内核代码不再安全。此外,任何新的热补丁都必须能够检测到已安装的热补丁所做的更改。
这就是热补丁系统状态跟踪的用武之地。它允许:
存储操作和恢复系统状态所需的数据
使用更改 ID 和版本定义热补丁之间的兼容性
1. 热补丁系统状态 API¶
系统状态可能会被多个热补丁回调或新使用的代码修改。此外,必须能够找到已安装的热补丁所做的更改。
每个修改后的状态都由 struct klp_state
描述,请参阅 include/linux/livepatch.h。
每个热补丁都定义了一个 struct klp_states 数组。它们列出了热补丁修改的所有状态。
热补丁作者必须为每个 struct klp_state
定义以下两个字段:
id
用于标识受影响的系统状态的非零数字。
version
描述给定热补丁支持的系统状态更改变体的数字。
可以使用两个函数来操作状态:
获取与给定热补丁和状态 ID 关联的
struct klp_state
。
获取与给定功能 ID 和已安装的热补丁关联的
struct klp_state
。
2. 热补丁兼容性¶
系统状态版本用于防止加载不兼容的热补丁。该检查在启用热补丁时完成。规则如下:
允许任何全新的系统状态修改。
对于已修改的系统状态,允许具有相同或更高版本的系统状态修改。
累积热补丁必须处理已安装的热补丁的所有系统状态修改。
非累积热补丁允许修改已修改的系统状态。
3. 支持的场景¶
热补丁有其生命周期,系统状态更改也是如此。每个兼容的热补丁都必须支持以下场景:
当启用热补丁且该状态尚未被正在替换的热补丁修改时,修改系统状态。
当系统状态已被正在替换的热补丁修改时,接管或更新系统状态修改。
禁用热补丁时,恢复原始状态。
在转换还原时,恢复先前的状态。它可能是原始系统状态,也可能是被替换的热补丁所做的状态修改。
当发生错误且无法启用热补丁时,删除任何已做的更改。
4. 预期用法¶
系统状态通常由热补丁回调修改。每个回调的预期角色如下:
pre_patch()
在必要时分配 state->data。分配可能会失败,而 pre_patch() 是唯一可以停止加载热补丁的回调。当数据已由先前安装的热补丁提供时,则不需要分配。
在转换完成之前,执行新代码所需的任何其他准备操作。例如,初始化 state->data。
系统状态本身通常在 post_patch() 中修改,此时整个系统都能够处理它。
在发生错误时清理自己的混乱。这可以通过自定义代码或显式调用 post_unpatch() 来完成。
post_patch()
当它们兼容时,从先前的热补丁复制 state->data。
进行实际的系统状态修改。最终允许新代码使用它。
确保 state->data 具有所有必要的信息。
当不再需要时,从替换的热补丁释放 state->data。
pre_unpatch()
阻止由热补丁添加的代码依赖于系统状态更改。
还原系统状态修改。
post_unpatch()
通过检查
klp_get_prev_state()
来区分转换还原和热补丁禁用。在转换还原的情况下,恢复先前的系统状态。这可能意味着什么都不做。
删除任何不再需要的设置或数据。
注意
pre_unpatch() 通常执行与 post_patch() 对称的操作。只是它仅在禁用热补丁时调用。因此,它不需要关心任何先前安装的热补丁。
post_unpatch() 通常执行与 pre_patch() 对称的操作。它也可能在转换还原期间被调用。因此,它必须处理先前安装的热补丁的状态。