影子变量

影子变量是实时补丁模块将额外的“影子”数据与现有数据结构关联的一种简单方法。影子数据与父数据结构分开分配,父数据结构保持不变。本文档中描述的影子变量 API 用于分配/添加和移除/释放影子变量到/从它们的父对象。

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

1. 简要 API 总结

(请参阅 livepatch/shadow.c 中的完整 API 用法文档。)

哈希表引用所有影子变量。这些引用通过 <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> 对

    • 如果存在

      • 发出警告并返回 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/ 中的示例影子变量实时补丁模块。)

对于以下用例示例,请考虑 提交 1d147bfa6429 (“mac80211: 修复 AP powersave TX 与唤醒竞争”),该提交在 net/mac80211/sta_info.h :: struct sta_info 中添加了一个自旋锁。每个用例示例都可以被视为此修复的独立实时补丁实现。

匹配父对象的生命周期

如果父数据结构经常创建和销毁,那么将它们的影子变量的生命周期与相同的分配和释放函数对齐可能是最简单的。在这种情况下,父数据结构通常被分配、初始化,然后以某种方式注册。影子变量的分配和设置可以被视为父对象初始化的一部分,并且应在父对象“上线”之前完成(即,对此 <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);
      ...

运行中的父对象

有时,可能不方便或不可能与它们的父对象一起分配影子变量。或者,实时补丁修复可能只需要一部分父对象实例的影子变量。在这些情况下,可以使用 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> 对创建的变量。

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

其他用例

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

3. 参考