直接内存回收中的等待队列

编程

在直接内存回收" title="内存回收">内存回收过程中,有可能会造成当前需要分配内存的进程被加入一个等待队列,当整个node的空闲页数量满足要求时,由kswapd唤醒它重新获取内存。这个等待队列头就是node结点描述符pgdat中的pfmemalloc_wait。如果当前进程加入到了pgdat->pfmemalloc_wait这个等待队列中,那么进程就不会进行直接内存回收,而是由kswapd唤醒后直接进行内存分配。

直接内存回收执行路径是:

__alloc_pages_slowpath() -> __alloc_pages_direct_reclaim() -> __perform_reclaim() -> try_to_free_pages() -> do_try_to_free_pages() -> shrink_zones() -> shrink_zone()

在__alloc_pages_slowpath()中可能唤醒了所有node的kswapd内核线程,也可能没有唤醒,每个node的kswapd是否在__alloc_pages_slowpath()中被唤醒有两个条件:

  1. 分配标志中没有__GFP_NO_KSWAPD,只有在透明大页的分配过程中会有这个标志。
  2. node中有至少一个zone的空闲页框没有达到 空闲页框数量 >= high阀值 + 1 << order + 保留内存,或者有至少一个zone需要进行内存压缩,这两种情况node的kswapd都会被唤醒。

而在kswapd中会对node中每一个不平衡的zone进行内存回收,直到所有zone都满足 zone分配页框后剩余的页框数量 > 此zone的high阀值 + 此zone保留的页框数量。kswapd就会停止内存回收,然后唤醒在等待队列的进程。

之后进程由于内存不足,对zonelist进行直接回收时,会调用到try_to_free_pages(),在这个函数内,决定了进程是否加入到node结点的pgdat->pfmemalloc_wait这个等待队列中,如下:

unsigned long try_to_free_pages(struct zonelist *zonelist, int order,

gfp_t gfp_mask, nodemask_t *nodemask)

{

unsigned long nr_reclaimed;

struct scan_control sc = {

/* 打算回收32个页框 */

.nr_to_reclaim = SWAP_CLUSTER_MAX,

.gfp_mask = (gfp_mask = memalloc_noio_flags(gfp_mask)),

/* 本次内存分配的order值 */

.order = order,

/* 允许进行回收的node掩码 */

.nodemask = nodemask,

/* 优先级为默认的12 */

.priority = DEF_PRIORITY,

/* 与/proc/sys/vm/laptop_mode文件有关

* laptop_mode为0,则允许进行回写操作,即使允许回写,直接内存回收也不能对脏文件页进行回写

* 不过允许回写时,可以对非文件页进行回写

*/

.may_writepage = !laptop_mode,

/* 允许进行unmap操作 */

.may_unmap = 1,

/* 允许进行非文件页的操作 */

.may_swap = 1,

};

/*

* Do not enter reclaim if fatal signal was delivered while throttled.

* 1 is returned so that the page allocator does not OOM kill at this

* point.

*/

/* 当zonelist中获取到的第一个node平衡,则返回,如果获取到的第一个node不平衡,则将当前进程加入到pgdat->pfmemalloc_wait这个等待队列中

* 这个等待队列会在kswapd进行内存回收时,如果让node平衡了,则会唤醒这个等待队列中的进程

* 判断node平衡的标准:

* 此node的ZONE_DMA和ZONE_NORMAL的总共空闲页框数量 是否大于 此node的ZONE_DMA和ZONE_NORMAL的平均min阀值数量,大于则说明node平衡

* 加入pgdat->pfmemalloc_wait的情况

* 1.如果分配标志禁止了文件系统操作,则将要进行内存回收的进程设置为TASK_INTERRUPTIBLE状态,然后加入到node的pgdat->pfmemalloc_wait,并且会设置超时时间为1s

* 2.如果分配标志没有禁止了文件系统操作,则将要进行内存回收的进程加入到node的pgdat->pfmemalloc_wait,并设置为TASK_KILLABLE状态,表示允许 TASK_UNINTERRUPTIBLE 响应致命信号的状态

* 返回真,表示此进程加入过pgdat->pfmemalloc_wait等待队列,并且已经被唤醒

* 返回假,表示此进程没有加入过pgdat->pfmemalloc_wait等待队列

*/

if (throttle_direct_reclaim(gfp_mask, zonelist, nodemask))

return 1;

trace_mm_vmscan_direct_reclaim_begin(order,

sc.may_writepage,

gfp_mask);

/* 进行内存回收,有三种情况到这里

* 1.当前进程为内核线程

* 2.最优node是平衡的,当前进程没有加入到pgdat->pfmemalloc_wait中

* 3.当前进程接收到了kill信号

*/

nr_reclaimed = do_try_to_free_pages(zonelist, &sc);

trace_mm_vmscan_direct_reclaim_end(nr_reclaimed);

return nr_reclaimed;

}

主要通过throttle_direct_reclaim()函数判断是否加入到pgdat->pfmemalloc_wait等待队列中,主要看此函数:

/* 当zonelist中第一个node平衡,则返回,如果node不平衡,则将当前进程加入到pgdat->pfmemalloc_wait这个等待队列中 

* 这个等待队列会在kswapd进行内存回收时,如果让node平衡了,则会唤醒这个等待队列中的进程

* 判断node平衡的标准:

* 此node的ZONE_DMA和ZONE_NORMAL的总共空闲页框数量 是否大于 此node的ZONE_DMA和ZONE_NORMAL的平均min阀值数量,大于则说明node平衡

* 加入pgdat->pfmemalloc_wait的情况

* 1.如果分配标志禁止了文件系统操作,则将要进行内存回收的进程设置为TASK_INTERRUPTIBLE状态,然后加入到node的pgdat->pfmemalloc_wait,并且会设置超时时间为1s

* 2.如果分配标志没有禁止了文件系统操作,则将要进行内存回收的进程加入到node的pgdat->pfmemalloc_wait,并设置为TASK_KILLABLE状态,表示允许 TASK_UNINTERRUPTIBLE 响应致命信号的状态

*/

static bool throttle_direct_reclaim(gfp_t gfp_mask, struct zonelist *zonelist,

nodemask_t *nodemask)

{

struct zoneref *z;

struct zone *zone;

pg_data_t *pgdat = NULL;

/* 如果标记了PF_KTHREAD,表示此进程是一个内核线程,则不会往下执行 */

if (current->flags & PF_KTHREAD)

goto out;

/* 此进程已经接收到了kill信号,准备要被杀掉了 */

if (fatal_signal_pending(current))

goto out;

/* 遍历zonelist,但是里面只会在获取到第一个pgdat时就跳出 */

for_each_zone_zonelist_nodemask(zone, z, zonelist,

gfp_mask, nodemask) {

/* 只遍历ZONE_NORMAL和ZONE_DMA区 */

if (zone_idx(zone) > ZONE_NORMAL)

continue;

/* 获取zone对应的node */

pgdat = zone->zone_pgdat;

/* 判断node是否平衡,如果平衡,则返回真

* 如果不平衡,如果此node的kswapd没有被唤醒,则唤醒,并且这里唤醒kswapd只会对ZONE_NORMAL以下的zone进行内存回收

* node是否平衡的判断标准是:

* 此node的ZONE_DMA和ZONE_NORMAL的总共空闲页框数量 是否大于 此node的ZONE_DMA和ZONE_NORMAL的平均min阀值数量,大于则说明node平衡

*/

if (pfmemalloc_watermark_ok(pgdat))

goto out;

break;

}

if (!pgdat)

goto out;

count_vm_event(PGSCAN_DIRECT_THROTTLE);

if (!(gfp_mask & __GFP_FS)) {

/* 如果分配标志禁止了文件系统操作,则将要进行内存回收的进程设置为TASK_INTERRUPTIBLE状态,然后加入到node的pgdat->pfmemalloc_wait,并且会设置超时时间为1s

* 1.pfmemalloc_watermark_ok(pgdat)为真时被唤醒,而1s没超时,返回剩余timeout(jiffies)

* 2.睡眠超过1s时会唤醒,而pfmemalloc_watermark_ok(pgdat)此时为真,返回1

* 3.睡眠超过1s时会唤醒,而pfmemalloc_watermark_ok(pgdat)此时为假,返回0

* 4.接收到信号被唤醒,返回-ERESTARTSYS

*/

wait_event_interruptible_timeout(pgdat->pfmemalloc_wait,

pfmemalloc_watermark_ok(pgdat), HZ);

goto check_pending;

}

/* Throttle until kswapd wakes the process */

/* 如果分配标志没有禁止了文件系统操作,则将要进行内存回收的进程加入到node的pgdat->pfmemalloc_wait,并设置为TASK_KILLABLE状态,表示允许 TASK_UNINTERRUPTIBLE 响应致命信号的状态

* 这些进程在两种情况下被唤醒

* 1.pfmemalloc_watermark_ok(pgdat)为真时

* 2.接收到致命信号时

*/

wait_event_killable(zone->zone_pgdat->pfmemalloc_wait,

pfmemalloc_watermark_ok(pgdat));

check_pending:

/* 如果加入到了pgdat->pfmemalloc_wait后被唤醒,就会执行到这 */

/* 唤醒后再次检查当前进程是否接受到了kill信号,准备退出 */

if (fatal_signal_pending(current))

return true;

out:

return false;

}

 有四点需要注意:

  1. 当前进程已经接收到kill信号,则不会将其加入到pgdat->pfmemalloc_wait中。
  2. 只获取第一个node,也就是当前进程最希望从此node中分配到内存。
  3. 判断一个node是否平衡的条件是:此node的ZONE_NORMAL和ZONE_DMA两个区的空闲页框数量 > 此node的ZONE_NORMAL和ZONE_DMA两个区的平均min阀值。如果不平衡,则加入到pgdat->pfmemalloc_wait等待队列中,如果平衡,则直接返回,并由当前进程自己进行直接内存回收。
  4. 如果当前进程分配内存时使用的标志没有__GFP_FS,则加入pgdat->pfmemalloc_wait中会有一个超时限制,为1s。并且加入后的状态是TASK_INTERRUPTABLE。

    其他情况的进程加入到pgdat->pfmemalloc_wait中没有超时限制,并且状态是TASK_KILLABLE。

      

如果进程加入到了node的pgdat->pfmemalloc_wait等待队列中。在此node的kswapd进行内存回收后,会通过再次判断此node是否平衡来唤醒这些进程,如果node平衡,则唤醒这些进程,否则不唤醒。实际上,不唤醒也说明了node没有平衡,kswapd还是会继续进行内存回收,最后kswapd实在没办法让node达到平衡水平下,会在kswapd睡眠前,将这些进程全部进行唤醒。

以上是 直接内存回收中的等待队列 的全部内容, 来源链接: utcz.com/z/515342.html

回到顶部