Linux内存管理之内存域(Zones)

本文探讨了硬件对物理地址的限制,特别关注DMA情况下设备访问内存的问题。Linux通过内存域如Zones_DMA和Zones_Normal进行管理。不同平台对DMA的需求取决于硬件配置,内存域布局并非所有架构都统一。

        在通常情况下,硬件会限制能够访问的物理地址范围。

        在有些情况下,设备并不能直接访问所有的物理内存(DMA)。

        在另一些情况下,所需要的物理内存超过了虚拟内存最大可寻址的范围,这个时候就需要一些特殊的操作来访问内存了。Linux根据内存页面在设备中的用途将它们进行分组,这些分组称为域。例如Zones_DMA,包含用于DMA的设备的内存页面;Zones_Normal,包含正常寻址的页面。

        不同的平台对于DMA有不同的需求,内存域的实际布局依赖于硬件,并不是所有架构都有定义域,

<think>我们正在讨论Linux内核的内存直接回收(DirectReclaim)机制。用户的问题非常明确:当memfree(空闲内存)低于min水位线时,如何处罚(触发)内存直接回收,并需要代码过程。根据之前的讨论,我们已经知道:-min水位线是系统保留的最小空闲内存,由vm.min_free_kbytes设置。-当空闲内存低于min时,会触发直接回收(在进程上下文中同步执行)。现在,我们需要深入代码层面,展示从检测到空闲内存低于min水位线到触发直接回收的完整过程。代码路径:1.内存分配路径(pageallocation)是触发直接回收的起点。当内核尝试分配内存页(通过alloc_pages系列函数)时,如果快速分配失败(即当前空闲内存不足),就会进入慢速路径(slowpath),在慢速路径中会触发直接回收。2.慢速路径中,会调用__alloc_pages_slowpath()函数,该函数在满足条件时会触发直接回收。3.直接回收的核心函数是try_to_free_pages(),它通过设置扫描控制结构(structscan_control)并调用do_try_to_free_pages()来执行回收。详细步骤:步骤1:内存分配时检查水位线在快速分配路径中,会通过get_page_from_freelist()函数尝试在各个内存(zone)中分配页面。在该函数中,会调用zone_watermark_fast()或zone_watermark_ok()来检查当前zone的水位线是否满足分配要求。如果不满足,则快速分配失败,进入慢速路径。步骤2:慢速路径中触发直接回收在慢速路径(__alloc_pages_slowpath())中,如果分配标志(gfp_mask)允许直接回收(即设置了__GFP_DIRECT_RECLAIM),则会调用__alloc_pages_direct_reclaim(),进而调用perform_reclaim(),最终调用try_to_free_pages()。步骤3:执行直接回收try_to_free_pages()函数设置扫描控制结构(structscan_control),然后调用do_try_to_free_pages()。该函数会遍历内存,对每个内存进行页面回收(shrink_zones()),然后通过shrink_node()进行节点级别的回收,最终调用shrink_node_memcgs()对每个内存控制组进行回收,并调用shrink_lruvec()扫描LRU链表。步骤4:LRU链表扫描和页面回收在shrink_lruvec()中,内核会按照优先级(priority)扫描LRU链表(包括活动和非活动的匿名页和文件页),将可以回收的页面写回(如果是脏页)或丢弃(如果是干净页),并释放页面。代码示例:我们来看关键函数的代码(基于Linux内核5.x版本):1.快速分配路径中检查水位线(mm/page_alloc.c):```cstaticstructpage*get_page_from_freelist(gfp_tgfp_mask,unsignedintorder,intalloc_flags,conststructalloc_context*ac){...for_each_zone_zonelist_nodemask(zone,z,ac->zonelist,ac->high_zoneidx,ac->nodemask){if(!zone_watermark_fast(zone,order,mark,ac->high_zoneidx,alloc_flags)){...continue;}...//如果水位线检查通过,则尝试分配页面page=rmqueue(ac->preferred_zoneref->zone,zone,order,gfp_mask,alloc_flags,ac->migratetype);if(page){...returnpage;}}returnNULL;}```2.慢速路径中触发直接回收(mm/page_alloc.c):```cstaticinlinestructpage*__alloc_pages_slowpath(gfp_tgfp_mask,unsignedintorder,structalloc_context*ac){...if(gfp_mask&__GFP_DIRECT_RECLAIM){...page=__alloc_pages_direct_reclaim(gfp_mask,order,alloc_flags,ac,&did_some_progress);if(page)gotogot_pg;...}...}```3.直接回收入口(mm/vmscan.c):```cunsignedlongtry_to_free_pages(structzonelist*zonelist,intorder,gfp_tgfp_mask,nodemask_t*nodemask){unsignedlongnr_reclaimed;structscan_controlsc={.nr_to_reclaim=max(SWAP_CLUSTER_MAX,high_wmark_pages(zone)),.gfp_mask=(gfp_mask=memalloc_noio_flags(gfp_mask)),.reclaim_idx=gfp_zone(gfp_mask),.order=order,.priority=DEF_PRIORITY,.may_writepage=!laptop_mode,.may_unmap=1,.may_swap=1,};nr_reclaimed=do_try_to_free_pages(zonelist,&sc);returnnr_reclaimed;}```4.扫描LRU链表(mm/vmscan.c):```cstaticvoidshrink_lruvec(structlruvec*lruvec,structscan_control*sc){...while(nr[LRU_INACTIVE_ANON]||nr[LRU_ACTIVE_FILE]||nr[LRU_INACTIVE_FILE]){unsignedlongnr_anon,nr_file,percentage;unsignedlongnr_scanned;for_each_evictable_lru(lru){if(nr[lru]){nr_to_scan=min(nr[lru],SWAP_CLUSTER_MAX);nr[lru]-=nr_to_scan;nr_reclaimed+=shrink_list(lru,nr_to_scan,lruvec,sc);}}...}...}```在shrink_list()函数中,会根据页面的类型(匿名页或文件页)以及活动状态决定如何回收。例如,对于非活动的文件页(如果是干净的)可以直接释放,脏页需要写回后再释放;对于匿名页,则需要通过交换(swap)来回收。总结触发条件:1.内存分配请求到达,且快速分配路径无法满足(即当前空闲内存低于水位线要求)。2.分配标志允许直接回收(__GFP_DIRECT_RECLAIM)。3.当前空闲内存低于min水位线(在慢速路径中,可能还会检查其他条件,但水位线是根本原因)。注意:min水位线是动态计算的,可以通过/proc/zoneinfo查看每个zone的min、low、high水位线。监控方法:-使用`cat/proc/zoneinfo`查看当前内存的水位线和空闲内存。-使用`dmesg`查看内核日志,当直接回收发生时,可能会有相关日志(取决于内核配置)。调整方法:-调整vm.min_free_kbytes:`sysctl-wvm.min_free_kbytes=65536`(设置为64MB)-调整vm.watermark_scale_factor:影响low和high水位线与min之间的间隔。最后,回答用户的问题:当memfree低于min时,在内存分配过程中,通过上述代码路径触发直接回收。直接回收是同步的,会阻塞当前进程,直到回收足够的内存以满足分配请求(或者回收失败,触发OOM)。</think>### Linux内存直接回收触发条件与代码实现 #### 一、触发核心条件 当系统空闲内存(`memfree`)低于最低水位线(`min`)时,Linux内核会**立即触发直接回收**。这是通过内核的内存水位线机制实现的,其核心逻辑为: $$ \text{触发条件} : \quad \text{free\_pages} < \text{min\_free\_pages} $$ 其中`min_free_pages`由`vm.min_free_kbytes`参数计算得出,默认公式为: $$ \text{min\_free\_kbytes} = \sqrt{\text{total\_memory}} \times 16 \div 4 \quad \text{(单位: KB)} $$ 可通过`sysctl -w vm.min_free_kbytes=65536`手动调整[^3]。 #### 二、代码执行过程 完整触发路径在内存分配失败路径中实现,主要代码位于`mm/page_alloc.c`和`mm/vmscan.c`: 1. **水位线检查**(分配路径) ```c // mm/page_alloc.c static struct page *get_page_from_freelist(...) { // 检查当前zone是否满足水位线要求 if (!zone_watermark_fast(zone, order, mark, ...)) { return NULL; // 快速分配失败 } ... } ``` 当`zone_watermark_ok()`检测到空闲页低于`min`时返回`false`,触发慢速分配路径[^1]。 2. **慢速分配路径入口** ```c // mm/page_alloc.c __alloc_pages_slowpath(gfp_t gfp_mask, ...) { // 检查是否允许直接回收 if (gfp_mask & __GFP_DIRECT_RECLAIM) { // 触发直接回收 page = __alloc_pages_direct_reclaim(...); } ... } ``` 3. **直接回收核心函数** ```c // mm/vmscan.c unsigned long try_to_free_pages(...) { struct scan_control sc = { .nr_to_reclaim = SWAP_CLUSTER_MAX, // 每次回收页数 .gfp_mask = gfp_mask, // 包含__GFP_DIRECT_RECLAIM .priority = DEF_PRIORITY, // 回收优先级 .may_writepage = 1, // 允许写回脏页 .may_swap = 1, // 允许交换匿名页 }; return do_try_to_free_pages(zonelist, &sc); } ``` 4. **LRU页面扫描** ```c // mm/vmscan.c static void shrink_lruvec(...) { // 按优先级扫描LRU链表 for_each_evictable_lru(lru) { // 优先回收非活动页 if (lru == LRU_INACTIVE_ANON || lru == LRU_INACTIVE_FILE) { shrink_list(lru, nr_to_scan, ...); } } } ``` 回收优先级:非活动匿名页 > 非活动文件页 > 活动页[^4]。 #### 三、关键机制说明 1. **同步执行**:直接回收在**进程上下文同步执行**,导致请求进程阻塞 2. **回收目标**: - 文件页:直接丢弃干净页,写回脏页后回收 - 匿名页:通过交换到swap空间回收 3. **终止条件**: - 回收足够页面满足当前分配请求 - 达到`SWAP_CLUSTER_MAX`页数限制 - 扫描完所有LRU链表仍无法回收足够页面 #### 四、验证与监控 1. **查看水位线**: ```bash $ cat /proc/zoneinfo | grep -E "min|low|high|free" ``` 输出示例: ``` min 5 low 6 high 7 free 4 # 当free<min时触发回收 ``` 2. **观察回收行为**: ```c // 测试程序:持续分配内存直到触发回收 #include <stdlib.h> int main() { while(1) malloc(1024*1024); // 每次分配1MB } ``` 运行后通过`dmesg`观察内核日志:"`page allocation failure: direct reclaim`"。 #### 五、特殊场景处理 1. **内存碎片**: ```c // mm/page_alloc.c if (order > 0 && (gfp_mask & __GFP_DIRECT_RECLAIM)) page = __alloc_pages_direct_compact(...); // 先尝试内存压缩 ``` 当大块内存分配失败时,会先尝试内存压缩再触发回收。 2. **OOM Killer**: ```c // mm/oom_kill.c if (!try_to_free_pages(...) && out_of_memory(...)) { oom_kill_process(...); // 回收失败触发OOM } ``` 当直接回收无法释放足够内存时,触发OOM Killer[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值