关于影子变量
影子变量是一种简单的方法,用于让 livepatch 模块将额外的“影子”数据与现有数据结构关联起来。影子数据是单独分配的,而父数据结构保持不变。本文档描述的影子变量 API 用于为它们的父对象分配/添加和移除/释放影子变量。
该实现引入了一个全局的内核哈希表,将父对象的指针和影子数据的数值标识符关联起来。数值标识符是一个简单的枚举,可用于描述影子变量的版本、类别或类型等。具体来说,父指针充当哈希表键,而数值标识符随后用于过滤哈希表查询。多个影子变量可以附加到同一个父对象上,但它们的数值标识符可以区分它们。
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/ 中的示例影子变量 livepatch 模块,以获取完整的工作演示。)
对于以下使用案例示例,请考虑提交 1d147bfa6429("mac80211: fix AP powersave TX vs. wakeup race"),该提交在 net/mac80211/sta_info.h :: struct sta_info 中添加了一个自旋锁。每个使用案例示例都可以视为此修复的独立 livepatch 实现。
匹配父对象的生命周期
如果父数据结构经常被创建和销毁,将它们的影子变量生命周期与相同的分配和释放函数对齐可能是最简单的方法。在这种情况下,通常会分配、初始化父数据结构,然后以某种方式注册它们。影子变量的分配和设置可以被视为父对象的初始化的一部分,并且应在父对象“上线”之前完成(即,为此 <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; /* 创建父结构 */ sta = kzalloc(sizeof(*sta) + hw->sta_data_size, gfp); /* 附加相应的影子变量,然后对其进行初始化 */ 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; /* 与 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; /* 与 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> 对创建的变量。
与之前的使用案例类似,影子自旋锁需要进行清理。影子变量可以在其父对象被释放之前释放,或者甚至在不再需要影子变量本身时释放。
其他使用案例
影子变量还可以用作指示数据结构是由新的、经过 livepatch 的代码分配的标志。在这种情况下,影子变量持有的数据值并不重要,其存在表明如何处理父对象。
3. 参考资料
- livepatch 实现基于 kpatch 版本的影子变量。
- Kritis Makris, Kyung Dong Ryu 于 2007 年提出了一种称为“影子数据结构”的数据类型更新技术。