interleave:页轮流散布到 nodemask 内的节点
weighted interleave 再按节点权重决定散布比例
mempolicy · nodemask
N0
权重 w0
N1
权重 w1
N2
权重 w2
N3
权重 w3
缺页 fault 路径上的两个选择器
interleave_nid()
均分:在 nodemask 内挑下一个节点
weighted_interleave_nid()
按权重挑下一个节点
alloc_pages_bulk_weighted_interleave()
遍历全局权重表 iw_table,算出每个节点各分多少页
每次 fault 或批量分配,都先把状态拷一份
pol->nodes
128 字节 nodemask
memcpy
栈上副本
每次 folio fault
MAX_NUMNODES=1024 时 128 字节
kmalloc + memcpy
批量分配器每次调用都拷一份全局权重表 iw_table
每次批量分配
synchronize_rcu()
写者换表后等读者退出,才能释放旧表
阻塞写路径
拷贝防并发修改与除零,但成本落在最热的路径上。
为什么以前要拷贝?
写者随时改状态
cpuset rebind 改写 nodemask
sysfs 写者整体更换 iw_table
于是:先拷贝,再遍历副本
写者换表后 synchronize_rcu() 等读者退出,才能释放旧表
读者不能持锁遍历
页分配器可能睡眠
不能留在 RCU 读侧临界区
拷贝没有修正节点分布,只防并发修改与除零。
权重表:不拷贝,就地遍历
SRCU 读侧临界区
pin 住当前权重表
直接引用
iw_table
就地遍历,不拷副本
零拷贝 / 零 kmalloc
SRCU 允许睡眠
页分配器可以在读侧临界区里睡眠,不必先拷表
普通 RCU 读侧不能睡
写者换表后的退役路径
换表指针
call_srcu()
回调里 kfree_rcu()
不再 synchronize_rcu()
旧表由回调回收,睡眠中的读者与普通 RCU 读者都被覆盖
nodemask:直接遍历,补边界检查
weighted_interleave_nid()
边累加权重边数节点,目标查找限制在单趟内
权重总和为零或遍历扑空
→ 回退 numa_node_id()
interleave_nid()
改用 next_node_in() 遍历 nodemask
mask 中途缩小会绕回策略内的节点
越界 → 回退 numa_node_id()
alloc_pages_bulk_weighted_interleave()
按权重总和推算每节点页数;总和与节点数在遍历之外单独读出
rebind 可能让推算超过请求量 → 每块钳制到 page_array 剩余空间
读到空 mask → 返回已经分到的部分
cpuset cookie 用不上:两个函数拿 VMA 策略,rebind 在 mmap_write_lock 下,不走 mems_allowed_seq。
代价:rebind 期间分布精度下降;除零与越界由边界检查接手。
收益 · -fstack-usage 实测栈占用
MAX_NUMNODES=1024,单位字节,越小越好
weighted_interleave_nid
Before
184 B
After
56 B
−69.6%
interleave_nid
Before
168 B
After
32 B
−81.0%
alloc_pages_bulk_mempolicy_noprof
Before
360 B
After
136 B
−62.2%
另外:少一次 kmalloc + memcpy;写者不再被 synchronize_rcu() 阻塞。
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x