win7网站后台无法编辑,vr网站建设,google推广妙招,网站专题页优化文章说明#xff1a; Linux内核版本#xff1a;5.0 架构#xff1a;ARM64 参考资料及图片来源#xff1a;《奔跑吧Linux内核》 Linux 5.0内核源码注释仓库地址#xff1a; zhangzihengya/LinuxSourceCode_v5.0_study (github.com)
1. 触发页面回收
Linux内核中触发页…文章说明 Linux内核版本5.0 架构ARM64 参考资料及图片来源《奔跑吧Linux内核》 Linux 5.0内核源码注释仓库地址 zhangzihengya/LinuxSourceCode_v5.0_study (github.com)
1. 触发页面回收
Linux内核中触发页面回收的机制大致有3个
直接页面回收机制。在内核态里调用页面分配接口函数alloc_pages()分配物理页面时由于系统内存短缺不能满足分配请求因此内核会直接自陷到页面回收机制尝试回收内存来解决当前的燃眉之急这称为直接页面回收。周期性回收内存机制。这是kswapd内核线程的工作职责。当内核路径调用alloc_pages() 分配物理页面时由于系统内存短缺没法在低水位情况下分配出内存因此会唤醒kswapd内核线程来异步回收内存。slab收割机slab shrinker机制。这是用来回收slab对象的。当内存短缺时直接页 面回收和周期性回收内存两种机制都会调用slab收割机机制来回收slab对象。slab机制分配的内存主要用于slab对象和kmalloc接口也可用于内核空间的内存分配。
注意
直接回收内存的进程主体是调用者本身。直接回收内存是同步回收这会阻塞调用者进程的执行。kswapd本身是内核线程它和调用者的关系是异步的。如test进程尝试调用alloc_pages()来分配内存当发现在低水位情况下无法分配出内存时它唤醒kswapd内核线程。这时kswapd 内核线程就开始执行页面回收工作了。test进程会继续尝试其他办法来分配内存如调用直接回收内存机制。
页面回收机制的主要调用路径如下图所示 下面将根据源码围绕这张图中的关键部分进行讲解。
2. kswapd内核线程
kswapd是Linux内核中一个非常重要的内核线程它负责在内存不足的情况下回收页面。kswapd内核线程初始化时会为系统中每个NUMA内存节点创建一个名为“kswapdd”的内核线程。
触发周期性回收内存机制的逻辑如下所示 balance_pgdat()函数是回收页面的主函数其主体函数是一个很长的while循环简化后的代码框架如下
static int balance_pgdat(pg_data_t *pgdat, int order, int classzone_idx)
{...restart:// 使用 sc.priority 表示页面扫描粒度或者优先级sc.priority DEF_PRIORITY;do {...// 检查这个内存节点中是否有合格的 zone其水位高于高水位并且能分配出 2 的 sc.priority 次方个连续的物理页面balanced pgdat_balanced(pgdat, sc.order, classzone_idx);if (!balanced nr_boost_reclaim) {nr_boost_reclaim 0;goto restart;}// 若符合条件则跳转到 out 标签处if (!nr_boost_reclaim balanced)goto out;...// 对匿名页面的活跃 LRU 链表进行老化age_active_anon(pgdat, sc);...// 回收页面的核心函数if (kswapd_shrink_node(pgdat, sc))raise_priority false;...if (raise_priority || !nr_reclaimed)// 不断加大扫描粒度sc.priority--;} while (sc.priority 1);if (!sc.nr_reclaimed)pgdat-kswapd_failures;out:if (boosted) {...// 若设置了 boosted则唤醒 kcompactd 内核线程wakeup_kcompactd(pgdat, pageblock_order, classzone_idx);}...// 返回已经回收的页面数量return sc.order;
}3. shrink_node()函数
shrink_node()函数用于扫描和回收内存节点中所有可回收的页面还会做一些数据的统计和反馈工作
// pgdat 表示内存节点
// sc 表示扫描的控制参数
static bool shrink_node(pg_data_t *pgdat, struct scan_control *sc)
{...do {...// 遍历 memory cgroup调用 shrink_node_memcg() 回收页面do {...// 基于内存节点的页面回收函数它会被 kswapd 内核线程和直接页面回收机制调用shrink_node_memcg(pgdat, memcg, sc, lru_pages);node_lru_pages lru_pages;if (sc-may_shrinkslab) {// shrink_slab() 调用内存管理系统中的 shrinker 接口用于回收 slab 对象shrink_slab(sc-gfp_mask, pgdat-node_id,memcg, sc-priority);}// vmpressure() 函数通过计算 scanned/reclaimed 比例来判断内存压力vmpressure(sc-gfp_mask, memcg, false,sc-nr_scanned - scanned,sc-nr_reclaimed - reclaimed);...} while ((memcg mem_cgroup_iter(root, memcg, reclaim)));...// 判断当前进程是否是 kswapd 内核线程if (current_is_kswapd()) {// 若当前系统回写的页面数量等于这一轮页面扫描的数量说明这些系统有大量回写页// 面因此应该设置 PGDAT_WRITEBACK表示发现有大量页面正在等待回写到磁盘if (sc-nr.writeback sc-nr.writeback sc-nr.taken)set_bit(PGDAT_WRITEBACK, pgdat-flags);// 若当前系统的脏页数量等于正在块设备 I/O 上进行回写数据的页面数量说明系统有大量// 页面堵塞在块设备的 I/O 操作上因此应该设置 PGDAT_CONGESTED表示内存节点中发// 现有大量脏页拥堵在一个 BDI 设备中if (sc-nr.dirty sc-nr.dirty sc-nr.congested)set_bit(PGDAT_CONGESTED, pgdat-flags);// 若当前系统还没有开始回写的脏页数量等于这一轮扫描的文件映射的页面数量说明系统有// 大量脏页面因此应该设置 PGDAT_DIRTY表示发现有大量的脏文件页面if (sc-nr.unqueued_dirty sc-nr.file_taken)set_bit(PGDAT_DIRTY, pgdat-flags);// 统计数据有 immediate 个页面说明在处理正在回写的页面时发现已经有大量的页面在等待回写// 因此需要调用 congestion_wait() 函数让页面等待 100msif (sc-nr.immediate)congestion_wait(BLK_RW_ASYNC, HZ/10);}...// 当前页面回收者是直接页面回收者的情况下// current_may_throttle() 判断当前回写设备是否拥堵若拥堵则睡眠一段时间来缓解拥堵情况。// 若成功回收了 sc-nr_reclaimed 个页面返回 trueif (!sc-hibernation_mode !current_is_kswapd() current_may_throttle() pgdat_memcg_congested(pgdat, root))wait_iff_congested(BLK_RW_ASYNC, HZ/10);// 通过这一轮中回收页面的数量和扫描页面的数量来判断是否需要继续扫描} while (should_continue_reclaim(pgdat, sc-nr_reclaimed - nr_reclaimed,sc-nr_scanned - nr_scanned, sc));...return reclaimable;
}4. shrink_active_list()函数
shrink_active_list()函数用于扫描活跃LRU链表包括匿名页面或者文件映射页面把最近一直没有人访问的页面添加到不活跃LRU链表中。
// nr_to_scan待扫描页面的数量
// lruvecLRU 链表集合
// sc页面扫描控制参数
// lru待扫描的 LRU 链表类型
static void shrink_active_list(unsigned long nr_to_scan,struct lruvec *lruvec,struct scan_control *sc,enum lru_list lru)
{...// 定义 3 个临时链表LIST_HEAD(l_hold); /* The pages which were snipped off */LIST_HEAD(l_active);LIST_HEAD(l_inactive);...// is_file_lru() 判断链表是否为文件映射的 LRU 链表int file is_file_lru(lru);// 从 lruvec 中返回内存节点描述符 pgdatstruct pglist_data *pgdat lruvec_pgdat(lruvec);...// 在操作链表时有一个保护 LRU 的自旋锁 pgdat-lru_lockspin_lock_irq(pgdat-lru_lock);// isolate_lru_pages() 批量地把 LRU 链表的部分页面迁移到临时链表l_hold链表中// 这样可以缩短加锁的时间nr_taken isolate_lru_pages(nr_to_scan, lruvec, l_hold,nr_scanned, sc, isolate_mode, lru);// 增加内存节点中的 NR_ISOLATED_ANON 计数__mod_node_page_state(pgdat, NR_ISOLATED_ANON file, nr_taken);// 增加 recent_scanned[] 计数reclaim_stat-recent_scanned[file] nr_taken;...// 页面迁移到临时链表 l_hold 后释放 pgdat-lru_lock 自旋锁spin_unlock_irq(pgdat-lru_lock);// while 循环扫描临时链表 l_hold 中的页面有些页面会添加到 l_active 中// 有些会添加到 l_inactive 中while (!list_empty(l_hold)) {cond_resched();// lru_to_page() 从链表中取一个页面page lru_to_page(l_hold);list_del(page-lru);// 如果页面是不可回收的就把它放回不可回收的 LRU 链表中if (unlikely(!page_evictable(page))) {putback_lru_page(page);continue;}...// page_referenced() 函数返回该页面最近访问、引用 PTE 的个数// 若返回 0表示最近没有访问、引用if (page_referenced(page, 0, sc-target_mem_cgroup,vm_flags)) {...}// 如果页面没有被引用清除页面的 PG_Active 标志位并且将页面加入 l_inactive 链表中ClearPageActive(page); /* we are de-activating */SetPageWorkingset(page);list_add(page-lru, l_inactive);}// 这段加锁期间把 l_inactive 和 l_active 链表中的页面迁移到相应的 LRU 链表中spin_lock_irq(pgdat-lru_lock);// 把最近引用的页面数量保存到 recent_rotated 中以便下一次扫描时在// get_scan_count() 中重新计算匿名页面和文件映射页面 LRU 链表的扫描比值reclaim_stat-recent_rotated[file] nr_rotated;nr_activate move_active_pages_to_lru(lruvec, l_active, l_hold, lru);nr_deactivate move_active_pages_to_lru(lruvec, l_inactive, l_hold, lru - LRU_ACTIVE);__mod_node_page_state(pgdat, NR_ISOLATED_ANON file, -nr_taken);spin_unlock_irq(pgdat-lru_lock);mem_cgroup_uncharge_list(l_hold);// l_hold 链表中是剩下的页面可以释放free_unref_page_list(l_hold);trace_mm_vmscan_lru_shrink_active(pgdat-node_id, nr_taken, nr_activate,nr_deactivate, nr_rotated, sc-priority, file);
}5. shrink_inactive_list()函数
shrink_inactive_list()函数扫描不活跃LRU链表以尝试回收页面并且返回已经回收的页面的数量。该函数的逻辑过于复杂因此这里用图来理解如下图所示感兴趣的道友可以根据该流程图去阅读该函数的源代码 6. 页面回收的流程图