系统状态变更

有些用户非常不愿意重启系统。这导致需要提供更多的热补丁,并维护它们之间的一些兼容性。

使用累积热补丁可以更容易地维护更多的热补丁。每个新的热补丁都会完全替换任何旧的热补丁。它可以保留、添加甚至删除修复。并且由于原子替换功能,通常可以安全地将任何版本的热补丁替换为任何其他版本。

问题可能来自影子变量和回调。它们可能会改变系统行为或状态,以至于返回并使用旧的热补丁或原始内核代码不再安全。此外,任何新的热补丁都必须能够检测到已安装的热补丁所做的更改。

这就是热补丁系统状态跟踪的用武之地。它允许:

  • 存储操作和恢复系统状态所需的数据

  • 使用更改 ID 和版本定义热补丁之间的兼容性

1. 热补丁系统状态 API

系统状态可能会被多个热补丁回调或新使用的代码修改。此外,必须能够找到已安装的热补丁所做的更改。

每个修改后的状态都由 struct klp_state 描述,请参阅 include/linux/livepatch.h。

每个热补丁都定义了一个 struct klp_states 数组。它们列出了热补丁修改的所有状态。

热补丁作者必须为每个 struct klp_state 定义以下两个字段:

  • id

    • 用于标识受影响的系统状态的非零数字。

  • version

    • 描述给定热补丁支持的系统状态更改变体的数字。

可以使用两个函数来操作状态:

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() 对称的操作。它也可能在转换还原期间被调用。因此,它必须处理先前安装的热补丁的状态。