影子变量

影子变量是 livepatch 模块将额外的“影子”数据与现有数据结构关联的一种简单方法。影子数据与保持未修改的父数据结构分开分配。本文档中描述的影子变量 API 用于在父对象之间分配/添加以及移除/释放影子变量。

该实现引入了一个全局的内核内哈希表,该表将指向父对象的指针与影子数据的数字标识符关联起来。数字标识符是一个简单的枚举,可用于描述影子变量的版本、类或类型等。更具体地说,父指针充当哈希表键,而数字 ID 随后用于过滤哈希表查询。多个影子变量可以附加到同一个父对象,但它们的数字标识符可以区分它们。

1. API 简要总结

(有关完整的 API 用法 docbook 注释,请参阅 livepatch/shadow.c。)

哈希表引用了所有影子变量。这些引用通过 <obj, id> 对进行存储和检索。

  • klp_shadow 变量数据结构封装了跟踪元数据和影子数据

    • 元数据

      • obj - 指向父对象的指针

      • id - 数据标识符

    • data[] - 影子数据的存储空间

需要注意的是,默认情况下 klp_shadow_alloc()klp_shadow_get_or_alloc() 会将变量清零。当需要非零值时,它们还允许调用自定义构造函数。调用者应提供所需的任何互斥机制。

请注意,构造函数是在 klp_shadow_lock 自旋锁下调用的。它允许执行在分配新变量时只能执行一次的操作。

  • klp_shadow_get() - 检索影子变量数据指针 - 在哈希表中搜索 <obj, id> 对

  • klp_shadow_alloc() - 分配并添加一个新的影子变量 - 在哈希表中搜索 <obj, id> 对

    • 如果存在

      • 发出警告 (WARN) 并返回 NULL

    • 如果 <obj, id> 尚不存在

      • 分配一个新的影子变量

      • 如果提供了自定义构造函数和数据,则使用它们初始化变量

      • 将 <obj, id> 添加到全局哈希表

  • klp_shadow_get_or_alloc() - 获取现有影子变量或分配新影子变量 - 在哈希表中搜索 <obj, id> 对

    • 如果存在

      • 返回现有的影子变量

    • 如果 <obj, id> 尚不存在

      • 分配一个新的影子变量

      • 如果提供了自定义构造函数和数据,则使用它们初始化变量

      • 将 <obj, id> 对添加到全局哈希表

  • klp_shadow_free() - 解除并释放一个 <obj, id> 影子变量 - 从全局哈希表中查找并移除 <obj, id> 引用

    • 如果找到

      • 如果定义了析构函数,则调用它

      • 释放影子变量

  • klp_shadow_free_all() - 解除并释放所有 <_, id> 影子变量 - 从全局哈希表中查找并移除任何 <_, id> 引用

    • 如果找到

      • 如果定义了析构函数,则调用它

      • 释放影子变量

2. 用例

(有关完整的可运行演示,请参阅 samples/livepatch/ 中的影子变量 livepatch 示例模块。)

对于以下用例示例,请考虑 提交 1d147bfa6429(“mac80211: fix AP powersave TX vs. wakeup race”),它向 net/mac80211/sta_info.h :: struct sta_info 添加了一个自旋锁。每个用例示例都可以被视为该修复程序的独立 livepatch 实现。

匹配父对象的生命周期

如果频繁创建和销毁父数据结构,最简单的方法可能是将它们的影子变量生命周期与相同的分配和释放函数对齐。在这种情况下,通常会分配、初始化父数据结构,然后以某种方式对其进行注册。然后,影子变量的分配和设置可以被视为父对象初始化的一部分,并且应该在父对象“上线(go live)”之前完成(即,针对此 <obj, id> 对发出任何影子变量 get-API 请求)。

对于 提交 1d147bfa6429,当分配父 sta_info 结构时,分配 ps_lock 指针的影子副本,然后对其进行初始化

#define PS_LOCK 1
struct sta_info *sta_info_alloc(struct ieee80211_sub_if_data *sdata,
                                const u8 *addr, gfp_t gfp)
{
      struct sta_info *sta;
      spinlock_t *ps_lock;

      /* Parent structure is created */
      sta = kzalloc(sizeof(*sta) + hw->sta_data_size, gfp);

      /* Attach a corresponding shadow variable, then initialize it */
      ps_lock = klp_shadow_alloc(sta, PS_LOCK, sizeof(*ps_lock), gfp,
                                 NULL, NULL);
      if (!ps_lock)
              goto shadow_fail;
      spin_lock_init(ps_lock);
      ...

当需要 ps_lock 时,查询影子变量 API 以检索特定 struct sta_info 的锁:

void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta)
{
      spinlock_t *ps_lock;

      /* sync with ieee80211_tx_h_unicast_ps_buf */
      ps_lock = klp_shadow_get(sta, PS_LOCK);
      if (ps_lock)
              spin_lock(ps_lock);
      ...

当释放父 sta_info 结构时,首先释放影子变量

void sta_info_free(struct ieee80211_local *local, struct sta_info *sta)
{
      klp_shadow_free(sta, PS_LOCK, NULL);
      kfree(sta);
      ...

运行中的父对象

有时,与父对象一起分配影子变量可能并不方便或不可能。或者,livepatch 修复可能仅需要父对象实例子集的影子变量。在这些情况下,可以使用 klp_shadow_get_or_alloc() 调用将影子变量附加到已经在运行中的父对象。

对于 提交 1d147bfa6429,分配影子自旋锁的一个好地方是在 ieee80211_sta_ps_deliver_wakeup() 内部

int ps_lock_shadow_ctor(void *obj, void *shadow_data, void *ctor_data)
{
      spinlock_t *lock = shadow_data;

      spin_lock_init(lock);
      return 0;
}

#define PS_LOCK 1
void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta)
{
      spinlock_t *ps_lock;

      /* sync with ieee80211_tx_h_unicast_ps_buf */
      ps_lock = klp_shadow_get_or_alloc(sta, PS_LOCK,
                      sizeof(*ps_lock), GFP_ATOMIC,
                      ps_lock_shadow_ctor, NULL);

      if (ps_lock)
              spin_lock(ps_lock);
      ...

此用法仅在需要时创建影子变量,否则它将使用已为此 <obj, id> 对创建的变量。

与前一个用例一样,需要清理影子自旋锁。可以在释放父对象之前释放影子变量,甚至在不再需要影子变量本身时释放它。

其他用例

影子变量还可以用作标志,指示数据结构是由新的 livepatched 代码分配的。在这种情况下,影子变量拥有什么数据值并不重要,它的存在表明了如何处理父对象。

3. 参考资料