如何安全遍历Linux内核所有struct page结构而不触发崩溃
崩溃原因
你当前的实现存在两个核心问题,直接导致内核崩溃:
- 暴力遍历虚拟地址的逻辑完全错误,内核的
struct page是集中存储的数组结构,只有特定范围内的地址属于合法的struct page实例,你遍历到边界外的未映射地址自然触发缺页异常。 - 硬编码每次偏移64字节不具备兼容性,
struct page的大小受内核配置(如DEBUG_PAGEALLOC、MEMCG、TRANSPARENT_HUGEPAGE等)影响,不是固定值。
正确遍历方案
内核原生提供了标准的遍历接口,无需手动计算地址,兼容所有配置场景,不会触发非法访问:
最简实现(5.4及以上内核支持)
#include <linux/mm.h> #include <linux/mmzone.h> #define PDEBUG(format, args...) printk(KERN_DEBUG "MyKernelModule: " format, ##args) void traverse_all_pages(void) { struct page *page; for_each_present_page(page) { PDEBUG("Page struct addr: %px, virtual addr: %px, zone: %px\n", page, page_address(page), page_zone(page)); } }
兼容旧内核的底层实现
如果内核版本没有提供for_each_present_page宏,可以用逐层遍历节点、内存域、页帧的方式实现:
#include <linux/mm.h> #include <linux/mmzone.h> #include <linux/pfn.h> #define PDEBUG(format, args...) printk(KERN_DEBUG "MyKernelModule: " format, ##args) void traverse_all_pages_compat(void) { pg_data_t *pgdat; struct zone *zone; unsigned long pfn, start_pfn, end_pfn; struct page *page; /* 遍历所有在线NUMA节点 */ for_each_online_pgdat(pgdat) { /* 遍历节点下所有内存域 */ for (zone = pgdat->node_zones; zone < pgdat->node_zones + MAX_NR_ZONES; zone++) { if (!populated_zone(zone)) continue; start_pfn = zone->zone_start_pfn; end_pfn = start_pfn + zone->spanned_pages; /* 遍历内存域内所有合法页帧号 */ for (pfn = start_pfn; pfn < end_pfn; pfn++) { if (!pfn_valid(pfn)) continue; page = pfn_to_page(pfn); PDEBUG("Page struct addr: %px, pfn: %lu, virtual addr: %px\n", page, pfn, page_address(page)); } } } }
注意事项
- 遍历过程建议持有
rcu_read_lock避免并发修改导致的异常,仅读取页面属性不需要加重锁 page_address仅对低端内存、已建立映射的高端内存页面有效,未映射的高端内存页面会返回NULL,不要对返回值直接解引用- 不要修改未持有引用计数的页面内容,会引发系统数据异常甚至崩溃
内容的提问来源于stack exchange,提问作者anon
相关产品推荐
相关产品推荐

