当前位置:首页>Linux>Linux MM 2026-05-08~10最新 Feature 分析报告

Linux MM 2026-05-08~10最新 Feature 分析报告

  • 2026-06-26 21:23:44
Linux MM 2026-05-08~10最新 Feature 分析报告
目录
  1. DAMON 随机数生成器性能优化:以无锁 PRNG 替代 get_random_u32_below()
  2. zsmalloc per-CPU 延迟释放:异步批量释放加速 swap entry 回收
  3. zsmalloc zs_free() 锁竞争优化:编码 class_idx 与缩小临界区
  4. readahead 在 EOF 处禁止设置 PG_readahead 标记
  5. 支持 zswap 后端的匿名大页换入
  6. userfaultfd 实现 VM 虚拟机内存工作集追踪
  7. dma-contiguous: 新增 Kconfig 选项为多 NUMA 节点自动设置 CMA
  8. 按 NUMA 节点暴露 Balloon 页面统计
  9. mm: 将零页设为只读以检测内核非法写入
  10. 限定零页重映射仅在 underused THP 分裂时生效
  11. selftests/cgroup: malloc 返回值检查
  12. mm/shrinker: 修复 set_shrinker_bit() 中的越界读取
  13. mm/filemap: 修复 page_cache_prev_miss() 无空洞时的返回值
  14. zram: 修复 zram_writeback_endio 中的 use-after-free
  15. x86/xen: 修复上下文切换中 lazy MMU 模式处理
  16. mm/slub: 在 flush_rcu_sheaves_on_cache() 外围持有 cpus_read_lock

1. DAMON 随机数生成器性能优化:以无锁 PRNG 替代 get_random_u32_below()

系列[PATCH v3] mm/damon: replace damon_rand() with a per-ctx lockless PRNG作者: Jiayuan Chen版本: v3(1p)

背景

DAMON(Data Access MONitor)通过周期性采样地址空间的随机页面来监控内存访问模式。在采样路径上,damon_rand() 函数被频繁调用,用于在每个 region 的地址范围内随机选取一个采样地址(sampling_addr)。原有的 damon_rand() 实现直接封装了内核的 get_random_u32_below(),是一个加密学强度的随机数生成器(CSPRNG),内部基于每 CPU 批处理熵池,周期性地用 ChaCha20 算法补充熵。每次调用需要执行 local_lock_irqsave() 和 local_unlock_irqrestore() 来保护该 per-CPU 熵池。

当 DAMON 配置了较高的 nr_regions 时(例如 20000+),kdamond 线程在每次采样迭代中需要对每个 region 调用一次 damon_rand(),导致 lock_acquire/local_lock 配对的开销以及 __get_random_u32_below() 自身的开销在性能剖析(perf profile)中占据了主导地位。

解决的问题

  • kdamond CPU 开销过高:在高 nr_regions 配置下,加密学随机数生成器的锁开销成为性能瓶颈,kdamond CPU 占用高达约 72% 的单核资源。
  • 安全随机数的不必要使用:DAMON 的 sampling_addr 仅用于选择 PTE accessed-bit 采样的探测点,不暴露给用户空间,不需要密码学强度的随机性。

如何做

本 patch 将 damon_rand() 的实现从加密学随机数生成器替换为基于 lfsr113(Linear Feedback Shift Register,一种线性 PRNG)的无锁伪随机数生成器。核心变化:

**PRNG 状态嵌入 damon_ctx**:在 struct damon_ctx 中新增 struct rnd_state rnd_state 字段。kdamond 是每个 ctx 的唯一消费者,因此不需要任何同步机制(无锁)。PRNG 在 damon_new_ctx() 中通过 get_random_u64() 获取密码学安全的种子初始化,之后所有调用完全在用户态级别完成。

范围映射:新的 damon_rand(ctx, l, r) 签名增加 ctx 参数以获取 PRNG 状态。对于跨度 span = r - l 不大于 U32_MAX 的常见情况,使用传统的 reciprocal multiplication 方法:l + (rnd * span) >> 32,与 get_random_u32_below() 所用方法一致。对于跨度超过 U32_MAX 的情况(仅在 64 位系统上可达),慢速路径将两个 32 位随机数组合为 64 位,然后使用 mul_u64_u64_shr() 进行 64 位范围映射。在 32 位系统上慢速路径是死代码,会被编译器消除。

调用者适配damon_split_regions_of() 和相关的 kunit 测试函数签名均增加 ctx 参数以传递 PRNG 上下文。物理地址监控(paddr.c)和虚拟地址监控(vaddr.c)的采样准备函数均相应更新。

作者明确指出 lfsr113 是线性 PRNG,绝对不能用于任何安全敏感的场合。此处使用非加密 PRNG 是合适的,因为 DAMON 的采样地址不暴露给用户空间,仅用作 PTE accessed-bit 采样的探测点。

收益

测试环境
指标
优化前
优化后
改善
paddr 监控, max_nr_regions=20000
kdamond CPU 使用率
~72%
~50%
降低约 30%

kdamond CPU 使用率从单核的约 72% 降至约 50%,相当于节省了约 22 个百分点的 CPU 资源。该优化对低 nr_regions 配置也有正向收益,因为完全消除了 per-CPU CSPRNG 的 local_lock_irqsave/restore 开销。


2. zsmalloc per-CPU 延迟释放:异步批量释放加速 swap entry 回收

系列[PATCH v3 00/04] mm/zsmalloc: per-cpu deferred free to accelerate swap entry release作者: Wenchao Hao, Barry Song版本: v3(4p)

背景

当进程的 VMA 中含有大量 swap entry 时,munmap(或进程退出)过程中的 swap 释放操作会非常昂贵。在 Android 系统的低内存终止(low-memory killer)场景中,当多个进程被同时终止以回收内存时,slot_free() 的耗时占到了释放 swap entry 总开销的 80% 以上。此前社区有两次尝试(Lei 和 Zhiguo)通过在 mm 核心层引入新线程来异步收集和释放 swap entry,方案设计本身相当复杂,未被合入。

当进程的匿名页(anon folios)和 swap entry 混合存在时,尽快回收 anon folios 可以帮助系统释放内存以满足新启动应用的需求。然而 swap 释放(涉及压缩内存的 zram/zswap 后端操作)如果阻塞了 anon folios 的释放,就会拖慢整体内存回收速度。swap 释放可以通过压缩内存逐步归还系统,但速率较慢。

解决的问题

  • swap 释放阻塞 anon folios 回收slot_free()(zram)和 zswap_entry_free()(zswap)在 munmap 热路径上执行,严重拖慢了被杀进程的内存回收速度。
  • 无统一的延迟释放框架:zram 和 zswap 各自需要异步释放能力,但缺乏统一的基础设施。
  • zram slot_free() 多次标志位写入开销:每次 slot_free() 中有 4 次独立的标志位清除操作,产生冗余的读-改-写周期。

如何做

本系列引入了一套基于回调(callback)的 zsmalloc 延迟释放框架,核心设计要点:

per-CPU 单页缓冲区:每个 CPU 拥有一个独立的单页缓冲区(struct zs_deferred_percpu),包含 buf 指针和 count 计数器。热路径(zs_free_deferred())通过 push 回调将值写入当前 CPU 的缓冲区,仅需关闭抢占(get_cpu_ptr),无需任何锁或原子操作。zram 存储 u32 的 slot 索引(每页 1024 个条目),zswap 存储 unsigned long 的 entry 指针(每页 512 个条目)。

页池旋转机制:预分配 ZS_DEFERRED_POOL_SIZE(256KB / PAGE_SIZE)个页面构成页池。当缓冲区填满时,从页池获取空闲页面进行交换(swap),将满页面加入 deferred_drain_list 并排队到 WQ_UNBOUND 工作队列(workqueue)。如果页池为空,调用者回退到同步处理路径。

回调接口:通过 struct zs_deferred_ops 定义 push 和 drain 两个回调。push 负责写入缓冲区并返回三种状态:ZS_PUSH_OK(写入成功)、ZS_PUSH_FULL(缓冲区已满,值未入队)、ZS_PUSH_FULL_QUEUED(缓冲区已满,值已入队)。drain 在后台 worker 中执行批量释放:zram 的 drain 执行 slot_lock + slot_free + slot_unlock,zswap 的 drain 执行完整的 zswap_entry_free()(包括 lru_delzs_free、memcg uncharge、cache_free 和统计更新)。

批量标志位清除:zram 的 slot_free() 中原有 4 次独立的 clear_slot_flag() 调用,分别清除 ZRAM_IDLEZRAM_INCOMPRESSIBLEZRAM_PP_SLOT 并重置压缩优先级。现合并为单次掩码写入:zram->table[index].attr.flags &= ~ZRAM_SLOT_FREE_CLEAR_MASK,减少冗余的读-改-写周期。

收益

测试平台:Raspberry Pi 4B, ARM64, 8GB RAM

Test 1: munmap 延迟,256MB swap-filled VMA(zram 后端)

模式
优化前
优化后
加速比
单进程
61.82ms
8.62ms
7.17x
多进程 2p
94.75ms
54.11ms
1.75x
多进程 3p
154.64ms
104.83ms
1.48x

Test 2: 不同大小的 munmap 延迟(zram,单进程)

大小
优化前
优化后
加速比
64MB
14.11ms
2.18ms
6.47x
128MB
29.45ms
4.48ms
6.57x
256MB
57.01ms
9.08ms
6.28x
512MB
115.13ms
55.58ms
2.07x
1024MB
229.66ms
153.28ms
1.50x

Test 3: munmap 延迟,256MB swap-filled VMA(zswap 后端)

模式
优化前
优化后
加速比
单进程
152.14ms
51.26ms
2.97x
多进程 2p
186.56ms
105.42ms
1.77x
多进程 3p
205.83ms
153.32ms
1.34x

单进程场景加速最为显著(zram 达 7.17x、zswap 达 2.97x),因为释放操作完全从热路径卸载到了后台 worker。512MB 以上时加速比递减,因为页池容量固定(256KB 缓冲区对应约 256 个 zspage),大量 swap entry 导致频繁回退到同步路径。


3. zsmalloc zs_free() 锁竞争优化:编码 class_idx 与缩小临界区

系列[PATCH 00/03] mm/zsmalloc: reduce lock contention in zs_free()作者: Wenchao Hao, Xueyuan Chen版本: v1(3p)

背景

在多进程并发执行 munmap 释放大量 swap entry 时,zsmalloc 的 zs_free() 函数是主要的性能瓶颈。锁竞争集中于两个层面:

  1. pool->lock(读写锁)的缓存行颠簸zs_free() 需要在 pool->lock 读锁保护下,通过 handle 逐步解析 handle->obj->PFN->zpdesc->zspage->class 链以获取 size class。虽然使用的是读锁,但 read_lock/read_unlock 的原子操作会在多个 CPU 同时访问同一缓存行时产生严重的缓存一致性流量(cacheline bouncing),多核并发时开销急剧上升。

  2. class->lock 横跨页面释放导致锁嵌套放大:原有的 zs_free() 在持有 class->lock(自旋锁)期间调用 free_zspage(),而 free_zspage() 最终需要获取 buddy allocator 的 zone->lock。在高内存压力下,zone->lock 竞争激烈时,持有 class->lock 的 CPU 会阻塞等待 zone->lock,从而阻塞所有等待同一 class->lock 的其他 CPU。

解决的问题

  • pool->lock 读锁竞争:多核并发 zs_free() 时的 read_lock 原子操作在高并发下成为瓶颈。
  • class->lock 临界区过长:页面释放的 zone->lock 嵌套在 class->lock 内,导致锁竞争传播放大。
  • 32 位系统的位宽限制:无法简单地在 obj 值中编码 class_idx。

如何做

本系列围绕三个独立的技术维度来解决上述锁竞争问题,三者可叠加但不互相依赖(64 位优化受架构条件限制,class->lock 拆分对所有架构生效)。

(一)obj 编码方案:将 class_idx 存入 obj 值中

这是消除 pool->lock 的前提条件。作者指出原有 64 位系统的 OBJ_INDEX_BITS 严重过剩——"OBJ_INDEX_BITS is over-provisioned on 64-bit systems",以 arm64 默认 chain_size=8 为例,OBJ_INDEX_BITS=24,但 obj_idx 只需 10 位。本方案从实际的 size class 数量和最大 obj 计数出发,动态计算所需位数 OBJ_CLASS_BITS_NEEDED 和 OBJ_IDX_BITS_NEEDED,将 obj 的位布局从 [PFN | obj_idx] 改为 [PFN | class_idx | obj_idx],并通过 static_assert 在编译期验证三个字段之和不超过 BITS_PER_LONG,确保在所有 64 位架构(包括 riscv64 Sv57 的 44 位 PFN 和 powerpc64 64K 页的 257 class)都能容纳。

关键不变量在于:"class_idx is invariant across page migration (only PFN changes)",迁移只改写 PFN 字段,class_idx 和 obj_idx 始终保持不变。因此无需加锁即可从 obj 值中提取 class_idx——无锁读取总是得到合法的 class_idx。location_to_obj() 接口同步扩展,传入 class_idx 参数;obj_malloc() 和 zs_page_migrate() 分别在分配和迁移路径上向 obj 编码 class_idx。该特性由 ZS_OBJ_CLASS_IDX 宏控制,仅在 BITS_PER_LONG >= 64 时启用,32 位系统位宽不足自动回退到原有布局。

(二)消除 pool->lock:在 zs_free() 中实现无锁 class 查找

有了 obj 编码中硬编码的 class_idx 之后,zs_free() 无需再遍历 handle->obj->PFN->zpdesc->zspage->class 链,也无需持有 pool->lock。如作者所述:"64-bit aligned reads are atomic, so a lockless read of the handle always yields a valid class_idx"。具体流程:首先无锁读取 handle 得到 obj 值,通过 obj_to_class_idx() 提取 class_idx 并定位到正确 size_class,随后获取 class->lock。在 class->lock 保护下,再次读取 handle 以获取稳定的 PFN——"migration also acquires class->lock, so the obj value is now stable and the PFN is valid"——因为页面迁移同样要获取同一 class->lock,在锁内读取的 PFN 必然有效。由此消除了 zs_free() 与页面迁移/压缩之间的读写锁竞争,彻底避免了多核并发下 read_lock/read_unlock 原子操作对 rwlock 同一缓存行造成的 cacheline bouncing。

该优化仍然由 ZS_OBJ_CLASS_IDX 控制,仅在 64 位系统生效;32 位系统保持原有 pool->lock 路径。

(三)拆分 class->lock 临界区:将 zspage 释放移到锁外

即使消除了 pool->lockclass->lock 本身也因嵌套 zone->lock 而导致竞争传播。作者提道:"freeing pages back to the buddy allocator requires acquiring the zone lock. Under heavy memory pressure, zone lock contention can be severe. When this happens, the CPU holding the class->lock will stall waiting for the zone lock, thereby blocking all other CPUs attempting to acquire the same class->lock."。解决方案是将页面释放动作拆分为 class->lock 内的记账操作和锁外的 buddy allocator 交互。在 class->lock 内,从 zspage 链表移除、扣除 ZS_OBJS_ALLOCATED 计数并标记 zspage_to_free;释放 class->lock 之后才调用 __free_zspage_lockless() 执行实际的 free_zpdesc() 和 buddy 页面归还,pages_allocated 的原子递减也移到锁外完成。这样即使 zone->lock 竞争激烈,持有 class->lock 的 CPU 不会阻塞在内,同一 class 的其他 CPU 得以继续并发释放。此优化对所有架构(包括 32 位)均生效。

收益

测试环境:每进程独立 mmap 256MB,写入数据,madvise MADV_PAGEOUT 换出到 zram(lzo-rle),然后并发 munmap

Raspberry Pi 4B(4 核 ARM64 Cortex-A72)

模式
优化前
优化后
加速比
单进程
59.0ms
56.0ms
1.05x
多进程 2p
94.6ms
66.7ms
1.42x
多进程 4p
202.9ms
110.6ms
1.83x

x86(20 核 Intel i7-12700,16 并发进程)

模式
优化前
优化后
加速比
单进程
11.7ms
9.8ms
1.19x
多进程 2p
24.1ms
17.2ms
1.40x
多进程 4p
63.0ms
45.3ms
1.39x

单进程场景改善有限(仅受益于 class->lock 临界区缩小)。多进程场景加速显著(4 进程达 1.83x),因为消除了 pool->lock 读锁在多核间的缓存行颠簸。该优化与延迟释放方案(Feature 2)可以叠加使用,获得更大的综合收益。Patch 1-2 仅在 64 位系统生效,Patch 3 对所有架构均有收益。


4. readahead 在 EOF 处禁止设置 PG_readahead 标记

系列[PATCH] mm/readahead: no PG_readahead on EOF作者: Frederick Mayle版本: 单 patch

背景

Linux 的预读(readahead)机制通过 PG_readahead 标记来驱动异步预读:当内核读取到一个带有该标记的 page 时,会触发下一轮异步预读。然而,该标记的语义隐含了一个假设——文件后面还有更多数据可供预读。当预读窗口已经覆盖到文件末尾(EOF)时,这个假设不再成立。此外,对于 mmap 文件 IO,PG_readahead 标记还会影响 fault-around 路径的行为:do_fault_around 调用 filemap_map_pages,而 next_uptodate_folio 会跳过所有设置了 PG_readahead 的页面,导致本可以被 fault-around 一并映射的页面被遗漏,产生额外的 minor fault。

解决的问题

  • 预读到达 EOF 后,PG_readahead 标记会触发一次几乎必然是空操作的异步预读,浪费 CPU。
  • 对于 mmap 文件 IO,EOF 页上的 PG_readahead 标记阻止了 fault-around 优化,导致无关的额外 minor fault。

如何做

核心思路是在两个预读入口函数中检测"已读到文件末尾"这个条件,并在该条件下禁止设置 readahead 标记。

在 do_page_cache_ra() 中,原有的逻辑是当 nr_to_read > end_index - index 时将 nr_to_read 截断为 end_index - index + 1。该 patch 在截断的同时将 lookahead_size 设为 0,这样 page_cache_ra_unbounded() 在设置 PG_readahead 标记时使用的 lookahead 窗口为零,即不会在任何 page 上设置该标记。

在 page_cache_ra_order() 中,原有的逻辑将 EOF 作为上限之一来限制预读范围,但 readahead 标记位(mark)仍按正常窗口计算。该 patch 改为:当上限由文件大小决定(而非预读窗口大小决定)时,将 mark 设为 ULONG_MAX,使得 folio_test_readahead() 永远不会被触发。

收益

作者未提供性能数据。从代码逻辑推断:对于顺序读文件的场景,该改动避免了 EOF 处一次无意义的 page_cache_ra_unbounded 调用;对于 mmap 文件 IO,避免了因 PG_readahead 被跳过而导致的额外 minor fault,fault-around 可以将相邻页面一并映射,减少缺页中断次数。


5. 支持 zswap 后端的匿名大页换入

系列[RFC PATCH 0/5] mm: support zswap-backed anonymous large folio swapin作者: fujunjie版本: RFC(5p)

背景

Linux v6.8 之后已经支持匿名大页(anonymous large folio)换出到 zswap:每个 base page 作为独立的 zswap entry 存储。然而换入路径一直是 order-0 的——zswap_load() 直接拒绝 large folio,alloc_swap_folio() 中也有 zswap_never_enabled() 检查,一旦 zswap 曾被启用过,所有匿名大页换入都被迫降级为 4KB。这导致在广泛使用 zswap 的系统中(如 Android、云环境),mTHP 换入的收益完全无法获得。该 RFC 作为第一步,保守地只支持全 zswap 后端或全磁盘后端的大页换入,混合后端仍回退到 order-0。

解决的问题

  • zswap 启用后,匿名大页换入被完全禁用,即使在 zswap 中所有 subpage 都存在的情况下也只能逐个 4KB 换入,失去了大页减少 TLB miss、减少缺页次数的优势。
  • 混合后端(部分在 zswap、部分在磁盘)的大页换入处理复杂,需要先建立正确的 fallback 路径。

如何做

本 RFC 系列围绕四个独立的技术维度实现 zswap 后端的匿名大页换入支持,遵循保守原则——优先覆盖全 zswap 范围,混合后端回退到 order-0,暂不涉及 shmem。

(一)逐页偏移解压:让 zswap_decompress() 支持任意 base page 偏移

zswap 换出时已将大页的每个 base page 存储为独立的 zswap entry,但换入端的 zswap_decompress() 始终写入 folio 的偏移 0 位置,只能服务于 order-0 场景。如作者所述:"That is sufficient while zswap only loads order-0 folios, but large folio swapin needs to fill each base page from its own zswap entry."。本方案为 zswap_decompress() 增加 index 参数,由调用者传入 base page 在大页中的位置索引。在函数内部,kmap_local_folio() 和 sg_set_folio() 的偏移量均基于 index * PAGE_SIZE 计算,确保每个 zswap entry 的解压数据被写入 folio 中对应的 base page 位置。现有调用者(zswap_writeback_entry() 和 order-0 的 zswap_load())传入 index=0,行为不变。

(二)后端一致性检测:用 zswap_entry_batch() 辨识全 zswap / 全磁盘 / 混合范围

大页换入必须知道连续 swap 范围的每个 slot 是否一致地由同一后端支持——"A range that is partly in zswap and partly on disk cannot be read by the existing whole-folio swap_read_folio() backend selection."。新增的 zswap_entry_batch() 函数从起始 swap entry 开始,依次查询每个 slot 的 zswap xarray 是否存在,返回首个异构点之前的连续同构 slot 数量,并可返回起始 slot 的 zswap 存在状态。CONFIG_ZSWAP=n 的 stub 实现报告整个范围都不在 zswap 中。

借助此检测,大页换入路径将 swap 范围区分为三种情形:"the whole range is absent from zswap and should fall through to the disk backend, the whole range is present and can be decompressed one base page at a time, or the range is mixed and must be treated as an invalid large-folio backend selection。"

(三)大页加载与回退:逐页解压 + 插入竞态重试

zswap_load() 的核心逻辑被重写为适用于大页的逐页处理流程。在通过 zswap_entry_batch() 确认整个范围均在 zswap 后,对每个 base page 依次执行:从对应 slot 的 xarray 提取 zswap entry → 调用 zswap_decompress(entry, folio, index) 解压到 folio 的对齐偏移 → 解压失败则整体回退。全部成功后将 folio 标记为 uptodate 和 dirty,并逐页从 xarray 中擦除每个 zswap entry("the swapcache folio becomes authoritative"),同时按 base page 数量统计 ZSWPIN 事件、按大页粒度统计一次 mTHP swpin 计数。

此外,同步换入路径引入 swapin_synchronous_folio() 封装:当大页无法插入 swap cache(插入竞态或后端 recheck 失败),记录 mTHP swpin fallback 后丢弃大页分配,自动重试 order-0 换入——"folio is charged, so NULL means the large folio could not be inserted and needs order-0 fallback"。

(四)策略限制移除:取消 zswap_never_enabled() 的匿名大页禁令

前两步完成后,大页换入的内核已有能力在调用路径早期检测混合范围并安全回退。作者指出:"With mixed zswap ranges rejected and the insertion-race fallback in place, remove the blanket zswap_never_enabled() fallback from the anonymous swapin path so all-zswap and all-disk anonymous ranges can use mTHP swapin."。具体变更包括:在 can_swapin_thp() 中加入 zswap_entry_batch() 调用,与现有的 zeromap 和 swapcache 检测并列为大页可行性判断条件;从 alloc_swap_folio() 中删除 !zswap_never_enabled() 的 fallback 分支;在 swap_cache_add_folio() 插入大页之前,于 swap cluster lock 保护下对后端一致性做最终 recheck——插入前若发现范围变为混合(可能因并发 writeback 将部分 slot 写入磁盘),返回 -EAGAIN 触发回退。shmem 路径保持原有的 zswap fallback 行为,不在本 RFC 范围内。

收益

测试环境:QEMU/KVM,CONFIG_ZSWAP=y, CONFIG_ZRAM=y, CONFIG_MEMCG=y, CONFIG_THP_SWAP=y

测试场景
结果
全 zswap 路径:512MiB 匿名映射,8192 个 64KB group,换出后重新换入
mthp64_zswpout=8192, zswpout=131072;换入后 mthp64_swpin=8192, zswpin=131072;pagemap/kpageflags 显示 8192 个 order-4 THP group
混合后端路径:1024 个 64KB group,1 个 base page 被 writeback 到磁盘
1023 个 order-4 THP group 通过 mTHP 换入,1 个混合 order-0 group;mthp64_swpin=1023, zswpin=16383, zswpwb=1

该 RFC 验证了全 zswap 大页换入的正确性和混合后端的正确回退行为。作者计划在后续工作中探索混合后端的全大页换入支持。


6. userfaultfd 实现 VM 虚拟机内存工作集追踪

系列[PATCH v2 00/14] userfaultfd: working set tracking for VM guest memory作者: Kiryl Shutsemau版本: v2(14p)

背景

虚拟机管理器(Virtual Machine Manager, VMM)管理客户机内存时需要一种高效手段来追踪哪些页面仍被访问(working-set tracking),以便将冷页面(cold pages)安全驱逐到分层存储(tiered storage)或远端存储,并在再次访问时按需取回。现有的 UFFDIO_REGISTER_MODE_WP(Write-Protection)只能拦截写访问,无法追踪读访问,因此无法准确判断完整的 working set。NUMA balancing 使用 PAGE_NONE(protnone)机制实现了访问拦截,userfaultfd 已有 UFFDIO_WRITEPROTECT 基础设施,但缺少将二者结合用于全读写保护的产品级接口。

解决的问题

  • 现有 userfaultfd WP 模式只能捕获写访问,无法追踪读访问,导致 working set 检测不完整
  • 缺乏一个统一的、跨 anon/shmem/hugetlbfs 后端的读写保护追踪原语
  • VMM 需要既能批量异步检测访问模式(低开销),又能在驱逐阶段同步拦截竞争条件(race-free eviction)

如何做

本系列在 userfaultfd 框架内新增 RWP(Read-Write Protection) 模式,核心技术是利用 PAGE_NONE + uffd PTE 位组合做全访问拦截。

架构设计: 复用 NUMA balancing 的 protnone 机制(CONFIG_ARCH_HAS_PTE_PROTNONE),将 PTE 设为 PAGE_NONE 使得 CPU 对任何访问(读/写)都触发 page fault。同时在 PTE 上设置 uffd 位(原本由 VM_UFFD_WP 独占的 _PAGE_UFFD),用于区分 RWP fault 和普通 mprotect(PROT_NONE) 或 NUMA hinting fault。VM_UFFD_WP 和 VM_UFFD_RWP 在同一 VMA 上互斥,因此同一个 PTE bit 在不同 VMA flag 语义下分别表示 WP 或 RWP。

核心接口:

  • **UFFDIO_REGISTER_MODE_RWP**:注册 VMA 进入 RWP 追踪模式,设置 VM_UFFD_RWP VMA flag。通过 CONFIG_USERFAULTFD_RWP(依赖 64BIT + ARCH_HAS_PTE_PROTNONE + HAVE_ARCH_USERFAULTFD_WP)编译控制。
  • **UFFDIO_RWPROTECT**:双向保护 ioctl。设置 UFFDIO_RWPROTECT_MODE_RWP 时调用 change_protection(MM_CP_UFFD_RWP),将已存在的 PTEs 改为 PAGE_NONE 并设 uffd 位;清除 MODE_RWP 时调用 change_protection(MM_CP_UFFD_RWP_RESOLVE) 恢复原始权限。
  • **UFFD_FEATURE_RWP_ASYNC**:异步模式。fault 发生时内核原地自动恢复 PTE 权限(do_uffd_rwp() / do_huge_pmd_uffd_rwp()),faulting 线程不阻塞,不向用户态发送消息。通过 PAGEMAP_SCAN 的 PAGE_IS_ACCESSED 标志(bit 9)反向查询:uffd 位仍置位的页即未被重新访问。
  • **UFFDIO_SET_MODE**:运行时在 sync/async 间切换,无需重新注册 userfaultfd。内部通过 mmap_write_lock() + vma_start_write() drain 所有运行中的 fault handler,然后 WRITE_ONCE(ctx->features, ...) 原子切换模式。切换至 async 时唤醒所有 pendings 的 sync 等待者。

Fault 路径修改:handle_pte_fault() 中,protnone + vma_accessible 的 PTE 先通过 userfaultfd_pte_rwp(vma, pte) 判断是否为 RWP 页,若是则路由到 do_uffd_rwp()。同理 __handle_mm_fault() 的 PMD 级和 hugetlb_fault() 的 hugetlb 级均增加对应的 RWP 分支。

marker 保留: 在 swap out、migration、fork、mremap、UFFDIO_MOVE、hugetlb copy 等 PTE 重写路径中保留 RWP marker(protnone + uffd 位),确保保护状态不被意外丢失。

收益

作者未提供性能数据。从代码逻辑推断的预期收益:

  • VMM 可获得精确的 guest 全访问(读+写)working set 画像,相比仅有写拦截的 WP 模式大幅提升冷热识别准确度
  • async 模式下检测零开销:不产生用户态消息、不阻塞 vCPU 线程,仅通过 PAGEMAP_SCAN 后验统计
  • UFFDIO_SET_MODE 使得 VMM 能在检测期用 async(低开销)、驱逐期用 sync(零竞争窗口),无需重新注册或重建页表,降低了操作延迟
  • 跨 anon/shmem/hugetlbfs 统一语义,VMM 无需为不同后端写三套代码
  • 适用范围:所有 64-bit 架构(x86, arm64, riscv, s390, loongarch, powerpc)只要支持 protnone 和 uffd-wp 即可启用

7. dma-contiguous: 新增 Kconfig 选项为多 NUMA 节点自动设置 CMA

系列[PATCH v5] dma-contiguous: add kconfig option to setup numa cma area if not configured explicitly作者: Feng Tang版本: v5(1p)

背景

在一台多 NUMA 节点的 arm64 服务器上,当 IOMMU 禁用时,dma_alloc_coherent() 始终从 node 0 分配 DMA 内存,即使设备挂载在其他 NUMA 节点。原因是系统只有一个默认的 64MB CMA 区域(Contiguous Memory Allocator),位于 node 0,内核会优先从中分配。而当 IOMMU 启用时,同样的 API 却能获取本地 DMA 内存。Robin Murphy 建议配置 per-NUMA CMA 或禁用 CMA 来解决问题。但对于缺乏内核知识的用户,某些架构(非 x86,x86 默认 CONFIG_CMA_SIZE_MBYTES=0)默认启用 CMA 导致问题静默发生。

解决的问题

  • 多 NUMA 节点系统中,未显式配置 per-numa CMA 时,非 node 0 的设备无法获得本地 DMA 内存,导致 DMA 性能下降
  • 普通用户难以意识到需要手动配置 numa_cma=<node>:size 或 cma_pernuma=<size> 内核参数

如何做

新增 CONFIG_CMA_SIZE_PERNUMA 布尔配置项(依赖 CONFIG_DMA_NUMA_CMA,默认 y)。启动时,在 dma_numa_cma_reserve() 中检查:

  1. 用户是否已通过命令行显式配置——若已配置,走原有逻辑。
  2. 若未显式配置且 dma_contiguous_default_area 存在,则遍历每个 NUMA 节点:如果节点 ID 不等于默认 CMA 所在节点(通过新增的 cma_get_nid() 获取),则为该节点分配与默认 CMA 相同大小的 CMA 区域;如果等于,则直接复用默认 CMA。

新增 cma_get_nid() 辅助函数(mm/cma.c),返回 cma->nid。同时在 __cma_declare_contiguous_nid() 中,当 nid == NUMA_NO_NODE 且 CONFIG_NUMA 启用时,通过 early_pfn_to_nid() 自动推断 CMA 区域的 NUMA 节点归属。

单 NUMA 节点系统行为保持不变(无需额外分配内存)。用户仍可通过 numa_cma=0:0 或 cma_pernuma=0 禁用此功能。

收益

作者未提供性能数据。从代码逻辑推断:多 NUMA 节点系统上,未进行任何 CMA 配置的情况下,设备能自动获得本地节点的 CMA 区域,显著改善 DMA 访问的局部性(latency 和 bandwidth)。用户无需手动配置内核命令行参数即可获得优化的 DMA 布局,降低了部署门槛。


8. 按 NUMA 节点暴露 Balloon 页面统计

系列[PATCH v3] per-node balloon pages exposure作者: Hao Ge版本: v3(1p)

背景

commit 835de37603ef("meminfo: add a per node counter for balloon drivers")新增了 NR_BALLOON_PAGES 统计计数器并将其暴露于 /proc/meminfo。该计数器已按 NUMA 节点追踪,但 /sys/devices/system/node/nodeX/meminfo 中并未同步更新。如作者所述:"Commit 835de37603ef ("meminfo: add a per node counter for balloon drivers") added NR_BALLOON_PAGES and exposed it in /proc/meminfo. However, the per-node view at /sys/devices/system/node/nodeX/meminfo was not updated, even though the counter is already tracked per-node."

解决的问题

  • /sys/devices/system/node/nodeX/meminfo 缺少 Balloon 字段,而 /proc/meminfo 中有
  • 用户无法通过标准 sysfs 接口按 NUMA 节点查看 balloon 内存占用

正如作者指出的,用户本应可以 "see balloon usage per NUMA node without having to parse the raw vmstat file",但当前接口缺少这一能力。

如何做

在 drivers/base/node.c 的 node_read_meminfo() 函数中,新增一行 "Node %d Balloon: %8lu kB\n" 格式字符串及对应的 node_page_state(pgdat, NR_BALLOON_PAGES) 参数。v2 版本将 Balloon 字段放置在 Unaccepted 之后(经 David Hildenbrand 建议),v3 版本在 rebase 到 commit 2232ba9c7931("mm: add gpu active/reclaim per-node stat counters (v2)")后,进一步精确对齐排序:将 Balloon 放在 Unaccepted 和 GPUActive 之间,确保与 /proc/meminfo 的字段顺序完全一致。改动仅 2 行插入。

收益

作者未提供性能数据。纯暴露性改动:将已存在的 per-node NR_BALLOON_PAGES 计数器暴露到 /sys/devices/system/node/nodeX/meminfo,用户无需解析原始 vmstat 文件,即可按 NUMA 节点直接查看 balloon 驱动占用的内存量,使 sysfs 接口与 /proc/meminfo 在字段完整性上保持一致。无性能影响。


9. mm: 将零页设为只读以检测内核非法写入

系列[PATCH] mm: make zeropage read-only作者:Jann Horn版本:v1(1p)

背景

内核的 empty_zero_page 是一个全局共享的 4K 全零页面,用于匿名映射中零填充页的 COW 源,任何对该页内容的修改都属于语义错误。作者这样阐述动机:"Put the zeropage in the read-only data section - nothing should ever change its contents." 该页之前存放在 .bss..page_aligned 段,属于可写数据段。为实现只读放置,需要新引入 .rodata..page_aligned 段,与已有的 .data..page_aligned 和 .bss..page_aligned 段镜像对应,如作者所述:"Set up a new section .rodata..page_aligned to mirror the existing .data..page_aligned and .bss..page_aligned sections."

解决的问题

近年来多次出现同类型安全漏洞:内核通过 GUP 或 splice 以只读语义获取用户空间页面的引用,但随后丢失了对只读语义的追踪并错误地写入了这些页面。作者指出:"There have been several security bugs where the kernel grabs references to pages from some userspace-specified source, via GUP or splice, with read-only semantics; and then later on, the kernel loses track of the pages' read-only semantics and writes into them. I have seen such bugs in out-of-tree GPU drivers before, and recently upstream Linux bugs of this shape have been discovered as well."

这类 bug 难以被 fuzzer 发现,因为内核没有直接机制检测此类语义违反。作者进一步说明:"One problem with these bugs is that fuzzers and such will have a hard time noticing them, because the kernel has no mechanism to directly detect that such a bug has occurred. It would be nice if we had debug infrastructure to keep track of whether file pages are supposed to be writable, or such; but for now, the easiest way to make these bugs detectable in at least some cases is to make sure that writing the 4K zeropage is mapped as read-only in the kernel, so that attempting to write into it immediately crashes (unless the write happens through a vmap mapping or such)."

如何做

  1. 新增段宏:在 include/linux/linkage.h 中定义 #define __page_aligned_rodata __section(".rodata..page_aligned") __aligned(PAGE_SIZE),与已有的 __page_aligned_data.data..page_aligned)和 __page_aligned_bss.bss..page_aligned)并列。
  2. 链接脚本:在 include/asm-generic/vmlinux.lds.h 的 .rodata 输出段开头添加 *(.rodata..page_aligned) 输入段描述,确保页对齐的只读数据在链接时被合并到只读段中。
  3. 变量声明:在 mm/mm_init.c 中将 empty_zero_page 的段属性从 __page_aligned_bss 改为 __page_aligned_rodata

作者提供了验证方法:"I have tested that with this patch applied, calling get_user_pages_fast(address, 1, 0, &page) on a freshly-created anonymous VMA and writing into the page with *(volatile char *)page_address(page) = 0 will cause an oops."

收益

作者未提供运行时性能数据。从代码逻辑推断:

  • 安全收益:将一类跨组件只读语义丢失漏洞的利用后果从"静默数据损坏"提升为"立即内核崩溃(oops)",使 fuzzer(如 syzkaller)能够检测到这类 bug
  • 空间开销:作者提到 "This patch might increase the size of vmlinux by 4K since .rodata is stored in the ELF file while .bss is not; but the compressed kernel image size shouldn't change much, since it's compressed." vmlinux ELF 文件增大 4K(.rodata 存储在 ELF 中而 .bss 不占文件空间),但压缩后内核镜像大小几乎不变
  • 运行时开销:零页访问模式不变(仍通过页表映射),仅页面权限不同,无性能影响

10. 限定零页重映射仅在 underused THP 分裂时生效

系列[PATCH] mm: limit zeropage remap to underused thp split only作者:Nico Pache版本:v1(1p)

背景

commit b1f202060afe("mm: remap unused subpages to shared zeropage when splitting isolated thp")引入了匿名 THP(透明大页)分裂时的零页重映射优化:通过 TTU_USE_SHARED_ZEROPAGE 标志将所有零填充子页重映射到共享零页,回收物理内存。然而该优化被无条件地应用于所有匿名 folio 分裂路径。如作者所述:"This flag is set unconditionally for every anonymous folio split, including splits triggered by KSM."

解决的问题

当 KSM(Kernel Same-page Merging)启用且 THP 设置为 always 时,该无条件行为产生两个严重的性能退化:

  1. use_zero_pages=1 场景:KSM 调用 try_to_merge_one_page() 触发 split_huge_page(),"The split remaps all 512 zero-filled subpages to the shared zeropage at once, freeing the entire 2MB THP when KSM only intended to process a single 4KB page. This bypasses KSM's pages_to_scan rate limiting, causing ~1GB to be freed almost instantly." KSM 仅意在处理单个 4K 页,却引起了整个 2MB THP 的释放,绕过其速率限制。

  2. use_zero_pages=0 场景:通过稳定/不稳定树合并路径,"Each pages_to_scan iteration triggers an expensive split_huge_page() that silently frees 2MB, while the scanner wastes cycles on tree searches for zero-filled pages that were already freed as a side-effect." 每次扫描都触发昂贵的非预期 THP 分裂,同时扫描器还在对已因副作用而释放的零填充页做无意义的树搜索。

如何做

作者的修复策略:"Fix this by restricting TTU_USE_SHARED_ZEROPAGE to only the deferred split shrinker path (deferred_split_scan), which is the only caller that intentionally splits underused THPs to reclaim zero-filled subpages."

具体改动:

  1. 在 __folio_split() 函数签名中新增 bool is_underused_thp 参数,仅当该参数为 true 且 folio 为匿名页时设置 TTU_USE_SHARED_ZEROPAGE 标志。
  2. 新增 folio_split_underused() 专用接口——"Introduce folio_split_underused() as a dedicated entry point that passes is_underused_thp=true through __folio_split()"——专供 deferred split shrinker 调用。
  3. 在 deferred_split_scan() 内部将 split_folio() 替换为 folio_split_underused()
  4. 所有其他分裂调用者(KSM、compaction、madvise 等)通过 folio_split() 和 __split_huge_page_to_list_to_order() 传入 false,不再获得零页重映射副作用。
  5. 仅在 2 个文件中改动,约 13 行插入、6 行删除。

收益

作者在 commit message 中说明了行为变化但未提供格式化性能数据表格。关键收益总结:

场景
修复前
修复后
KSM use_zero_pages=1 + THP=always
~1GB 瞬间释放,绕过 pages_to_scan 限速
KSM 按 pages_to_scan 正常速率推进
KSM use_zero_pages=0
每次迭代触发昂贵的 THP 分裂,CPU 浪费在已释放页上
无 THP 分裂副作用,扫描按预期进行
Underused THP shrinker
正常工作
保持不变(folio_split_underused() 保留 TTU_USE_SHARED_ZEROPAGE
Compaction/madvise 触发的分裂
附带零页重映射副作用
仅执行分裂,无零页重映射

此补丁带有 Fixes: b1f202060afe 标签,属于对上述 commit 引入回归的精确修复,将优化行为从其非预期的调用场景中撤回,同时保留 shrinker 路径的预期回收能力。


11. selftests/cgroup: malloc 返回值检查

系列[PATCH] selftests/cgroup: fix malloc null check in test_memcontrol作者:Hongfu Li版本:v1(1p)

背景

tools/testing/selftests/cgroup/test_memcontrol.c 中的 alloc_anon() 系列函数是 cgroup 内存控制器自测试的基础辅助函数,负责分配匿名内存并按页写入以触发物理页面分配,用于验证 kmem、memsw、swap 等一系列 cgroup 内存控制功能。这些函数通过 malloc() 分配堆内存,但其返回值从未被检查。如作者所述:"The alloc_anon() function calls malloc() without checking for a NULL return."

解决的问题

如若内存分配失败,进程将立即崩溃而非以受控方式报告失败。作者准确指出了后果:"If memory allocation fails, a NULL pointer dereference will occur when accessing the buffer." 当系统内存不足或因 cgroup 限制严格导致 malloc() 返回 NULL 时,后续的 for (ptr = buf; ptr < buf + size; ptr += PAGE_SIZE) *ptr = 0; 写入循环将对 NULL 指针解引用,触发 SIGSEGV。该问题影响全部四个 alloc_anon 变体——alloc_anon()alloc_anon_50M_check()alloc_anon_noexit() 和 alloc_anon_50M_check_swap()。作者在补丁中覆盖全部四个变体:"Add proper error handling to return -1 when malloc() fails in all four alloc_anon variants."

如何做

在四个 alloc_anon 变体函数的 malloc() 调用后统一插入 NULL 检查逻辑:若 malloc() 返回 NULL,则通过 fprintf(stderr, "malloc() failed\n") 向标准错误输出错误信息,并返回 -1。这与 cgroup 自测试框架的错误处理约定一致——返回非零值表示测试失败,由调用方(通常是 cg_run() 宏)负责报告。改动仅 5 行新增代码。

收益

作者未提供性能数据。此改动属于健壮性修复,将不可恢复的 SIGSEGV 转化为可控的测试失败,收益体现在三个层面:

  1. 可诊断性提升:从程序崩溃变为清晰的错误信息输出,开发者能够立即定位根因为内存分配失败,大幅缩短故障排查时间。
  2. 测试稳定性增强:在内存压力场景下(如 CI 环境资源受限),测试不会因偶然的 malloc() 失败而意外退出,避免因环境抖动导致的假阳性结果。
  3. 基础设施完整性:一次性修复核心辅助函数,消除空指针解引用这一未定义行为的入口,避免后续新增测试用例继承该缺陷。

12. mm/shrinker: 修复 set_shrinker_bit() 中的越界读取

系列[PATCH] mm/shrinker: avoid out-of-bounds read in set_shrinker_bit()作者:David Carlier版本:v1(1p)

背景

内核的 shrinker 框架用于在内存压力下回收可回收对象。每个 memcg 维护一个 shrinker_info 结构,其中 unit[] 数组存储每个 shrinker 的 bitmap,map_nr_max 记录当前可见的最大 shrinker 数量。该机制由 commit 307bececcd12("mm: shrinker: add a secondary array for shrinker_info::{map, nr_deferred}")引入。

作者原文指出问题本质:

"set_shrinker_bit() reads info->unit[shrinker_id_to_index(shrinker_id)] before checking shrinker_id against info->map_nr_max, so an id past the currently visible map_nr_max reads past the unit[] array before the WARN_ON_ONCE() catches it. Move the load into the bounded branch."

解决的问题

原始代码中,info->unit[shrinker_id_to_index(shrinker_id)] 的数组访问发生在 WARN_ON_ONCE(shrinker_id >= info->map_nr_max) 边界检查之前。当 shrinker_id 超出 map_nr_max 时,unit[] 数组的索引越界——读取发生在 WARN_ON_ONCE 捕获问题之前。

如何做

该补丁采用最小修复策略——将 unit 的赋值语句移动到边界检查分支内部:

if (!WARN_ON_ONCE(shrinker_id >= info->map_nr_max)) {
structshrinker_info_unit *unit;
    unit = info->unit[shrinker_id_to_index(shrinker_id)];
    smp_mb__before_atomic();
    set_bit(shrinker_id_to_offset(shrinker_id), unit->map);
}

同时将 unit 变量的声明从函数作用域移至分支内部,使其作用域与有效生命周期一致。

收益

作者未提供性能数据。修复消除了越界读取路径,性能影响为零——仅将已存在的 load 指令下移数行,编译器生成的代码路径在正常(非越界)情况下完全不变。在未启用 KASAN 的生产内核中,越界读取可能导致读取到相邻内存区域的任意数据,虽不直接导致崩溃但构成信息泄露隐患。Fixes 标签指向 commit 307bececcd12


13. mm/filemap: 修复 page_cache_prev_miss() 无空洞时的返回值

系列[PATCH] mm/filemap: fix page_cache_prev_miss() when no hole is found作者:Tal Zussman版本:v1(1p)

背景

page_cache_prev_miss() 是文件映射层预读机制的核心辅助函数,用于在 XArray(页缓存基数树)中向后搜索第一个缺失页的索引。其唯一调用者 page_cache_sync_ra() 根据返回值估算顺序读之前的连续缓存范围长度,进而决定预读窗口大小。该函数由 commit 0d3f92966629("page cache: Convert hole search to XArray")引入。

作者原文描述问题:

"page_cache_prev_miss() is documented to return a value outside the searched range when no gap is found. However, the no-gap-found path returns xas.xa_index, which after a successful loop is the first index in the range. As such, that index is misreported as a gap."

对称函数 page_cache_next_miss() 同样的问题已在 commit bbcaee20e03e 中修复,但 prev_miss 此前被遗漏。值得注意的是,该 bug 最初由 Claude Opus 4.7 在审查 mm/filemap.c 时发现。

解决的问题

作者在 commit message 中进一步说明影响:

"The sole caller, page_cache_sync_ra(), uses the return value to estimate the cached run preceding a sequential read. In some cases, the buggy return value can undercount the contiguous range by one, shrinking the readahead window or pushing borderline requests into the small-random-read branch."

具体来说:当搜索范围完整遍历完毕未找到空洞时,返回 xas.xa_index(范围第一个索引)被误报为空洞,导致顺序读连续缓存范围少计 1 页,收缩预读窗口或将临界顺序读请求误判为小随机读分支。

如何做

引入独立递减计数器 nr,保留原始 max_scan 不变。核心改动:

unsignedlong nr = max_scan;
while (nr--) {
void *entry = xas_prev(&xas);
if (!entry || xa_is_value(entry))
return xas.xa_index;
if (xas.xa_index == ULONG_MAX)
return ULONG_MAX;
}
return index - max_scan;

找到空洞时返回 xas.xa_index(空洞所在位置),未找到空洞时返回 index - max_scan(落在搜索范围外,与 page_cache_next_miss 的 index + max_scan 对称)。

收益

作者未提供性能数据。修复纠正了预读判断中的 off-by-one 误报,确保顺序读预读窗口不被错误收缩。hugetlb 已不再使用此函数(commit 9425c591e06a 曾尝试同时修复两个函数但因 hugetlb 性能退化被 revert),因此本次修复不会引入回归风险。Fixes 标签指向 commit 0d3f92966629


14. zram: 修复 zram_writeback_endio 中的 use-after-free

系列[PATCH v2] zram: fix use-after-free in zram_writeback_endio作者:Richard Chang版本:v2(1p)

背景

zram 支持将压缩页面回写(writeback)到后备存储设备以释放内存。commit f405066a1f0d("zram: introduce writeback bio batching")引入了 bio 完成批处理机制:struct zram_wb_ctl 作为回写会话控制结构,包含等待队列 done_wait、完成请求链表 done_reqs 和 inflight 计数器 num_inflight。回写任务通过 wait_event(wb_ctl->done_wait, ...) 等待所有 bio 完成,完成后释放 wb_ctl

解决的问题

作者原文描述了崩溃现象和根本原因:

"A crash was observed in zram_writeback_endio due to a NULL pointer dereference in wake_up. The root cause is a race condition between the bio completion handler (zram_writeback_endio) and the writeback task."

具体复现路径为经典的 TOCTOU 竞态:

Step 1:写回任务分配 wb_ctl,提交多个 bio(每个 bio 引用 wb_ctl 作为 bi_private),然后 wait_event(wb_ctl->done_wait) 睡眠等待。

Step 2:CPU 0 上 bio 完成,回调 zram_writeback_endio() 获取 wb_ctl->done_lock 自旋锁,将请求加入 done_reqs 链表,释放自旋锁。

Step 3(竞态窗口):CPU 0 释放自旋锁后、调用 wake_up(&wb_ctl->done_wait) 之前——CPU 1 上的写回任务被唤醒,处理所有完成请求,num_inflight 降为 0,退出循环,调用 release_wb_ctl() 释放 wb_ctl 结构体。

Step 4(UAF):CPU 0 调用 wake_up(&wb_ctl->done_wait) ——此时 wb_ctl 已被释放,对已释放内存中的 wait_queue_head_t 调用 wake_up() 导致 NULL 指针解引用崩溃。

如何做

采用 RCU 保护 wb_ctl 的完整生命周期:

  • 在 struct zram_wb_ctl 中嵌入 struct rcu_head rcu 字段
  • release_wb_ctl() 改用 kfree_rcu(wb_ctl, rcu) 延迟释放——kfree_rcu() 等待一个 RCU 宽限期结束后才真正释放内存,确保所有 CPU 上的 RCU 读侧临界区均已退出
  • zram_writeback_endio() 中用 rcu_read_lock()/rcu_read_unlock() 包裹对 wb_ctl 的全部访问,使回调函数成为 RCU 读者,与 kfree_rcu 的宽限期同步

该方案由 zram 维护者 Sergey Senozhatsky 和 Minchan Kim 建议,相比 v1(将 wake_up() 移到锁内)不改变锁的持有时间,不引入额外锁竞争。

收益

作者未提供性能数据。该修复消除了 zram writeback 路径上一个确定性的 UAF 崩溃。RCU 开销极小(rcu_read_lock/unlock 在大多数架构上是空操作或编译器屏障),对 bio 完成热路径几乎无性能影响。Fixes 标签指向 commit f405066a1f0d


15. x86/xen: 修复上下文切换中 lazy MMU 模式处理

系列[PATCH] x86/xen: Fix lazy mmu handling across context switch作者:Juergen Gross版本:v1(1p)

背景

Xen PV(半虚拟化)guest 使用 lazy MMU 模式批量处理页表更新:开启 lazy 模式后,页表修改操作只记录在本地队列中,直到显式刷新才批量提交给 hypervisor,能显著减少 hypercall 次数。commit 291b3abed657("x86/xen: use lazy_mmu_state when context-switching")将 lazy MMU 模式跟踪从 per-CPU 开关重构为 per-task 的 struct lazy_mmu_state(包含 enable_count 和 pause_count 计数字段),以支持嵌套和正确跨上下文切换保存/恢复。

解决的问题

作者明确指出了两个问题:

"The recent rework of mmu lazy mode has resulted in problems when running as a Xen PV guest. Enabling lazy mmu mode for the new context during context switch is done from the arch_end_context_switch() hook, but when calling this hook current hasn't been changed yet, so the lazy mmu mode state of the wrong task is modified."

具体来说:xen_end_context_switch() 通过 arch_end_context_switch() 钩子被调用,但此时 current 宏尚未更新(仍指向旧任务)。该函数调用 __task_lazy_mmu_mode_active(next) 检查新任务是否需要 lazy MMU,然后调用 arch_enter_lazy_mmu_mode() 来启用——但 arch_enter_lazy_mmu_mode() 操作的是 current->lazy_mmu_state(旧任务),而非新任务的状态,导致新任务的 lazy MMU 模式未能正确恢复。

"Additionally it is much cleaner to use lazy_mmu_mode_pause() and lazy_mmu_mode_resume() in the Xen context switch hooks, as it avoids conditionals in those hooks."

如何做

通过引入一个新的可指定任务指针的子函数来解决 current 时机问题:

  1. 新增 lazy_mmu_mode_resume_task(struct task_struct *task)(位于 include/linux/pgtable.h):直接操作 task->lazy_mmu_state 而非 current->lazy_mmu_state,使调用者可以在 current 尚未切换时正确恢复下一个任务的 lazy MMU 状态。内部逻辑与原 lazy_mmu_mode_resume() 一致:递减 pause_count,当 pause_count == 0 且 enable_count > 0 时调用 arch_enter_lazy_mmu_mode()

  2. **重构 lazy_mmu_mode_resume()**:保留 in_interrupt() 检查,内部委托给 lazy_mmu_mode_resume_task(current),对外接口和行为不变。

  3. 简化 Xen 钩子

    • xen_start_context_switch():将显式条件判断替换为 lazy_mmu_mode_pause()
    • xen_end_context_switch():将显式条件判断替换为 lazy_mmu_mode_resume_task(next)

收益

作者未提供性能数据。修复后 Xen PV guest 的 lazy MMU 模式在上下文切换过程中能正确恢复,预期可恢复因缺失 lazy 批处理而损失的页表修改性能(避免每次 PTE 更新都触发 hypercall)。代码简化方面:Xen 钩子不再需要直接判断 Xen lazy 模式状态,完全通过通用 API 实现,消除了其中的条件判断。Fixes 标签指向 commit 291b3abed657


16. mm/slub: 在 flush_rcu_sheaves_on_cache() 外围持有 cpus_read_lock

系列[PATCH] mm/slub: hold cpus_read_lock around flush_rcu_sheaves_on_cache()作者:Qing Wang版本:v1(1p)

背景

SLUB 分配器引入了 RCU sheave 机制来优化 kvfree_rcu() 路径:当对象通过 kvfree_rcu() 释放时,它们被缓存在 per-CPU 的 sheave(束)中,批量等待 RCU 宽限期过后再归还给 slab 页。flush_rcu_sheaves_on_cache() 负责冲刷特定 kmem_cache 上所有 CPU 的待处理 sheave——它遍历 for_each_online_cpu() 并为每个 online CPU 调用 queue_work_on() 将冲刷工作排入该 CPU 的工作队列。kvfree_rcu_barrier_on_cache() 是暴露给调用者的屏障接口,确保指定缓存的所有 pending kvfree_rcu 回调完成后再继续。

该函数有两条调用路径:"flush_rcu_sheaves_on_cache() calls queue_work_on() in a for_each_online_cpu() loop, which requires the cpu to stay online. But cpus_read_lock() is not held in kvfree_rcu_barrier_on_cache()." 其中 flush_all_rcu_sheaves() 路径已有锁,kvfree_rcu_barrier_on_cache() 路径缺失。

解决的问题

CPU 热插拔竞态与锁排序冲突:

  1. CPU 热插拔竞态for_each_online_cpu(cpu) 循环中,若无 CPU 热插拔读锁保护,CPU 可能在 for_each_online_cpu 返回后到 queue_work_on() 调用前之间下线,导致 work 被排队到一个不再 online 的 CPU 上。

  2. 锁排序问题阻止了在函数内部加锁:作者解释了为什么不能在 flush_rcu_sheaves_on_cache() 内部加锁:

"Why not move cpus_read_lock() from flush_all_rcu_sheaves() into flush_rcu_sheaves_on_cache()? The reason is it would introduce a new lock order (slab_mutex -> cpu_hotplug_lock). The reverse order (cpu_hotplug_lock -> slab_mutex) is established by cpuhp_setup_state_nocalls(..., slub_cpu_setup, ...) and kmem_cache_destroy(). The two orders together would form an AB-BA deadlock."

如何做

将 CPU 热插拔读锁加在调用者一侧(kvfree_rcu_barrier_on_cache()),而非被调用函数内部,避免了锁排序问题。同时:

"Finally, add lockdep_assert_cpus_held() in flush_rcu_sheaves_on_cache() to catch the same problem in the future."

这属于内核社区的防御性编程最佳实践——将隐式前置条件转化为显式断言,利用 lockdep 基础设施在开发阶段就捕获未来的违规调用。对于 flush_all_rcu_sheaves() 调用路径,lockdep_assert_cpus_held() 仅作为文档性校验(该路径已有锁)。

收益

作者未提供性能数据。cpus_read_lock() 是读-写信号量的读端,多个读者可并发持有,CPU 热插拔操作极为罕见(通常仅在系统启动/关闭或手动触发时),因此对热路径 kvfree_rcu_barrier_on_cache() 的性能影响可忽略不计。修复消除了一个理论上的 CPU 热插拔竞态条件(作者未报告实际触发过的 crash),并通过 lockdep 断言将隐式调用约定固化为可自动验证的运行时约束。


总结

性能优化

#
系列
量化数据
1
DAMON 无锁 PRNG
 [v3, 1p]
kdamond CPU 使用率从 ~72% 降至 ~50%(降低约 30%)
2
zsmalloc per-CPU 延迟释放
 [v3, 4p]
munmap 延迟加速 1.5x–7.17x(zram),1.34x–2.97x(zswap)
3
zsmalloc zs_free() 锁竞争优化
 [v1, 3p]
多进程 munmap 加速 1.39x–1.83x,64 位系统消除 pool->lock 读锁竞争
4
readahead EOF 禁止 PG_readahead
 [v1, 1p]
消除 EOF 处无效异步预读,mmap IO 减少 fault-around 遗漏导致的 minor fault
5
zswap 匿名大页换入
 [RFC, 5p]
全 zswap 路径实现 mTHP swapin(验证 8192 个 64KB group),混合后端正确回退

新机制 / 新接口

#
系列
要点
6
userfaultfd RWP 工作集追踪
 [v2, 14p]
Meta 提交,新增 UFFDIO_REGISTER_MODE_RWP 模式,利用 PAGE_NONE 实现全读写访问拦截,支持 sync/async 运行时切换
7
dma-contiguous NUMA CMA 自动配置
 [v5, 1p]
新增 CONFIG_CMA_SIZE_PERNUMA,多 NUMA 节点系统自动为每个节点分配 CMA 区域
8
per-node Balloon 页面暴露
 [v3, 1p]
/sys/devices/system/node/nodeX/meminfo
 新增 Balloon 字段,与 /proc/meminfo 对齐
9
零页只读安全加固
 [v1, 1p]
将 empty_zero_page 从可写 BSS 移至只读 rodata 段,内核非法写入立即触发 oops

Bug Fix

#
系列
影响
10
限定零页重映射至 underused THP
 [v1, 1p]
修复 KSM+THP=always 场景下约 1GB 被瞬间释放的性能退化,Fixes: b1f202060afe
11
selftests/cgroup malloc 检查
 [v1, 1p]
修复 4 个 alloc_anon 变体中 NULL 指针解引用风险,防止 SIGSEGV
12
shrinker 越界读取修复
 [v1, 1p]
修复 set_shrinker_bit() 中数组访问早于边界检查,Fixes: 307bececcd12
13
page_cache_prev_miss 返回值修复
 [v1, 1p]
修复预读判断中 off-by-one 误报导致的预读窗口收缩,Fixes: 0d3f92966629
14
zram writeback UAF 修复
 [v2, 1p]
RCU 保护 wb_ctl 生命周期消除 bio 完成竞态崩溃,Fixes: f405066a1f0d
15
x86/xen lazy MMU 修复
 [v1, 1p]
修复上下文切换中 current 时机问题导致操作错误任务,Fixes: 291b3abed657
16
slub cpus_read_lock 缺失修复
 [v1, 1p]
修复 kvfree_rcu_barrier_on_cache() 中 CPU 热插拔竞态 + lockdep 断言

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-03 20:45:09 HTTP/2.0 GET : https://f.mffb.com.cn/a/492879.html
  2. 运行时间 : 0.095069s [ 吞吐率:10.52req/s ] 内存消耗:4,642.22kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=222eb858d15de308d4fac7794439a7a6
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000615s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000843s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000344s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000346s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000561s ]
  6. SELECT * FROM `set` [ RunTime:0.002191s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000636s ]
  8. SELECT * FROM `article` WHERE `id` = 492879 LIMIT 1 [ RunTime:0.004260s ]
  9. UPDATE `article` SET `lasttime` = 1783082709 WHERE `id` = 492879 [ RunTime:0.011015s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000337s ]
  11. SELECT * FROM `article` WHERE `id` < 492879 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000512s ]
  12. SELECT * FROM `article` WHERE `id` > 492879 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000886s ]
  13. SELECT * FROM `article` WHERE `id` < 492879 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000824s ]
  14. SELECT * FROM `article` WHERE `id` < 492879 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.001379s ]
  15. SELECT * FROM `article` WHERE `id` < 492879 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.001069s ]
0.096713s