系列:[PATCH v2] mm/slub: skip freelist construction for whole-slab bulk refill作者: Shengming Hu版本: v2(1 个 patch)
SLUB 分配器在处理批量分配(bulk allocation)时,核心路径 refill_objects() 需要从伙伴系统(buddy allocator)获取新的 slab,然后通过 alloc_from_new_slab() 将对象逐个从 freelist 中弹出并交给调用者。这一流程中存在一个已知的优化机会——代码中曾有一条 TODO 注释明确指出:"possible optimization - if we know we will consume the whole slab we might skip creating the freelist?"。问题在于,当批量分配请求的数量足以消耗整个新 slab 的全部对象时,allocate_slab() 仍然会先构建完整的 freelist(即遍历 slab 中的所有对象,将每个对象的 freepointer 指向下一个对象,形成链表),随后 alloc_from_new_slab() 又立刻将这条链表完全拆解,逐个把对象放入调用者的数组。这个 "先建链表再拆链表" 的过程是纯粹的冗余开销。当启用 CONFIG_SLAB_FREELIST_RANDOM=y 时,freelist 的构建还涉及 Fisher-Yates shuffle 随机化,开销更大。
CONFIG_SLAB_FREELIST_RANDOM=y 配置下,随机化 freelist 的构建开销尤其显著,但批量分配后这些对象仍会被全部取走,随机化并未带来安全收益setup_object() 辅助函数,导致热路径上的函数调用开销增加作者修改了 mm/slub.c(+136/-19 行),核心改动包括:
1. 为 allocate_slab() / new_slab() 添加 build_freelist 参数。 当调用者判断将消耗整个 slab 时,传入 build_freelist=false,跳过 freelist 构建和 shuffle_freelist() 调用。slab 的 freelist 字段被设为 NULL:
static struct slab *allocate_slab(struct kmem_cache *s, gfp_t flags, int node,
bool build_freelist, bool *allow_spinp)
2. 新增 alloc_whole_from_new_slab() 函数。 该函数直接遍历 slab 的物理布局,对每个对象调用 setup_object() 和 maybe_wipe_obj_freeptr() 后放入调用者数组,完全绕过 freelist 链表操作:
while (allocated < slab->objects - 1) {
p[allocated] = object;
maybe_wipe_obj_freeptr(s, object);
allocated++;
object += s->size;
object = setup_object(s, object);
}
3. 处理 CONFIG_SLAB_FREELIST_RANDOM=y 场景。 新增 alloc_whole_from_new_slab_random() 函数,利用现有的 s->random_seq 和 next_freelist_entry() 按随机顺序遍历对象,直接放入数组,但不构建任何链表。
4. 在 refill_objects() 中通过 bulk_refill_consumes_whole_slab() 判断。 该内联函数检查 count >= oo_objects(s->oo),如果剩余待分配数量不小于一整个 slab 的对象数,则走优化路径。
5. 将 setup_object() 标记为 inline。 作者发现优化后编译器不再自动内联该函数,显式标注恢复了预期的代码生成。
作者提供了详细的 benchmark 数据,测试环境为 QEMU (x86_64, 8 SMP, KVM, host CPU),内核 Linux 7.0.0-rc6-next-20260330,使用 slub_bulk_bench 工具。
CONFIG_SLAB_FREELIST_RANDOM=n 时:
obj_size=16, batch=256:5.29 ns/object → 2.42 ns/object,降低 54.4%obj_size=128, batch=32:19.95 ns/object → 5.72 ns/object,降低 71.3%obj_size=256, batch=32:24.31 ns/object → 6.33 ns/object,**降低 74.0%**(最佳)CONFIG_SLAB_FREELIST_RANDOM=y 时:
obj_size=16, batch=256:9.32 ns/object → 3.51 ns/object,降低 62.4%obj_size=256, batch=32:29.80 ns/object → 7.98 ns/object,降低 73.2%obj_size=512, batch=32:30.38 ns/object → 8.01 ns/object,**降低 73.6%**(最佳)总体而言,每对象分配耗时降低约 **54% 至 74%**,大对象(size >= 128)受益更明显,因为 slab 中对象数少、批量分配更容易覆盖整个 slab。
系列:[PATCH v6 0/3] mm: Free contiguous order-0 pages efficiently作者: Muhammad Usama Anjum版本: v6(3 个 patch)
内核中多个路径需要释放一段连续的 order-0 页面:free_contig_range()(用于 CMA 和连续内存分配)、free_contig_frozen_range()(用于 CMA 的 frozen page 释放),以及 vfree()(vmalloc 释放路径)。历史上,这些路径都是逐页调用 __free_page() 或 free_frozen_pages(),每次释放一个 order-0 页面到 pcp(per-cpu pageset)的 order-0 链表。这种逐页释放不仅效率低(每次都需要获取/释放锁),还会导致高阶内存碎片化——原本连续的大块内存被打散到 order-0 pcp 链表中。
触发本系列开发的直接原因是 commit a06157804399("mm/vmalloc: request large order pages from buddy allocator")引入的性能回退。该 commit 让 vmalloc 更积极地从 buddy 分配高阶页面,然后立即 split_page() 为 order-0 页面使用。但释放时仍逐页 __free_page(),导致原本从 buddy 分配的高阶页面无法回到高阶 pcp,必须在 buddy 中重新合并,造成 vmalloc 基准测试出现 11% 至 50% 的显著回退。
free_contig_range() 逐页释放连续 order-0 页面,锁竞争严重、无法利用高阶批量释放vfree() 路径同样逐页释放,导致 vmalloc 高阶分配优化的收益被释放路径抵消__free_contig_frozen_range() 对 CMA frozen page 的释放同样低效本系列由 Ryan Roberts 和 Muhammad Usama Anjum 合作开发,共 3 个 patch,修改了 mm/page_alloc.c、mm/vmalloc.c 和 include/linux/gfp.h。
Patch 1:优化 free_contig_range()。 引入核心函数 __free_contig_range_common(),将连续 PFN 范围分解为最大可能的 power-of-2 对齐块,以高阶方式批量释放。关键设计:
FPI_PREPARED 标志(fpi_t 位标志),当设置时 __free_pages_prepare() 直接返回 true,跳过重复的准备工作put_page_testzero() 和 free_pages_prepare() 进行状态和计数管理free_prepared_contig_range() 中,通过 __ffs(pfn) 计算当前 PFN 的最大对齐阶数,再取 min(order, ilog2(nr_pages), MAX_PAGE_ORDER),以最大块调用 __free_frozen_pages(page, order, FPI_PREPARED)memdesc_section()),跨 section 时需要使用 pfn_to_page() 重新计算 struct page 指针Patch 2:优化 vfree() 路径。 新增 free_pages_bulk() 函数,接受 struct page **page_array 和页面数量,使用 num_pages_contiguous() 检测数组中连续的 PFN 段,对每段调用 __free_contig_range() 批量释放。
Patch 3:优化 __free_contig_frozen_range()。 将原来的逐页 free_frozen_pages() 循环替换为 __free_contig_range_common(),复用 Patch 1 的批量释放基础设施。
free_contig_range() 微基准(arm64 server,100000 次迭代):
vmalloc test_vmalloc 基准(arm64 server):
fix_size_alloc_test: p:512, h:1:改善 **118.09%**(耗时降为不到一半)fix_size_alloc_test: p:256, h:1:改善 89.44%fix_size_alloc_test: p:64, h:1:改善 84.17%random_size_align_alloc_test:改善 76.99%与 v6.18 基线对比(修复回退 + 额外优化):
fix_size_alloc_test: p:512, h:1:改善 49.21%CMA frozen page 释放(debugfs 测试,16384 页/次):
系列:[PATCH v9 0/4] mm/vmalloc: free unused pages on vrealloc() shrink作者: Shivam Kalra版本: v9(4 个 patch)
vrealloc() 是 vmalloc 的重新分配接口,支持扩大和缩小分配。当前实现中,缩小操作仅更新元数据(vm->requested_size 和 KASAN shadow),但不会释放不再使用的物理页面。代码中有一条明确的 TODO 注释标记了这个问题:"TODO: Shrink the vm_area, i.e. unmap and free unused pages." 这意味着如果一个 vmalloc 分配被缩小(例如从 16KB 缩到 4KB),多余的 12KB 物理内存会一直被占用,直到整个分配被 vfree() 释放。
这个问题的实际影响体现在 Rust binder 驱动中。KVVec::shrink_to 会调用 vrealloc() 缩小向量容量来回收内存,但由于底层页面未被释放,内存回收实际上并没有发生。
vrealloc() 缩小时不释放尾部页面,导致物理内存浪费VM_FLUSH_RESET_PERMS、VM_USERMAP、kmemleak 扫描器兼容性本系列共 4 个 patch,修改 mm/vmalloc.c(+135/-19 行)和 lib/test_vmalloc.c(+62 行)。
Patch 1:提取 vm_area_free_pages() helper。 将 vfree() 中的页面释放循环和 NR_VMALLOC 统计更新抽取为独立函数,操作范围为 [start_idx, end_idx),支持部分释放。释放后将 vm->pages[i] 置为 NULL 防止悬挂指针。
Patch 2:修正 grow-in-place 检查。 将 vrealloc() 中的 in-place 判断从 size <= alloced_size(基于虚拟区域大小)改为 size <= (size_t)vm->nr_pages << PAGE_SHIFT(基于实际物理页面数)。
Patch 3:实现缩小时释放尾部页面(核心 patch)。 当 vrealloc() 缩小且跨越页面边界时(new_nr_pages < vm->nr_pages),执行以下步骤:
vm_area_page_order(vm) > 0(huge page)、VM_FLUSH_RESET_PERMS、VM_USERMAPkmemleak_free_part() 通知 kmemleak 缩小后的分配范围vunmap_range() 解除尾部页面的虚拟地址映射busy.lock 锁,更新 vm->nr_pages = new_nr_pagesvm_area_free_pages(vm, new_nr_pages, old_nr_pages) 释放物理页面Patch 4:添加测试用例。 在 lib/test_vmalloc.c 中新增 vrealloc_test,覆盖 grow、shrink 跨页面边界、shrink 同页内、grow in-place 四个场景。
作者未提供性能 benchmark 数据。从代码逻辑推断:缩小操作跨页面边界时,尾部页面会被立即释放回 page allocator,减少内存浪费。Rust binder 驱动的 KVVec::shrink_to 调用将真正回收内存,改善 binder 的内存使用效率。
系列:[PATCH] mm/vmscan: add tracepoint for changes in min_seq and max_seq作者: wangzhen版本: v1(1 个 patch)
MGLRU(Multi-Gen LRU)是内核中的多代 LRU 页面回收框架,其核心概念是通过 generation(代)来划分页面的冷热程度。每个 lruvec 维护两个关键序列号:min_seq 表示最老的一代(即将被回收的页面所在代),max_seq 表示最新的一代(最近被访问的页面所在代)。当内存压力出现时,kswapd 或直接回收路径会递增 min_seq 来淘汰最老一代的页面,同时递增 max_seq 来创建新的一代。
目前,开发者调试 MGLRU 代际变化时只能依赖 debugfs 接口(/sys/kernel/debug/lru_gen),该接口仅提供某一时刻的快照,无法追踪实时变化过程。正如作者所述:"The granularity of debugfs is too coarse when we debug the generations of mglru. there's no way to trace the increase in min_seq and max_seq."
ftrace、perf、bpftrace 等标准 Linux tracing 工具链集成Patch 在 include/trace/events/vmscan.h 中新增了两个 tracepoint,均置于 #ifdef CONFIG_LRU_GEN 保护下:
**mm_vmscan_lru_inc_min_seq**:记录 min_seq 递增事件,输出字段包括 memcg_id(内存控制组 ID)、type(LRU 类型,0 = anon,1 = file)、swappiness 和递增后的 min_seq 值。
**mm_vmscan_lru_inc_max_seq**:记录 max_seq 递增事件,输出字段包括 memcg_id、swappiness 和递增后的 max_seq 值(max_seq 对 anon 和 file 共享,不需要 type 字段)。
在 mm/vmscan.c 中,tracepoint 被插入到三个关键位置:inc_min_seq() 中 WRITE_ONCE() 之后、try_to_inc_min_seq() 中 WRITE_ONCE() 之后、inc_max_seq() 中 smp_store_release() 之后。
作者提供了 tracepoint 的输出示例,展示了 kswapd0 在多个 memcg 上交替执行 min_seq 和 max_seq 递增的完整时序。这种精细的时序信息在 debugfs 快照中是无法获得的。开发者可以使用 trace-cmd、perf record -e mm_vmscan_lru_inc_min_seq 或 bpftrace 等工具实时分析 MGLRU 的代际演进行为,对诊断内存回收延迟、分析不同 memcg 间的回收公平性有直接价值。
系列:[PATCH v2] mm/memory hotplug/unplug: Optimize zone contiguous check when changing pfn range作者: Yuan Liu版本: v2(1 个 patch)
每个 zone 维护一个 contiguous 布尔标志,用于标识该 zone 内的所有 PFN 是否都有有效的 memmap 映射。当 zone->contiguous 为 true 时,pageblock_pfn_to_page() 函数可以直接调用 pfn_to_page() 而无需逐个检查 pfn_valid(),这是 compaction(内存规整)等热路径的关键优化。
当前实现中,set_zone_contiguous() 函数通过遍历 zone 内的每一个 pageblock 来验证连续性。在内存热插拔(memory hotplug)场景中,每次调用 move_pfn_range_to_zone() 或 remove_pfn_range_from_zone() 都会先 clear_zone_contiguous() 再重新扫描整个 zone。对于大内存系统(如 256GB 或 512GB 的虚拟机热插拔),这种全量扫描代价极其高昂。
set_zone_contiguous() 的 O(zone_size) 全量扫描导致极长延迟(256GB 热插需要 10 秒,512GB 需要 36 秒)Patch 引入了增量式连续性追踪机制:
1. 新增 zone 成员 pages_with_online_memmap。 在 struct zone 中新增 unsigned long pages_with_online_memmap 字段,精确追踪 zone span 内拥有已初始化 memmap 的页面数量。
2. 重写 set_zone_contiguous() 为 O(1) 判断:
staticinlinevoidset_zone_contiguous(struct zone *zone)
{
if (zone->spanned_pages == zone->pages_with_online_memmap)
zone->contiguous = true;
}
当 spanned_pages 等于 pages_with_online_memmap 时,说明 zone span 内没有空洞。
3. 增量维护计数器。 在 memmap_init_zone_range()(启动阶段)和 adjust_present_page_count()(热插拔路径)中同步更新计数器。计数器可能出现 undercount,但这是安全的——最坏情况只是回退到逐 pageblock 检查的旧路径。
测试环境为 Intel Icelake 服务器,QEMU + virtio-mem:
| 70% | ||||
| 81% | ||||
| 64% | ||||
| 75% |
系列:[PATCH] mm/vmstat: spread vmstat_update requeue across the stat interval作者: Breno Leitao版本: v1(1 个 patch)
内核使用 per-CPU 的 vmstat_work(DEFERRABLE_WORK)定期将 per-CPU 的 vmstat 差异同步回全局计数器。当前代码使用 round_jiffies_relative(sysctl_stat_interval) 来计算下次触发时间。round_jiffies_relative() 会将所有 CPU 的定时器对齐到同一个秒边界,导致所有 CPU 同时触发 vmstat_update() → refresh_cpu_vm_stats() → decay_pcp_high() → free_pcppages_bulk(),所有调用都需要获取 zone->lock 自旋锁,形成典型的惊群效应(thundering herd)。
vmstat_update 定时器因秒对齐同时触发,导致 zone->lock 严重争用free_pcppages_bulk() 路径的锁竞争显著影响系统性能引入新的 vmstat_spread_delay() 函数,将时间窗口均匀分配给所有在线 CPU:
staticunsignedlongvmstat_spread_delay(void)
{
unsignedlong interval = sysctl_stat_interval;
unsignedint nr_cpus = num_online_cpus();
if (nr_cpus <= 1)
return round_jiffies_relative(interval);
return interval + (interval * (smp_processor_id() % nr_cpus)) / nr_cpus;
}
每个 CPU 根据其 smp_processor_id() 获得唯一的偏移量,触发时间均匀分布在一个 interval 内。设计要点:不增加定时器中断次数;使用 DEFERRABLE_WORK 不会唤醒 idle CPU;单 CPU 场景退回原始行为。
测试环境为 72-CPU aarch64 系统,使用 stress-ng --vm + perf lock contention:
开启 KASAN:
竞争次数下降 7.5 倍,总等待时间减少约 **60%**。
未开启 KASAN:
竞争次数减少约 **45%**,总等待时间减少约 **28%**。
系列:[PATCH] mm/vmpressure: skip socket pressure for costly order reclaim作者: JP Kobryn (Meta)版本: v1(1 个 patch)
vmpressure 机制通过 scanned 与 reclaimed 的比值判断内存回收压力等级。当压力超过 VMPRESSURE_LOW 时,内核会触发 TCP 内存压力控制,导致网络栈减少 socket 缓冲区大小,降低 TCP 吞吐量。
问题在于:当 kswapd 因内存碎片化在高阶分配请求下进行回收时,会扫描大量页面但实际回收很少——因为页面正在被活跃使用,系统并不缺内存。这种"扫描多、回收少"被 vmpressure 误判为回收效率低下。作者指出:"kswapd scans many pages but finds little to reclaim - the pages are actively in use and don't need to be freed."
1. 接口变更:vmpressure() 新增 int order 参数。
2. 核心过滤逻辑: 原来的 if (level > VMPRESSURE_LOW) 被修改为:
if (level > VMPRESSURE_LOW && order <= PAGE_ALLOC_COSTLY_ORDER)
PAGE_ALLOC_COSTLY_ORDER 定义为 3,是内核中既有的分界线——超过此 order 的分配由 compaction 接管,不应触发 socket 压力。
3. 保持 memcg 和 priority 路径不受影响:vmpressure_prio() 传入 order 0,确保当回收优先级降到极低时 critical 信号不会被误拦截。
作者未提供性能数据。从代码逻辑推断,此改动可避免运行网络密集型应用(如数据库、分布式存储)的服务器在 THP 高阶回收时 TCP 吞吐量被错误抑制。修改范围仅涉及 3 个文件共 24 行变动,与现有 PAGE_ALLOC_COSTLY_ORDER 语义一致。
系列:[PATCH 0/4] mm: improve write performance with RWF_DONTCACHE作者: Jeff Layton版本: v1(4 个 patch)
RWF_DONTCACHE(内核内部对应 IOCB_DONTCACHE 标志)是近期加入内核的一种 I/O 模式,介于传统 buffered I/O 和 direct I/O 之间:数据仍经过 page cache,但写入后会被标记为可优先驱逐(dropbehind),从而避免污染 page cache。nfsd 最近新增了 debugfs 控制,允许为读写分别选择 buffered、dontcache 或 direct 模式。
然而,作者在用 fio 做基准测试时发现,RWF_DONTCACHE 在写密集负载下表现极差。根本原因在于 generic_write_sync() 的实现:每次 IOCB_DONTCACHE 写入完成后都调用 filemap_flush_range() 并设置 nr_to_write=LONG_MAX,即刷新该范围内所有脏页。在并发写入场景下,这导致多个 writer 在 writeback 提交路径上严重串行化。
PAGECACHE_TAG_WRITEBACK 清除时所有等待的 writer 同时发起 flush 造成惊群效应,reader p99.9 延迟飙升 83 倍Patch 1:引入 filemap_dontcache_writeback_range()。 替代 filemap_flush_range(),实现两层限流:
mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK) 检查是否已有 writeback 在进行,如果是则跳过本次 flushnr_to_write 限制为本次写入的页数(nr_written / PAGE_SIZE),而非 LONG_MAXintfilemap_dontcache_writeback_range(struct address_space *mapping,
loff_t start, loff_t end, ssize_t nr_written)
{
if (mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK))
return0;
nr = (nr_written + PAGE_SIZE - 1) >> PAGE_SHIFT;
return filemap_writeback(mapping, start, end, WB_SYNC_NONE, &nr,
WB_REASON_BACKGROUND);
}
Patch 2:增加原子 flush 守卫和 dirty pressure 逃逸阀。 新增 AS_DONTCACHE_FLUSHING 标志位,使用 test_and_set_bit() 确保同一时刻最多只有一个 dontcache writer 在执行 flush。还增加 dirty pressure 逃逸机制:当全局脏页数超过 dirty_ratio 阈值的 75% 时,绕过 skip-if-busy 强制 flush,防止触发 balance_dirty_pages() 限流。
作者提供了 fio benchmark 数据。补丁前的 IOCB_DONTCACHE 写入吞吐量仅为 buffered I/O 的约 47%(单客户端 575 MB/s vs 1442 MB/s),并出现 multi-second tail latency。作者指出 skip-if-busy 单独使用时 "reader p99.9 spikes 83x",比例限制单独使用时 "still serializes on xarray locks",因此两种机制缺一不可。Patch 3-4 提供了可复现的 fio benchmark 套件。
系列:[PATCH v2] mm/mempolicy: fix memory leaks in weighted_interleave_auto_store()作者: Jackie Liu版本: v2(1 个 patch)
Weighted interleave(加权交错)是内核 NUMA 内存策略的一种分配模式,v6.16 引入了自动调优(auto-tuning)功能(commit e341f9c3c841)。用户通过 sysfs 接口写入 true 或 false 来切换自动/手动模式,对应函数为 weighted_interleave_auto_store()。该函数在进入时分配一个新的 wi_state 结构体,然后根据用户输入执行不同逻辑路径,但存在两处内存泄漏。
return count,但没有 kfree(new_wi_state)old_wi_state 的获取,rcu_assign_pointer() 覆盖了旧指针而清理路径因 old_wi_state == NULL 不执行。如作者所述:"A user can trigger this repeatedly by writing '1' in a loop."将 old_wi_state 的获取从条件分支内移到 mutex_lock 之后、条件判断之前,成为无条件执行的操作:
mutex_lock(&wi_state_lock);
old_wi_state = rcu_dereference_protected(wi_state,
lockdep_is_held(&wi_state_lock));
if (old_wi_state && input == old_wi_state->mode_auto) {
mutex_unlock(&wi_state_lock);
kfree(new_wi_state);
return count;
}
统一 early return 路径确保 new_wi_state 被释放;确保旧状态被正确获取使清理路径能正常执行。
纯正确性修复。消除两处内存泄漏,防止长时间运行的 NUMA 系统因反复切换 weighted interleave 模式导致内存缓慢增长。Patch 标记 Cc: stable@vger.kernel.org # v6.16+,将被回移到稳定分支。
系列:[PATCH v2] LoongArch: mm: fix FLATMEM ARCH_PFN_OFFSET for non-zero DRAM base作者: Ethan Yang版本: v2(1 个 patch)
在 CONFIG_FLATMEM(平坦内存模型)下,内核通过 pfn_to_page() 将 PFN 转换为 struct page 指针,计算公式为 mem_map + (pfn - ARCH_PFN_OFFSET)。LoongArch 当前将 ARCH_PFN_OFFSET 定义为 PFN_UP(PHYS_OFFSET),而 PHYS_OFFSET 默认为 0。如果系统的 DRAM 不从物理地址 0 开始,ARCH_PFN_OFFSET 就会小于实际 RAM base 的 PFN,导致 pfn_to_page() 计算出错误的指针。
ARCH_PFN_OFFSET 小于实际 DRAM base PFN 时,pfn_to_page() 计算出的指针偏移了 mem_map 的预期起始位置memmap_init_range() 初始化 struct page 数组时写入错误的内存区域,导致早期启动失败修复分两部分,涉及 3 个文件共 5 行改动:
arch/loongarch/include/asm/page.h**:将 ARCH_PFN_OFFSET 从编译期常量 PFN_UP(PHYS_OFFSET) 改为运行时变量 min_low_pfnarch/loongarch/kernel/mem.c**:在 memblock_init() 中设置 min_low_pfn = PFN_UP(memblock_start_of_DRAM())arch/loongarch/kernel/setup.c**:在处理 mem=xxx 命令行参数后重新计算 min_low_pfn关键的启动正确性修复。没有此修复,DRAM base 非零的 LoongArch FLATMEM 系统根本无法启动。仅影响 CONFIG_FLATMEM 配置,SPARSEMEM 不受影响。
性能优化
| mm/slub: skip freelist construction | ||
| mm: Free contiguous order-0 pages | ||
| mm/memory hotplug: O(1) zone contiguous check | ||
| mm/vmstat: spread vmstat_update | ||
| mm: RWF_DONTCACHE write improvement |
新机制 / 新接口
| mm/vmalloc: vrealloc() shrink | ||
| mm/vmscan: MGLRU tracepoint | ||
| mm/vmpressure: skip socket pressure |
Bug Fix
| mm/mempolicy: fix memory leaks | ||
| LoongArch: fix FLATMEM ARCH_PFN_OFFSET |