shrink_page_list(

本文深入探讨了Linux内核中的内存页回收算法,详细解释了如何遍历和处理待回收的页面列表,包括脏页处理、映射取消、写回操作及页面释放等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

static unsigned longshrink_page_list(struct list_head *page_list, struct zone *zone,

                                  struct scan_control *sc,enum ttu_flagsttu_flags,

                                  unsigned long *ret_nr_dirty,unsigned long*ret_nr_unqueued_dirty,

                                  unsigned long *ret_nr_congested,unsignedlong *ret_nr_writeback,

                                  unsigned long *ret_nr_immediate,boolforce_reclaim)

{

       LIST_HEAD(ret_pages);

       LIST_HEAD(free_pages);

       intpgactivate = 0;

       unsignedlong nr_unqueued_dirty = 0;

       unsignedlong nr_dirty = 0;

       unsignedlong nr_congested = 0;

       unsignedlong nr_reclaimed = 0;

       unsignedlong nr_writeback = 0;

       unsignedlong nr_immediate = 0;

 

       cond_resched();

 

       while (!list_empty(page_list)) {

              structaddress_space *mapping;

              structpage *page;

              intmay_enter_fs;

              enumpage_references references = PAGEREF_RECLAIM_CLEAN;

              booldirty, writeback;

              boollazyfree = false;

              intret = SWAP_SUCCESS;

 

              cond_resched();

 

              page= lru_to_page(page_list);

              list_del(&page->lru);

 

              if (!trylock_page(page))

                     gotokeep;

 

              VM_BUG_ON_PAGE(PageActive(page),page);

              VM_BUG_ON_PAGE(page_zone(page)!= zone, page);

 

              sc->nr_scanned++;

 

              if(unlikely(!page_evictable(page)))

                     gotocull_mlocked;

 

              if (!sc->may_unmap &&page_mapped(page))

                     gotokeep_locked;

 

              /*Double the slab pressure for mapped and swapcache pages */

              if (page_mapped(page) ||PageSwapCache(page))

                     sc->nr_scanned++;

 

              may_enter_fs= (sc->gfp_mask & __GFP_FS) || (PageSwapCache(page) &&

(sc->gfp_mask & __GFP_IO));

 

              page_check_dirty_writeback(page,&dirty, &writeback);

              if(dirty || writeback)

                     nr_dirty++;

 

              if(dirty && !writeback)

                     nr_unqueued_dirty++;

 

              mapping = page_mapping(page);

              if(((dirty || writeback) && mapping &&inode_write_congested(mapping->host)) ||

                            (writeback&& PageReclaim(page)))

                     nr_congested++;

 

              if(PageWriteback(page)) {

                     /*Case 1 above */

                     if (current_is_kswapd() &&PageReclaim(page) &&

 test_bit(ZONE_WRITEBACK, &zone->flags)) {

                            nr_immediate++;

                            gotokeep_locked;

 

                     /*Case 2 above */

                     }else if (sane_reclaim(sc) || !PageReclaim(page) || !may_enter_fs) {

                            SetPageReclaim(page);

                            nr_writeback++;

                            gotokeep_locked;

 

                     /*Case 3 above */

                     } else {

                            unlock_page(page);

                            wait_on_page_writeback(page);

                            /*then go back and try same page again */

                            list_add_tail(&page->lru,page_list);

                            continue;

                     }

              }

 

              if(!force_reclaim)

                     references= page_check_references(page, sc);

 

              switch(references) {

              case PAGEREF_ACTIVATE:

                     gotoactivate_locked;

              case PAGEREF_KEEP:

                     gotokeep_locked;

              case PAGEREF_RECLAIM:

              case PAGEREF_RECLAIM_CLEAN:

                     ;/* try to reclaim the page below */

              }

 

              if (PageAnon(page) &&!PageSwapCache(page)) {

                     if(!(sc->gfp_mask & __GFP_IO))

                            gotokeep_locked;

                     if(!add_to_swap(page, page_list))

                            gotoactivate_locked;

                     lazyfree= true;

                     may_enter_fs= 1;

 

                     /*Adding to swap updated mapping */

                     mapping= page_mapping(page);

              }

 

              if (page_mapped(page) && mapping) {

                     switch (ret = try_to_unmap(page, lazyfree ?

(ttu_flags |TTU_BATCH_FLUSH | TTU_LZFREE) :

(ttu_flags |TTU_BATCH_FLUSH))) {

                     case SWAP_FAIL:

                            gotoactivate_locked;

                     case SWAP_AGAIN:

                            gotokeep_locked;

                     case SWAP_MLOCK:

                            gotocull_mlocked;

                     case SWAP_LZFREE:

                            gotolazyfree;

                     case SWAP_SUCCESS:

                            ;/* try to free the page below */

                     }

              }

 

              if (PageDirty(page)) {

                     if(page_is_file_cache(page) && (!current_is_kswapd() || !test_bit(ZONE_DIRTY,

                     &zone->flags))){

                            inc_zone_page_state(page,NR_VMSCAN_IMMEDIATE);

                            SetPageReclaim(page);

                            gotokeep_locked;

                     }

 

                     if(references == PAGEREF_RECLAIM_CLEAN)

                            gotokeep_locked;

                     if(!may_enter_fs)

                            gotokeep_locked;

                     if(!sc->may_writepage)

                            gotokeep_locked;

 

                     try_to_unmap_flush_dirty();

                     switch(pageout(page, mapping, sc)) {

                     casePAGE_KEEP:

                            gotokeep_locked;

                     casePAGE_ACTIVATE:

                            gotoactivate_locked;

                     casePAGE_SUCCESS:

                            if(PageWriteback(page))

                                   gotokeep;

                            if(PageDirty(page))

                                   gotokeep;

 

                            if(!trylock_page(page))

                                   gotokeep;

                            if(PageDirty(page) || PageWriteback(page))

                                   gotokeep_locked;

                            mapping= page_mapping(page);

                     casePAGE_CLEAN:

                            ;/* try to free the page below */

                     }

              }

 

              if (page_has_private(page)) {

                     if(!try_to_release_page(page, sc->gfp_mask))

                            gotoactivate_locked;

                     if(!mapping && page_count(page) == 1) {

                            unlock_page(page);

                            if(put_page_testzero(page))

                                   gotofree_it;

                            else{

                                   nr_reclaimed++;

                                   continue;

                            }

                     }

              }

 

lazyfree:

              if(!mapping || !__remove_mapping(mapping, page, true))

                     gotokeep_locked;

 

              __ClearPageLocked(page);

free_it:

              if(ret == SWAP_LZFREE)

                     count_vm_event(PGLAZYFREED);

 

              nr_reclaimed++;

 

              list_add(&page->lru,&free_pages);

              continue;

 

cull_mlocked:

              if (PageSwapCache(page))

                     try_to_free_swap(page);

              unlock_page(page);

              list_add(&page->lru,&ret_pages);

              continue;

 

activate_locked:

              if(PageSwapCache(page) && mem_cgroup_swap_full(page))

                     try_to_free_swap(page);

              VM_BUG_ON_PAGE(PageActive(page),page);

              SetPageActive(page);

              pgactivate++;

keep_locked:

              unlock_page(page);

keep:

              list_add(&page->lru,&ret_pages);

              VM_BUG_ON_PAGE(PageLRU(page)|| PageUnevictable(page), page);

       }

 

       mem_cgroup_uncharge_list(&free_pages);

       try_to_unmap_flush();

       free_hot_cold_page_list(&free_pages,true);

 

       list_splice(&ret_pages,page_list);

       count_vm_events(PGACTIVATE,pgactivate);

 

       *ret_nr_dirty+= nr_dirty;

       *ret_nr_congested+= nr_congested;

       *ret_nr_unqueued_dirty+= nr_unqueued_dirty;

       *ret_nr_writeback+= nr_writeback;

       *ret_nr_immediate+= nr_immediate;

       returnnr_reclaimed;

}

<think>我们正在讨论Linux内核内存管理中的`shrink_folio_list`函数。根据提供的引用和上下文,我们可以分析该函数的作用和应用场景。 ### 上下文回顾 - 引用[1]提到在`shrink_inactive_list`中,当`do_demotion_pages`标志设置时,物理页被加入`demotion_folios`链表,然后传递给`demote_folio_list`进行降级(而不是回收)。 - 引用[2]和[3]描述了页面回收的流程:`shrink_node` -> `shrink_lruvec` -> `get_scan_count`确定扫描页数,然后循环扫描活跃链表(通过`shrink_active_list`)和不活跃链表(通过`shrink_inactive_list`)。 - 引用[4]提到kswapd线程被唤醒以释放内存。 ### `shrink_folio_list`的作用 `shrink_folio_list`是Linux内核中内存回收(reclaim)过程的核心函数之一。它负责处理不活跃链表(inactive list)中的folio(内存页的集合),尝试回收这些页面。具体来说,该函数会: 1. **检查页面状态**:确定页面是否可以被回收(例如,是否为脏页、是否被锁定、是否有其他引用等)。 2. **执行回收操作**:根据页面类型和状态,采取不同的回收策略: - 对于文件缓存页(file page),如果页面是干净的(clean),可以直接丢弃(因为可以从磁盘重新读取);如果是脏的(dirty),则需要写回(writeback)到存储设备,然后才能回收。 - 对于匿名页(anonymous page),如果系统配置了交换空间(swap),则将其写入交换空间,然后回收。 3. **处理不可回收的页面**:如果页面不能被回收(例如,正在被使用或锁定),则将其移回活跃链表(active list)或其他适当的位置。 ### 使用场景 `shrink_folio_list`在以下场景中被调用: - **直接内存回收(Direct Reclaim)**:当进程在分配内存时发现内存不足(低于低水位线),会触发直接回收,此时会调用`shrink_folio_list`尝试立即回收页面。 - **kswapd后台回收**:当内存压力持续存在时,kswapd守护进程会被唤醒,定期扫描并回收页面,其中也会调用`shrink_folio_list`。 - **不活跃链表扫描**:在`shrink_inactive_list`函数中,会将不活跃链表中的folio收集起来,然后传递给`shrink_folio_list`进行实际的回收操作。 ### 函数流程(简化) 1. 遍历传入的folio链表。 2. 对每个folio: a. 检查是否被锁定(locked),如果是则跳过。 b. 检查引用计数(reference count),如果还有其他引用,则不能回收。 c. 根据页面类型(匿名页或文件页)和状态(脏/干净)执行相应操作。 d. 如果回收成功,将folio从LRU链表中移除,并添加到空闲链表(free list)中;否则,根据情况放回活跃或不活跃链表。 3. 返回已回收的页面数量。 ### 与引用内容的关联 - 引用[1]中提到的`demotion_folio_list`是一种替代回收的机制(将页面降级而不是回收),这与`shrink_folio_list`的回收路径不同。当启用降级时,`shrink_inactive_list`会跳过回收逻辑,转而使用降级。 - 引用[3]中`shrink_inactive_list`会调用`shrink_folio_list`(或类似函数)来回收不活跃链表中的页面。 ### 总结 `shrink_folio_list`是Linux内核内存回收的关键函数,负责处理不活跃链表中的页面,尝试回收它们以释放内存。它在直接回收和后台回收(kswapd)过程中被调用,处理文件页和匿名页的回收。 §§相关问题§§ 1. 在Linux内核中,页面回收的完整流程是怎样的? 2. 如何配置和优化内存回收机制以避免系统卡顿? 3. 文件页和匿名页在回收过程中有哪些不同的处理方式? 4. 什么是内存降级(demotion)?它和传统的页面回收有什么区别? 5. 如何监控Linux系统的页面回收活动? [^1]: DETECT/DEMOTION:在核心回收函数 shrink_inactive_list 中,遍历链表中的每个物理页。当检测到 do_demotion_pages 标志时,函数不执行内存回收逻辑,而是将这些物理页加入 demotion_folis 链表,因此不会解除映射或清除页表。最后将 demote_folis 传递给 demote_folio_list 进行实际降级。 [^2]: 在 shrink_node 一文主要分析了 shrink_lruvec,并知道在get_scan_count() 中根据扫描平衡条件,确定了anon/file LRU 扫描页数,并最终会shrink_list() 进行深入的回收处理。本文将继续分析页面回收 shrink_lruvec()部分的 shrink_list() 和 shrink_active_list() 流程。 [^3]: shrink_lruvec 中会计算本次要扫描的页数,然后触发实际的扫描和页回收(shrink_list):实现如下: shrink_lruvec(): // 计算要扫描页数 get_scan_count(lruvec, sc, nr); while 未扫描足够的页数且文件页或匿名页数量都不是0: for 2个 active lru list(file and anon): // 如果指定了may_deactivate,则尝试将部分hot 页移至cold 链表中 shrink_active_list(); for 2个 inactive lru list(file and anon):   shrink_inactive_list(); // 如果匿名页的cold页少,尝试deactivate 一些老的hot匿名页。 shrink_active_list(); 3.get_scan_count [^4]: kswap内存回收机制。当⼀个task在分配内存过程中,如果在Low watermark水线之上,从现有伙伴系统中没有分配到所需的内存时,就会通过wakeup_kswapd()函数来唤醒kswapd线程,以释放部分内存来满⾜当前的内存分配需求。</think>### `shrink_folio_list` 函数的作用及在内存管理中的应用 `shrink_folio_list` 是 Linux 内核内存回收机制中的核心函数,主要负责处理**不活跃内存页(inactive folios)的回收过程**。它在内存压力场景下被调用,通过释放物理页框来满足系统内存需求。以下是详细分析: --- #### 一、核心作用 1. **回收不活跃内存页** 遍历给定的不活跃页链表,尝试回收其中的物理内存页。回收策略根据页类型(文件页/匿名页)动态调整: - **文件页(File Page)**:若为**干净页**(未修改),直接释放;若为**脏页**(已修改),触发回写(writeback)后释放[^1]。 - **匿名页(Anonymous Page)**:若系统启用 Swap,将其写入交换空间后释放[^3]。 2. **处理页面状态转换** - 将无法回收的页重新激活(移回活跃链表)。 - 处理被锁定的页或正在回写的页(等待或跳过)。 3. **统计回收效果** 返回成功回收的页数,供上层回收逻辑(如 `shrink_inactive_list`)决策是否继续扫描[^2]。 --- #### 二、调用场景 该函数在以下内存回收路径中被调用: ```mermaid graph LR A[内存不足] --> B[shrink_node] B --> C[shrink_lruvec] C --> D[get_scan_count 计算扫描页数] D --> E[shrink_inactive_list] E --> F[shrink_folio_list] --> G[实际回收] ``` 1. **直接内存回收(Direct Reclaim)** 进程分配内存时触发(如 `alloc_pages` 失败),**同步执行**回收[^4]。 2. **kswapd 后台回收** 当内存水位低于低水位线(Low Watermark)时,由 kswapd 线程**异步触发**,逐步释放内存[^4]。 3. **主动降级(Demotion)场景** 当启用 `do_demotion_pages` 标志时,跳过回收逻辑,将页转移到降级链表(`demotion_folios`)供后续处理[^1]。 --- #### 三、关键流程 ```c // 伪代码逻辑 int shrink_folio_list(list, reclaim_stat) { while (folio in list) { if (folio is locked || under writeback) skip or wait; // 跳过或等待 if (folio is file page) { if (folio is clean) free_folio(); // 直接释放 else schedule_writeback(); // 触发回写 } else if (folio is anonymous page && swap_enabled) { swap_out(folio); // 交换到磁盘 } else { activate_folio(); // 移回活跃链表 } } return reclaimed_pages; // 返回回收页数 } ``` --- #### 四、数学关系与调优 1. **回收效率模型** 设 $P_r$ 为回收成功率,$N_{\text{scan}}$ 为扫描页数,则实际回收页数: $$ N_{\text{reclaimed}} = P_r \times N_{\text{scan}} $$ $P_r$ 取决于工作负载特征(如脏页比例、访问频率)。 2. **调优参数** - **脏页回写阈值**:通过 `vm.dirty_background_ratio` 控制脏页比例,影响回收延迟。 - **交换倾向**:`vm.swappiness`(值域 0-100)决定匿名页与文件页的回收优先级: - 高值:优先回收文件页($P_r$ 增大)。 - 低值:优先回收匿名页(需 Swap 支持)。 --- #### 五、应用实例 1. **数据库系统** 当 InnoDB 缓冲池过大时,文件页被频繁回收,可通过降低 `vm.swappiness` 保护文件缓存[^1]。 2. **容器环境** 内存超卖(Overcommit)场景下,`shrink_folio_list` 的回收效率直接影响容器 OOM 概率。 --- #### 六、监控方法 ```bash # 查看回收统计 grep -e pgscan -e pgsteal /proc/vmstat # 输出示例 pgscan_kswapd 1200 # kswapd 扫描页数 pgsteal_kswapd 800 # kswapd 成功回收页数 pgscan_direct 300 # 直接回收扫描页数 pgsteal_direct 200 # 直接回收成功页数 ``` - **回收效率比**:$\frac{\text{pgsteal}}{\text{pgscan}}$ 应接近 1(过低表明回收效率差)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值