記事 | コンテンツ |
---|---|
Linux のメモリ管理: Bootmem が主導権を握る | Bootmem プロセスメモリアロケータを開始します |
Linux メモリ管理: Buddy システムは長い間待ち望まれていました | Buddy System パートナー システム メモリ アロケータ |
Linux メモリ管理: Slab がデビュー | Slab メモリアロケータ |
Linux メモリ管理: メモリ割り当てとメモリ リサイクルの原則 | メモリ割り当てとメモリ回復の原則 |
ソースコード解析コラムの4回目の記事です。
主に、メモリ管理、デバイス管理、システム起動、その他の部分の 4 つの主要な分析モジュールに分かれています。
メモリ管理はBootmem
、Buddy System
と のSlab
3 つの部分に分かれています。もちろん、メモリの初期化に加えて、メモリの割り当てとメモリのリサイクルも行う必要があります。
一部は
todo
後で追加されます
目次
記憶を取り戻す
基本的な考え方
システム メモリの負荷が高い場合、linux
システム内の負荷の高い各zone
メモリがリサイクルされます。
メモリのリサイクルは主に匿名ページとファイル ページで実行されます。
- 匿名ページの場合、使用頻度の低い匿名ページの一部がメモリのリサイクル プロセス中に除外され、
swap
パーティションに書き込まれ、空きページ フレームとしてパートナー システムに解放されます。- ファイル ページの場合、このファイル ページに保存されているコンテンツがクリーン ページの場合、書き込み可能である必要はなく、フリー ページはパートナー システムに直接解放されますが、逆にダーティ ページは書き込み可能になります。最初にディスクに書き戻されてから、パートナー システムにリリースされます。
ただし、このとき大きな負荷がかかるという欠点があるため、I/O
システムではzone
通常、1ページごとにラインを設定し、空きページフレーム数がこのラインに満たない場合には、メモリが不足します。リサイクル操作が実行されます。それ以外の場合、メモリのリサイクルは実行されません。
メモリのリサイクルはユニットzone
に基づいており、通常はzone
次の 3 つの行があります。
watermark[WMARK_MIN]
: このしきい値は、高速割り当てが失敗した後の低速割り当てでの割り当てに使用されます。この値が低速割り当てでも割り当てられない場合は、ダイレクト メモリ リサイクルと高速メモリ リサイクルが実行されます。watermark[WMARK_LOW]
: 低しきい値、高速割り当てのデフォルトのしきい値です。割り当てプロセス中に、zone
空きページの数がこのしきい値よりも低い場合、システムはzone
高速メモリ リサイクルを実行します。watermark[WMARK_HIGH]
: 高いしきい値は、zone
空きページ数として十分な値です。一般に、zone
メモリ再利用を実行するときの目標は、zone
空きページ数をこの値まで増やすことです。
liuzixuan@liuzixuan-ubuntu ~ # cat /proc/zoneinfo
Node 0, zone Normal
pages free 5179
min 4189
low 5236
high 6283
メモリ リサイクルの主な対象となるのは、 、リンク リスト内のページ、およびリンク リスト内のページの 3 つです。主にプロセス空間で使用されるメモリ ページの管理に使用されます。主に 3 種類のページを管理します。匿名ページ、匿名ページ
zone
、ファイル ページとページ。slab
lru
buffer_head
lru
shmem
メモリページがリサイクル可能かどうかを判断する前提は、
page->_count = 0
セルフアロケータ
メモリ割り当てalloc_page
とalloc_page
一般的な呼び出し__alloc_pages_nodemask->__alloc_pages_internal
static inline struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
struct zonelist *zonelist, nodemask_t *nodemask)
{
return __alloc_pages_internal(gfp_mask, order, zonelist, nodemask);
}
__alloc_pages_internal
low
一般に、しきい値を使用した高速メモリ割り当てget_page_from_freelist
と、min
使用量しきい値を使用した低速メモリ割り当てが実行されます。
struct page *
__alloc_pages_internal(gfp_t gfp_mask, unsigned int order,
struct zonelist *zonelist, nodemask_t *nodemask)
{
// ...
page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,
zonelist, high_zoneidx, ALLOC_WMARK_LOW|ALLOC_CPUSET);
if (page)
goto got_pg;
高速なメモリ割り当て
高速メモリ割り当て機能は、しきい値get_page_from_freelist
を介して適切な割り当てを取得します。しきい値に達しない場合は、高速メモリ リサイクルが実行されます。高速メモリ リサイクル後、割り当てが再度試行されます。low
zonelist
zone
zone
low
gfp_mask
: メモリの申請に使用されますgfp mask
order
: 物理メモリレベルを適用しますzonelist
:zone
ノードのzonelist
配列alloc_flags
: 変換後にメモリを申請しますflags
high_zoneidx
: 適用できる最大メモリ量zone
alloc flags
これは、内部でメモリを適用するためにバディによって使用されflag
、いくつかのメモリ割り当て動作を決定します。
/* The ALLOC_WMARK bits are used as an index to zone->watermark */
#define ALLOC_WMARK_MIN WMARK_MIN
#define ALLOC_WMARK_LOW WMARK_LOW
#define ALLOC_WMARK_HIGH WMARK_HIGH
#define ALLOC_NO_WATERMARKS 0x04 /* don't check watermarks at all */
/* Mask to get the watermark bits */
#define ALLOC_WMARK_MASK (ALLOC_NO_WATERMARKS-1)
/*
* Only MMU archs have async oom victim reclaim - aka oom_reaper so we
* cannot assume a reduced access to memory reserves is sufficient for
* !MMU
*/
#ifdef CONFIG_MMU
#define ALLOC_OOM 0x08
#else
#define ALLOC_OOM ALLOC_NO_WATERMARKS
#endif
#define ALLOC_HARDER 0x10 /* try to alloc harder */
#define ALLOC_HIGH 0x20 /* __GFP_HIGH set */
#define ALLOC_CPUSET 0x40 /* check for correct cpuset */
#define ALLOC_CMA 0x80 /* allow allocations from CMA areas */
#ifdef CONFIG_ZONE_DMA32
#define ALLOC_NOFRAGMENT 0x100 /* avoid mixing pageblock types */
#else
#define ALLOC_NOFRAGMENT 0x0
#endif
#define ALLOC_KSWAPD 0x800 /* allow waking of kswapd, __GFP_KSWAPD_RECLAIM set */
ロゴの意味は以下の通りです
ALLO_WMARK_XXX
:メモリを申請するときにwatermark
関連しますALLOC_NO_WATERMARKS
:メモリ申請時にチェックしないwater mark
ALLOC_OOM
:メモリ不足時のトリガを許可する:OOMALLOC_HARDER
ページ移行時にMIGRATE_HIGHATOMIC
予約メモリの使用を許可するかどうかALLOC_HIGH
:と__GFP_HIGH
同じ機能ALLOC_CPUSET
:CPUSET
メモリアプリケーションを制御する機能を使用するかどうかALLOC_CMA
: メモリCMA
のALLOC_NOFRAGMENT
: 設定されている場合、no_fallback
メモリが不足している場合に使用されるポリシーが決定され、リモート ノードからのメモリの適用、つまり外部メモリ フラグメントの生成が許可されません。ALLOC_KSWAPD
:メモリ不足時に有効可能kswapd
get_page_from_freelist
このアルゴリズムをメモリに適用するのは初めての試みであり、中心となるアイデアはbuddy
、メモリが十分なzone
ときに対応するorder
物理ページfreelist
から物理ページを取得することです。
get_page_from_freelist
/*
* get_page_from_freelist goes through the zonelist trying to allocate
* a page.
*/
static struct page *
get_page_from_freelist(gfp_t gfp_mask, nodemask_t *nodemask, unsigned int order,
struct zonelist *zonelist, int high_zoneidx, int alloc_flags)
{
struct zoneref *z;
struct page *page = NULL;
int classzone_idx;
struct zone *zone, *preferred_zone;
nodemask_t *allowednodes = NULL;/* zonelist_cache approximation */
int zlc_active = 0; /* set if using zonelist_cache */
int did_zlc_setup = 0; /* just call zlc_setup() one time */
取得zone
_id
classzone_idx = zone_idx(preferred_zone);
ラベルを見てみましょうzonelist_scan
。ラベルが渡された場合はzonelist
、十分な空きページがあるラベルを探します。zone
zonelist_scan:
/*
* Scan zonelist, looking for a zone with enough free.
* See also cpuset_zone_allowed() comment in kernel/cpuset.c.
*/
for_each_zone_zonelist_nodemask(zone, z, zonelist,
high_zoneidx, nodemask) {
if (NUMA_BUILD && zlc_active &&
!zlc_zone_worth_trying(zonelist, z, allowednodes))
continue;
if ((alloc_flags & ALLOC_CPUSET) &&
!cpuset_zone_allowed_softwall(zone, gfp_mask))
goto try_next_zone;
if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {
unsigned long mark;
int ret;
if (alloc_flags & ALLOC_WMARK_MIN)
mark = zone->pages_min;
else if (alloc_flags & ALLOC_WMARK_LOW)
mark = zone->pages_low;
else
mark = zone->pages_high;
if (zone_watermark_ok(zone, order, mark,
classzone_idx, alloc_flags))
goto try_this_zone;
if (zone_reclaim_mode == 0)
goto this_zone_full;
ret = zone_reclaim(zone, gfp_mask, order);
switch (ret) {
case ZONE_RECLAIM_NOSCAN:
/* did not scan */
goto try_next_zone;
case ZONE_RECLAIM_FULL:
/* scanned but unreclaimable */
goto this_zone_full;
default:
/* did we reclaim enough */
if (!zone_watermark_ok(zone, order, mark,
classzone_idx, alloc_flags))
goto this_zone_full;
}
}
へのマクロzonelist_scan
展開について
for (z = first_zones_zonelist(zonelist, high_zoneidx, nodemask, &zone)
{
zone;
z = next_zones_zonelist(++z, high_zoneidx, nodemask, &zone)// 获取zonelist中的下一个zone
}
static inline struct zoneref *first_zones_zonelist(struct zonelist *zonelist,
enum zone_type highest_zoneidx,
nodemask_t *nodes,
struct zone **zone)
{
return next_zones_zonelist(zonelist->_zonerefs, highest_zoneidx, nodes,
zone);
}
struct zoneref *next_zones_zonelist(struct zoneref *z,
enum zone_type highest_zoneidx,
nodemask_t *nodes,
struct zone **zone)
{
/*
* Find the next suitable zone to use for the allocation.
* Only filter based on nodemask if it's set
*/
if (likely(nodes == NULL))
while (zonelist_zone_idx(z) > highest_zoneidx)
z++;
else
while (zonelist_zone_idx(z) > highest_zoneidx ||
(z->zone && !zref_in_nodemask(z, nodes)))
z++;
*zone = zonelist_zone(z); // 获得zonelist中的zone
return z;
}
if (NUMA_BUILD && zlc_active &&
// z->zone所在的节点不允许分配或者该zone已经饱满了
!zlc_zone_worth_trying(zonelist, z, allowednodes))
continue;
if ((alloc_flags & ALLOC_CPUSET) &&
// 开启了检查内存节点是否在指定CPU集合,并且该zone不被允许在该CPU上分配内存
!cpuset_zone_allowed_softwall(zone, gfp_mask))
goto try_next_zone;
zlc_zone_worth_trying
ノードが割り当てを許可しているかどうか、またはノードzone
がいっぱいかどうかを確認します
static int zlc_zone_worth_trying(struct zonelist *zonelist, struct zoneref *z,
nodemask_t *allowednodes)
{
struct zonelist_cache *zlc; /* cached zonelist speedup info */
int i; /* index of *z in zonelist zones */
int n; /* node that zone *z is on */
zlc = zonelist->zlcache_ptr; // 得到zonelist_cache指针信息
if (!zlc)
return 1;
i = z - zonelist->_zonerefs; // 获得_zonerefs数组位置
n = zlc->z_to_n[i];
/* This zone is worth trying if it is allowed but not full */
return node_isset(n, *allowednodes) && !test_bit(i, zlc->fullzones);
}
struct zonelist_cache {
unsigned short z_to_n[MAX_ZONES_PER_ZONELIST]; /* zone->nid */
DECLARE_BITMAP(fullzones, MAX_ZONES_PER_ZONELIST); /* zone full? */
unsigned long last_full_zap; /* when last zap'd (jiffies) */
};
主なものは、次の 2 つの関数をテストすることですnode_isset
。test_bit
#define node_isset(node, nodemask) test_bit((node), (nodemask).bits)
static inline int test_bit(int nr, const volatile unsigned long *addr)
{
return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1)));
}
cpuset_zone_allowed_softwall
関数についても同様であり、概要は示しません。
高速メモリ割り当ての水位を取得する
if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {
unsigned long mark;
int ret;
if (alloc_flags & ALLOC_WMARK_MIN)
mark = zone->pages_min; // 选择min阈值
else if (alloc_flags & ALLOC_WMARK_LOW)
mark = zone->pages_low; // 选择low阈值
else
mark = zone->pages_high; // 选择high阈值
ゾーン_ウォーターマーク_ok
zone
割り当てるのに十分なページがあることを確認してください
if (zone_watermark_ok(zone, order, mark,
classzone_idx, alloc_flags))
goto try_this_zone;
水位を確認します。これは、 、のいずれかmask
です。この関数から、割り当てられるページはいくつかの条件を満たしている必要があることがわかります。low
min
high
2^order
- 割り当てられたページ フレームに加えて、メモリ管理領域には少なくとも
min
1 つの空きページ フレームもあります。 - 割り当てられたページ フレームに加えて、
order
少なくともo
ブロック内にmin/2^o
空きページ フレーム以上の空きページ フレームが存在します。
/*
* Return 1 if free pages are above 'mark'. This takes into account the order
* of the allocation.
*/
int zone_watermark_ok(struct zone *z, int order, unsigned long mark,
int classzone_idx, int alloc_flags)
{
/* free_pages my go negative - that's OK */
long min = mark;
// 获得空闲页的数量vm_stat[NR_FREE_PAGES]
// 减去要分配的页面(1 << order)
long free_pages = zone_page_state(z, NR_FREE_PAGES) - (1 << order) + 1;
int o;
if (alloc_flags & ALLOC_HIGH)
min -= min / 2;
if (alloc_flags & ALLOC_HARDER)
min -= min / 4;
// lowmem_reserve表示要预留的页面个数
if (free_pages <= min + z->lowmem_reserve[classzone_idx])
return 0;
// 除去要分配的页面个数,从order k 到 order 10的空闲页面总数,至少得是 min/(2^k)
for (o = 0; o < order; o++) {
/* At the next order, this order's pages become unavailable */
free_pages -= z->free_area[o].nr_free << o;
/* Require fewer higher order pages to be free */
min >>= 1;
if (free_pages <= min)
return 0;
}
return 1;
}
zone_watermark_ok
水位try_this_zone
監視を通過した後、直接ページフレームの割り当て、つまりbuffered_rmqueue
関数の割り当てに進みます。
バッファされた_rmqueue
この機能は、相手システムのzone
指定領域にページを配置するためのコア機能です。
static struct page *buffered_rmqueue(struct zone *preferred_zone,
struct zone *zone, int order, gfp_t gfp_flags)
{
unsigned long flags;
struct page *page;
int cold = !!(gfp_flags & __GFP_COLD);
int cpu;
int migratetype = allocflags_to_migratetype(gfp_flags); //
again:
cpu = get_cpu();
if (likely(order == 0)) {
// 表示单页,从pcplist进行分配 冷热页
struct per_cpu_pages *pcp;
// 获取到本节点的cpu高速缓存页
pcp = &zone_pcp(zone, cpu)->pcp;
local_irq_save(flags);
// 该链表为空,大概率上次获取的cpu高速缓存的迁移类型和这次不一致
if (!pcp->count) {
// 从伙伴系统中获得页,然后向高速缓存中添加内存页
pcp->count = rmqueue_bulk(zone, 0,
pcp->batch, &pcp->list, migratetype);
// 如果链表仍然为空,那么说明伙伴系统中页面也没有了,分配失败
if (unlikely(!pcp->count))
goto failed;
}
/* Find a page of the appropriate migrate type */
// 如果分配的页面不需要考虑硬件缓存(注意不是每CPU页面缓存),则取出链表的最后一个节点返回给上层
if (cold) {
list_for_each_entry_reverse(page, &pcp->list, lru)
if (page_private(page) == migratetype)
break;
} else {
// 如果要考虑硬件缓存,则取出链表的第一个页面,这个页面是最近刚释放到每CPU缓存的,缓存热度更高
list_for_each_entry(page, &pcp->list, lru)
if (page_private(page) == migratetype)
break;
}
/* Allocate more to the pcp list if necessary */
if (unlikely(&page->lru == &pcp->list)) {
pcp->count += rmqueue_bulk(zone, 0,
pcp->batch, &pcp->list, migratetype);
page = list_entry(pcp->list.next, struct page, lru);
}
//将页面从每CPU缓存链表中取出,并将每CPU缓存计数减1
list_del(&page->lru);
pcp->count--;
// 分配的是多个页面,不需要考虑每CPU页面缓存,直接从系统中分配
} else {
// 去指定的migratetype的链表中去分配
spin_lock_irqsave(&zone->lock, flags); //关中断,并获得管理区的锁
page = __rmqueue(zone, order, migratetype);
spin_unlock(&zone->lock); //先回收(打开)锁,待后面统计计数设置完毕后再开中断
if (!page)
goto failed;
}
// 事件统计计数,debug(调试)用
__count_zone_vm_events(PGALLOC, zone, 1 << order);
zone_statistics(preferred_zone, zone);
local_irq_restore(flags); //恢复中断
put_cpu();
VM_BUG_ON(bad_range(zone, page));
if (prep_new_page(page, order, gfp_flags))
goto again;
return page;
failed:
local_irq_restore(flags);
put_cpu();
return NULL;
}
この機能は__rmqueue
2 つの状況に分けられます。
- クイック割り当て
__rmqueue_smallest
: 指定された移行タイプのリンク リストから直接割り当てます。 - 遅い割り当て
__rmqueue_fallback
: 指定されたリンク リスト内の移行タイプに十分なメモリがない場合、バックアップ リストが使用されます。
/*
* Do the hard work of removing an element from the buddy allocator.
* Call me with the zone->lock already held.
*/
static struct page *__rmqueue(struct zone *zone, unsigned int order,
int migratetype)
{
struct page *page;
// 快速分配
page = __rmqueue_smallest(zone, order, migratetype);
if (unlikely(!page))
page = __rmqueue_fallback(zone, order, migratetype);
return page;
}
この時点で完了ですが、zone_watermark_ok
水位検知に失敗した場合は引き続き下方向へ呼び出しを行ってください。
ここで実行すると、zone
ページをリサイクルする必要があることを示します
つまり、水位検出に失敗した場合は、使用可能なページが存在しないことを意味するため、ここである程度のメモリリサイクルが実行されます。
ret = zone_reclaim(zone, gfp_mask, order);
zone_reclaim
ページをリサイクルすることで
ページフレーム数が再利用された
2^order
場合のみ true が返され、再利用されてページフレーム数に達していない場合でも false が返されます。
int zone_reclaim(struct zone *zone, gfp_t gfp_mask, unsigned int order)
{
int node_id;
int ret;
// 都小于最小设定的值
if (zone_pagecache_reclaimable(zone) <= zone->min_unmapped_pages &&
zone_page_state(zone, NR_SLAB_RECLAIMABLE) <= zone->min_slab_pages)
return ZONE_RECLAIM_FULL;
if (zone_is_all_unreclaimable(zone)) // 设定了标识不回收
return ZONE_RECLAIM_FULL;
// 如果没有设置__GFP_WAIT,即wait为0,则不继续进行内存分配
// 如果PF_MEMALLOC被设置,也就是说调用内存分配函数的本身就是内存回收进程,则不继续进行内存分配
if (!(gfp_mask & __GFP_WAIT) || (current->flags & PF_MEMALLOC))
return ZONE_RECLAIM_NOSCAN;
node_id = zone_to_nid(zone);// 获得本zone的nodeid
// 不属于该cpu范围
if (node_state(node_id, N_CPU) && node_id != numa_node_id())
return ZONE_RECLAIM_NOSCAN;
// 其他进程在回收
if (zone_test_and_set_flag(zone, ZONE_RECLAIM_LOCKED))
return ZONE_RECLAIM_NOSCAN;
ret = __zone_reclaim(zone, gfp_mask, order);// 回收该zone的页
zone_clear_flag(zone, ZONE_RECLAIM_LOCKED);// 释放回收锁
if (!ret)
count_vm_event(PGSCAN_ZONE_RECLAIM_FAILED);
return ret;
}
ここ
PF_MEMALLOC
と はプロセス フラグ ビットである__GFP_WAIT
ことに注意してください。通常、メモリ管理以外のサブシステムはこのフラグを使用すべきではありません。PF_MEMALLOC
コールダウンを続けます__zone_reclaim
static int __zone_reclaim(struct zone *zone, gfp_t gfp_mask, unsigned int order)
{
/* Minimum pages needed in order to stay on node */
const unsigned long nr_pages = 1 << order;
struct task_struct *p = current;
struct reclaim_state reclaim_state;
int priority;
struct scan_control sc = {
// 控制扫描结果
.may_writepage = !!(zone_reclaim_mode & RECLAIM_WRITE),
.may_unmap = !!(zone_reclaim_mode & RECLAIM_SWAP),
.may_swap = 1,
.swap_cluster_max = max_t(unsigned long, nr_pages,
SWAP_CLUSTER_MAX),
.gfp_mask = gfp_mask,
.swappiness = vm_swappiness,
.order = order,
.isolate_pages = isolate_pages_global,
};
unsigned long slab_reclaimable;
disable_swap_token();
cond_resched();
/*
* We need to be able to allocate from the reserves for RECLAIM_SWAP
* and we also need to be able to write out pages for RECLAIM_WRITE
* and RECLAIM_SWAP.
*/
p->flags |= PF_MEMALLOC | PF_SWAPWRITE;
reclaim_state.reclaimed_slab = 0;
p->reclaim_state = &reclaim_state;
if (zone_pagecache_reclaimable(zone) > zone->min_unmapped_pages) {
priority = ZONE_RECLAIM_PRIORITY;
do {
note_zone_scanning_priority(zone, priority);
shrink_zone(priority, zone, &sc); // 回收内存
priority--;
} while (priority >= 0 && sc.nr_reclaimed < nr_pages);
}
slab_reclaimable = zone_page_state(zone, NR_SLAB_RECLAIMABLE);
if (slab_reclaimable > zone->min_slab_pages) {
while (shrink_slab(sc.nr_scanned, gfp_mask, order) &&
zone_page_state(zone, NR_SLAB_RECLAIMABLE) >
slab_reclaimable - nr_pages)
;
sc.nr_reclaimed += slab_reclaimable -
zone_page_state(zone, NR_SLAB_RECLAIMABLE);
}
p->reclaim_state = NULL;
current->flags &= ~(PF_MEMALLOC | PF_SWAPWRITE);
return sc.nr_reclaimed >= nr_pages;
}
についてshrink_zone
はshrink_slab
後ほど説明します
switch (ret) {
case ZONE_RECLAIM_NOSCAN:
/* did not scan */
goto try_next_zone;
case ZONE_RECLAIM_FULL:
/* scanned but unreclaimable */
goto this_zone_full;
default:
/* did we reclaim enough */
if (!zone_watermark_ok(zone, order, mark,
classzone_idx, alloc_flags))
goto this_zone_full;
}
理想的には、メモリの割り当てを開始する
try_this_zone:
page = buffered_rmqueue(preferred_zone, zone, order, gfp_mask);
if (page)
break;
zone
いっぱいになるはずです
this_zone_full:
if (NUMA_BUILD)
zlc_mark_zone_full(zonelist, z);
次へ進むzone
try_next_zone:
if (NUMA_BUILD && !did_zlc_setup) {
/* we do zlc_setup after the first zone is tried */
allowednodes = zlc_setup(zonelist, alloc_flags);
zlc_active = 1;
did_zlc_setup = 1;
}
}
再びサイクルします
if (unlikely(NUMA_BUILD && page == NULL && zlc_active)) {
/* Disable zlc cache for second zonelist scan */
zlc_active = 0;
goto zonelist_scan;
}
メモリ割り当てが遅い
低速メモリ割り当て: 高速メモリ割り当てがある場合、つまり、高速割り当てでメモリが取得されないzonelist
場合、低速割り当てに対してしきい値が使用されます。zone
min
- 非同期メモリ圧縮
- 直接メモリ再利用
- 軽量同期メモリ圧縮 (
oom
割り当てに応じて)
各node
カーネルkswapd
スレッドを起動します
for_each_zone_zonelist(zone, z, zonelist, high_zoneidx)
wakeup_kswapd(zone, order); // 唤醒每个node的kswapd内核线程
/*
* A zone is low on free memory, so wake its kswapd task to service it.
*/
void wakeup_kswapd(struct zone *zone, int order)
{
pg_data_t *pgdat;
if (!populated_zone(zone))
return;
pgdat = zone->zone_pgdat;
// 检查水位
if (zone_watermark_ok(zone, order, zone->pages_low, 0, 0))
return;
if (pgdat->kswapd_max_order < order)
pgdat->kswapd_max_order = order;
if (!cpuset_zone_allowed_hardwall(zone, GFP_KERNEL)) // 允许位
return;
if (!waitqueue_active(&pgdat->kswapd_wait))
return;
wake_up_interruptible(&pgdat->kswapd_wait);
}
要件を減らし、min
高速メモリ割り当ての基準としてしきい値を使用するようにしてください。
alloc_flags = ALLOC_WMARK_MIN;
if ((unlikely(rt_task(p)) && !in_interrupt()) || !wait)
alloc_flags |= ALLOC_HARDER;
if (gfp_mask & __GFP_HIGH)
alloc_flags |= ALLOC_HIGH;
if (wait)
alloc_flags |= ALLOC_CPUSET;
いくつかのマクロ定義はここにあります
ALLOC_HARDER
: メモリの割り当てにさらに努力していることを示しますALLOC_HIGH
:発信者の__GFP_HIGH
優先度を高く設定することを示しますALLOC_CPUSET
:cpuset
メモリページの割り当てが許可されているかどうかを確認することを示します
page = get_page_from_freelist(gfp_mask, nodemask, order, zonelist,
high_zoneidx, alloc_flags);
if (page)
goto got_pg;
ここには「5 つの剣」という言葉がありますが、1 番目の剣はカジュアルに使用され、規格に従って割り当てられている
low
、つまり直接呼び出されます。2 番目の剣は、規格に従ってメモリを割り当てるため、およびメモリを割り当てるときにget_free_page_list()
使用されます。記号にとをmin
追加する記号ALLOC_WMARK_MIN
ALLOC_HARDER
ALLOC_HIGH
ここでは、水位をまったくチェックせずに呼び出しが行われ、alloc_flags
値が次のように割り当てられます。ALLOC_NO_WATERMARKS
rebalance:
if (((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE)))
&& !in_interrupt()) {
if (!(gfp_mask & __GFP_NOMEMALLOC)) {
nofail_alloc:
/* go through the zonelist yet again, ignoring mins */
page = get_page_from_freelist(gfp_mask, nodemask, order,
zonelist, high_zoneidx, ALLOC_NO_WATERMARKS); // 不检查水位分配内存
if (page)
goto got_pg;
if (gfp_mask & __GFP_NOFAIL) {
congestion_wait(WRITE, HZ/50);
goto nofail_alloc;
}
}
goto nopage;
}
try_to_free_pages
メモリを同期的に解放してメモリ ページを取得します。主な機能は次のとおりです。try_to_free_pages
cpuset_update_task_memory_state();
p->flags |= PF_MEMALLOC;
lockdep_set_current_reclaim_state(gfp_mask);
reclaim_state.reclaimed_slab = 0;
p->reclaim_state = &reclaim_state;
did_some_progress = try_to_free_pages(zonelist, order,
gfp_mask, nodemask);
p->reclaim_state = NULL;
lockdep_clear_current_reclaim_state();
p->flags &= ~PF_MEMALLOC;
関数本体は
unsigned long try_to_free_pages(struct zonelist *zonelist, int order,
gfp_t gfp_mask, nodemask_t *nodemask)
{
struct scan_control sc = {
// 扫描控制结构
.gfp_mask = gfp_mask,
.may_writepage = !laptop_mode,
.swap_cluster_max = SWAP_CLUSTER_MAX,
.may_unmap = 1,
.may_swap = 1,
.swappiness = vm_swappiness,
.order = order,
.mem_cgroup = NULL,
.isolate_pages = isolate_pages_global,
.nodemask = nodemask,
};
return do_try_to_free_pages(zonelist, &sc);
}
明確にする必要がある概念的な問題があります。最初の 3 つの方法はメモリを割り当てることです。メモリが不足している場合は、他のノードから
node
ページ フレームを取得するだけです(もちろんメモリのリサイクルもありますzone_reclaim
が、本質はメモリを見つけることです)他のノードから)、ここからは他のノードから直接このノードを検索します
do_try_to_free_pages
これは、shrink_zone
ページshrink_slab
をリサイクルする主な論理機能です。
static unsigned long do_try_to_free_pages(struct zonelist *zonelist,
struct scan_control *sc)
{
int priority;
unsigned long ret = 0;
unsigned long total_scanned = 0;
struct reclaim_state *reclaim_state = current->reclaim_state;
unsigned long lru_pages = 0;
struct zoneref *z;
struct zone *zone;
enum zone_type high_zoneidx = gfp_zone(sc->gfp_mask);
delayacct_freepages_start();
if (scanning_global_lru(sc))
count_vm_event(ALLOCSTALL);
/*
* mem_cgroup will not do shrink_slab.
*/
if (scanning_global_lru(sc)) {
for_each_zone_zonelist(zone, z, zonelist, high_zoneidx) {
if (!cpuset_zone_allowed_hardwall(zone, GFP_KERNEL))
continue;
lru_pages += zone_lru_pages(zone);
}
}
for (priority = DEF_PRIORITY; priority >= 0; priority--) {
sc->nr_scanned = 0;
if (!priority)
disable_swap_token();
shrink_zones(priority, zonelist, sc);
/*
* Don't shrink slabs when reclaiming memory from
* over limit cgroups
*/
if (scanning_global_lru(sc)) {
shrink_slab(sc->nr_scanned, sc->gfp_mask, lru_pages);
if (reclaim_state) {
sc->nr_reclaimed += reclaim_state->reclaimed_slab;
reclaim_state->reclaimed_slab = 0;
}
}
total_scanned += sc->nr_scanned;
if (sc->nr_reclaimed >= sc->swap_cluster_max) {
ret = sc->nr_reclaimed;
goto out;
}
/*
* Try to write back as many pages as we just scanned. This
* tends to cause slow streaming writers to write data to the
* disk smoothly, at the dirtying rate, which is nice. But
* that's undesirable in laptop mode, where we *want* lumpy
* writeout. So in laptop mode, write out the whole world.
*/
if (total_scanned > sc->swap_cluster_max +
sc->swap_cluster_max / 2) {
wakeup_pdflush(laptop_mode ? 0 : total_scanned);
sc->may_writepage = 1;
}
/* Take a nap, wait for some writeback to complete */
if (sc->nr_scanned && priority < DEF_PRIORITY - 2)
congestion_wait(WRITE, HZ/10);
}
/* top priority shrink_zones still had more to do? don't OOM, then */
if (!sc->all_unreclaimable && scanning_global_lru(sc))
ret = sc->nr_reclaimed;
out:
/*
* Now that we've scanned all the zones at this priority level, note
* that level within the zone so that the next thread which performs
* scanning of this zone will immediately start out at this priority
* level. This affects only the decision whether or not to bring
* mapped pages onto the inactive list.
*/
if (priority < 0)
priority = 0;
if (scanning_global_lru(sc)) {
for_each_zone_zonelist(zone, z, zonelist, high_zoneidx) {
if (!cpuset_zone_allowed_hardwall(zone, GFP_KERNEL))
continue;
zone->prev_priority = priority;
}
} else
mem_cgroup_record_reclaim_priority(sc->mem_cgroup, priority);
delayacct_freepages_end();
return ret;
}
if (likely(did_some_progress)) {
page = get_page_from_freelist(gfp_mask, nodemask, order,
zonelist, high_zoneidx, alloc_flags); // 回收后再去分配
if (page)
goto got_pg;
todo
、複雑すぎるので、後で見てみましょう
最後にomm
、本当にページが割り当てられていない場合、特定のプロセスが占有しているページを強制終了する (少し残酷な) メカニズムを使用することです。これが、いわゆるメカニズムですout of memory killer
。
} else if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {
if (!try_set_zone_oom(zonelist, gfp_mask)) {
schedule_timeout_uninterruptible(1);
goto restart;
}
/*
* Go through the zonelist yet one more time, keep
* very high watermark here, this is only to catch
* a parallel oom killing, we must fail if we're still
* under heavy pressure.
*/
page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask,
order, zonelist, high_zoneidx,
ALLOC_WMARK_HIGH|ALLOC_CPUSET); // 虚晃一枪,使用ALLOC_WMARK_HIGH来要求,明显不可能完成
if (page) {
clear_zonelist_oom(zonelist, gfp_mask);
goto got_pg;
}
/* The OOM killer will not help higher order allocs so fail */
if (order > PAGE_ALLOC_COSTLY_ORDER) {
clear_zonelist_oom(zonelist, gfp_mask);
goto nopage;
}
out_of_memory(zonelist, gfp_mask, order); // 释放进程的内存
clear_zonelist_oom(zonelist, gfp_mask);
goto restart;
スキャン制御
スキャン制御構造。その主な機能は、メモリの再利用またはメモリ圧縮のための変数とパラメータを保存することであり、一部の処理結果もここに保存されます。
主にメモリのリサイクルとメモリ圧縮に使用されます。
struct scan_control {
/* Incremented by the number of inactive pages that were scanned */
unsigned long nr_scanned; // 已经扫描的页框数量
/* Number of pages freed so far during a call to shrink_zones() */
unsigned long nr_reclaimed; // 已经回收的页框数量
/* This context's GFP mask */
gfp_t gfp_mask; // 申请内存时使用的分配标志
int may_writepage; // 能否执行回写操作
/* Can mapped pages be reclaimed? */
int may_unmap; // 能否进行unmap操作,即将所有映射了此页的页表项清空
/* Can pages be swapped as part of reclaim? */
int may_swap; // 能否进行swap交换
/* This context's SWAP_CLUSTER_MAX. If freeing memory for
* suspend, we effectively ignore SWAP_CLUSTER_MAX.
* In this context, it doesn't matter that we scan the
* whole list at once. */
int swap_cluster_max;
int swappiness;
int all_unreclaimable;
int order; // 申请内存时使用的order值,因为只有申请内存,然后内存不足时才会进行扫描
/* Which cgroup do we reclaim from */
struct mem_cgroup *mem_cgroup; // 目标memcg,如果是针对整个zone的,则此为NULL
/*
* Nodemask of nodes allowed by the caller. If NULL, all nodes
* are scanned.
*/
nodemask_t *nodemask; // 允许执行扫描的node节点掩码
/* Pluggable isolate pages callback */
unsigned long (*isolate_pages)(unsigned long nr, struct list_head *dst,
unsigned long *scanned, int order, int mode,
struct zone *z, struct mem_cgroup *mem_cont,
int active, int file);
};
メモリ圧縮テクノロジ: 通常の状況では、メモリが不足している場合、システムの最適化を達成するためにメモリ データを
I/O
ディスクに頻繁に書き戻すとflash
、寿命に影響を与えるだけでなく、システムのパフォーマンスにも重大な影響を与えるため、メモリ圧縮テクノロジの導入が必要になります。 、主流なものは次のとおりです。
zSwap
:スワップスペース、通常は圧縮された匿名ページzRam
: メモリを使用してブロックデバイスをシミュレートする方法で、一般に匿名ページは圧縮されます。zCache
: 通常、ファイルのページは圧縮されています。
swap
匿名ページは、プロセスのヒープやスタックなど、ファイルに関連付けられていないページであり、ディスク ファイルと交換することはできませんが、ハード ディスク上に追加のスワップ パーティションを分割するか、スワップ ファイルを使用することで交換できます。
アクティブページと遅延ページについては、システム内のアプリケーションが頻繁にアクセスするかどうかでページがアクティブであるかどうかを判断するのが一般的で、ページが設定されていない場合は遅延ページであるため、移動する必要があります。ページが設定されており、最近アクセスされたことを示す場合は、アクティブなリンク リストに移動する必要があります。
時間が経つにつれて、最もアクティブでないページが遅延リンク リストの最後に配置されます。メモリが不足すると、カーネルはこれらのページをスワップアウトします。これらのページは、誕生からスワップアウトされるまでほとんど使用されないためです。の原則に従って
LRU
、これらのページを交換してもシステムへのダメージは最小限に抑えられます。
メモリの縮小
メモリのリサイクルは通常、適切なzone
メモリのリサイクルを指しますが、最後にmemcg
リサイクルされるメモリのリサイクルを指す場合もあります。
2^(order+1)
毎回ページ フレームをリサイクルし、このメモリ割り当てに満足して、できるだけ多くのページ フレームを再利用しようとします。非アクティブリンクリストlru
の番号がこの基準を満たしていない場合、この状態の判定はキャンセルされます。zone
メモリのリサイクルにはメモリの圧縮が伴うzone
ため、zone
メモリのリサイクルを実行すると、メモリ圧縮が完了するまで空きページ フレームが返されます。
前述のリサイクル機能によると、主に 3 つの機能があります
shrink_zone
。shrink_list
shrink_slab
シュリンクゾーン
static void shrink_zone(int priority, struct zone *zone,
struct scan_control *sc)
{
unsigned long nr[NR_LRU_LISTS];
unsigned long nr_to_scan;
unsigned long percent[2]; /* anon @ 0; file @ 1 */
enum lru_list l;
unsigned long nr_reclaimed = sc->nr_reclaimed;
unsigned long swap_cluster_max = sc->swap_cluster_max;
get_scan_ratio
get_scan_ratio(zone, sc, percent);
一般に、物理メモリが十分ではない場合、次の 2 つのオプションがあります。
- 一部の匿名ページを
swap
パーティションに置き換えます page cache
内部のデータをディスクにフラッシュするか、直接クリーンアップします。
どちらの方法でも、swap
重みは次のように置き換えられます。
/*
* With swappiness at 100, anonymous and file have the same priority.
* This scanning priority is essentially the inverse of IO cost.
*/
anon_prio = sc->swappiness;
file_prio = 200 - sc->swappiness;
まず完全に閉まっているかどうかを確認しますswap
static void get_scan_ratio(struct zone *zone, struct scan_control *sc,
unsigned long *percent)
{
unsigned long anon, file, free;
unsigned long anon_prio, file_prio;
unsigned long ap, fp;
struct zone_reclaim_stat *reclaim_stat = get_reclaim_stat(zone, sc);
/* If we have no swap space, do not bother scanning anon pages. */
if (!sc->may_swap || (nr_swap_pages <= 0)) {
percent[0] = 0;
percent[1] = 100;
return;
}
page cache
匿名ページとページの数を数える
anon = zone_nr_pages(zone, sc, LRU_ACTIVE_ANON) +
zone_nr_pages(zone, sc, LRU_INACTIVE_ANON);
file = zone_nr_pages(zone, sc, LRU_ACTIVE_FILE) +
zone_nr_pages(zone, sc, LRU_INACTIVE_FILE);
空きページ数free
+page cache
ページ数がhigh
しきい値未満の場合、すべてがswap
中央に配置されます。
if (scanning_global_lru(sc)) {
free = zone_page_state(zone, NR_FREE_PAGES);
/* If we have very few page cache pages,
force-scan anon pages. */
if (unlikely(file + free <= zone->pages_high)) {
percent[0] = 100;
percent[1] = 0;
return;
}
}
割合を計算する
anon_prio = sc->swappiness;
file_prio = 200 - sc->swappiness;
/*
* The amount of pressure on anon vs file pages is inversely
* proportional to the fraction of recently scanned pages on
* each list that were recently referenced and in active use.
*/
ap = (anon_prio + 1) * (reclaim_stat->recent_scanned[0] + 1);
ap /= reclaim_stat->recent_rotated[0] + 1;
fp = (file_prio + 1) * (reclaim_stat->recent_scanned[1] + 1);
fp /= reclaim_stat->recent_rotated[1] + 1;
/* Normalize to percentages */
percent[0] = 100 * ap / (ap + fp + 1);
percent[1] = 100 - percent[0];
途中に切り替えがあります
for_each_evictable_lru(l) {
int file = is_file_lru(l);
unsigned long scan;
scan = zone_nr_pages(zone, sc, l);
if (priority) {
scan >>= priority;
scan = (scan * percent[file]) / 100;
}
if (scanning_global_lru(sc)) {
zone->lru[l].nr_scan += scan;
nr[l] = zone->lru[l].nr_scan;
if (nr[l] >= swap_cluster_max)
zone->lru[l].nr_scan = 0;
else
nr[l] = 0;
} else
nr[l] = scan;
}
マクロは次for_each_evictable_lru
のように展開されます
for (l = 0; l <= LRU_ACTIVE_FILE; l++)
l
表現される変数は次のとおりですstruct lru_list
enum lru_list {
LRU_INACTIVE_ANON = LRU_BASE,
LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,
LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,
LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,
#ifdef CONFIG_UNEVICTABLE_LRU
LRU_UNEVICTABLE,
#else
LRU_UNEVICTABLE = LRU_ACTIVE_FILE, /* avoid compiler errors in dead code */
#endif
NR_LRU_LISTS
};
Visible
LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE
、リサイクル可能なリンク リストを示します
lru
したがって、このループはこれら 3 つのリンク リストをループし、リサイクル可能なリンク リストをすべて走査します。
シュリンクリスト
LRU_INACTIVE_ANON,LRU_ACTIVE_ANON,LRU_INACTIVE_FILE,LRU_ACTIVE_FILE
この順序でlru
リンクされたリストをたどります
while (nr[LRU_INACTIVE_ANON] || nr[LRU_ACTIVE_FILE] ||
nr[LRU_INACTIVE_FILE]) {
for_each_evictable_lru(l) {
if (nr[l]) {
nr_to_scan = min(nr[l], swap_cluster_max);
nr[l] -= nr_to_scan;
// 对此lru类型的链表进行回收
nr_reclaimed += shrink_list(l, nr_to_scan,
zone, sc, priority);
}
}
/*
* On large memory systems, scan >> priority can become
* really large. This is fine for the starting priority;
* we want to put equal scanning pressure on each zone.
* However, if the VM has a harder time of freeing pages,
* with multiple processes reclaiming pages, the total
* freeing target can get unreasonably large.
*/
if (nr_reclaimed > swap_cluster_max &&
priority < DEF_PRIORITY && !current_is_kswapd())
break;
}
vfs_cache_init
各ディレクトリエントリがどのように保存されたかを思い出してくださいshrinker_list
。ここでメモリのリサイクルが実行されます。主なカテゴリは次のとおりです。
LRU_ACTIVE_FILE
: アクティブ ファイル、呼び出し、プロセスshrink_active_list
アクティブリンク リストlru
LRU_ACTIVE_ANON
: アクティビティ匿名、呼び出しshrink_active_list
、lru
アクティビティ リストの処理、非匿名アクティビティ ページが少なすぎますshrink_inactive_list
、非アクティブなlru
リンクされたリスト
static unsigned long shrink_list(enum lru_list lru, unsigned long nr_to_scan,
struct zone *zone, struct scan_control *sc, int priority)
{
int file = is_file_lru(lru);
if (lru == LRU_ACTIVE_FILE) {
shrink_active_list(nr_to_scan, zone, sc, priority, file);
return 0;
}
if (lru == LRU_ACTIVE_ANON && inactive_anon_is_low(zone, sc)) {
shrink_active_list(nr_to_scan, zone, sc, priority, file);
return 0;
}
return shrink_inactive_list(nr_to_scan, zone, sc, priority, file);
}
再利用されたメモリを設定する
sc->nr_reclaimed = nr_reclaimed;
非匿名のアクティブなページが少なすぎる場合は、shrink_active_list
/*
* Even if we did not try to evict anon pages at all, we want to
* rebalance the anon lru active/inactive ratio.
*/
if (inactive_anon_is_low(zone, sc))
shrink_active_list(SWAP_CLUSTER_MAX, zone, sc, priority, 0);
書き戻されたダーティ ページが多すぎる場合は、ここでしばらくスリープします。
throttle_vm_writeout(sc->gfp_mask);
シュリンクアクティブリスト
表示する機能を選択してください
static void shrink_active_list(unsigned long nr_pages, struct zone *zone,
struct scan_control *sc, int priority, int file)
{
unsigned long pgmoved;
int pgdeactivate = 0;
unsigned long pgscanned;
LIST_HEAD(l_hold); /* The pages which were snipped off */
LIST_HEAD(l_inactive);
struct page *page;
struct pagevec pvec;
enum lru_list lru;
struct zone_reclaim_stat *reclaim_stat = get_reclaim_stat(zone, sc);
lru_add_drain();
spin_lock_irq(&zone->lru_lock);
pgmoved = sc->isolate_pages(nr_pages, &l_hold, &pgscanned, sc->order,
ISOLATE_ACTIVE, zone,
sc->mem_cgroup, 1, file);
/*
* zone->pages_scanned is used for detect zone's oom
* mem_cgroup remembers nr_scan by itself.
*/
if (scanning_global_lru(sc)) {
zone->pages_scanned += pgscanned;
}
reclaim_stat->recent_scanned[!!file] += pgmoved;
if (file)
__mod_zone_page_state(zone, NR_ACTIVE_FILE, -pgmoved);
else
__mod_zone_page_state(zone, NR_ACTIVE_ANON, -pgmoved);
spin_unlock_irq(&zone->lru_lock);
pgmoved = 0;
while (!list_empty(&l_hold)) {
cond_resched();
page = lru_to_page(&l_hold);
list_del(&page->lru);
if (unlikely(!page_evictable(page, NULL))) {
putback_lru_page(page);
continue;
}
/* page_referenced clears PageReferenced */
if (page_mapping_inuse(page) &&
page_referenced(page, 0, sc->mem_cgroup))
pgmoved++;
list_add(&page->lru, &l_inactive);
}
/*
* Move the pages to the [file or anon] inactive list.
*/
pagevec_init(&pvec, 1);
lru = LRU_BASE + file * LRU_FILE;
spin_lock_irq(&zone->lru_lock);
/*
* Count referenced pages from currently used mappings as
* rotated, even though they are moved to the inactive list.
* This helps balance scan pressure between file and anonymous
* pages in get_scan_ratio.
*/
reclaim_stat->recent_rotated[!!file] += pgmoved;
pgmoved = 0;
while (!list_empty(&l_inactive)) {
page = lru_to_page(&l_inactive);
prefetchw_prev_lru_page(page, &l_inactive, flags);
VM_BUG_ON(PageLRU(page));
SetPageLRU(page);
VM_BUG_ON(!PageActive(page));
ClearPageActive(page);
list_move(&page->lru, &zone->lru[lru].list);
mem_cgroup_add_lru_list(page, lru);
pgmoved++;
if (!pagevec_add(&pvec, page)) {
__mod_zone_page_state(zone, NR_LRU_BASE + lru, pgmoved);
spin_unlock_irq(&zone->lru_lock);
pgdeactivate += pgmoved;
pgmoved = 0;
if (buffer_heads_over_limit)
pagevec_strip(&pvec);
__pagevec_release(&pvec);
spin_lock_irq(&zone->lru_lock);
}
}
__mod_zone_page_state(zone, NR_LRU_BASE + lru, pgmoved);
pgdeactivate += pgmoved;
__count_zone_vm_events(PGREFILL, zone, pgscanned);
__count_vm_events(PGDEACTIVATE, pgdeactivate);
spin_unlock_irq(&zone->lru_lock);
if (buffer_heads_over_limit)
pagevec_strip(&pvec);
pagevec_release(&pvec);
}
高速メモリ再利用
この機能でget_page_from_freelist()
は、トラバーサルzonelist
処理中に、zone
割り当て前にそれぞれが判断され、zone
割り当て後の空きメモリ量 < しきい値 + 予約されたページ フレーム数の場合、zone
高速メモリ リサイクルが実行されます。
しきい値は
min/low/high
次のいずれかになります。
直接メモリ再利用
直接メモリ再利用は低速割り当てで発生します。低速割り当てでは、node
すべてのノードのkswap
カーネル スレッドが最初に起動され、その後、呼び出しはしきい値get_page_from_freelist
を使用してmin
連続ページ フレームをzonelist
後ろから削除しようとしzone
ます。失敗すると、非同期圧縮が行われます。非同期zonelist
圧縮しきい値の使用が試行され、失敗した場合は、ダイレクト メモリ リサイクルが実行されます。zone
get_page_from_freelist
min
kswapd メモリのリサイクル
kswapd->balance_pgdat()->kswapd_shrink_zone()->shrink_zone()
get_page_from_freelist()
割り当てプロセス中、関数がしきい値から連続ページ フレームを取得できず、割り当てメモリ フラグがマークされていないlow
限り、カーネル スレッドが起動され、その中でメモリのリサイクルが実行されます。zonelist
zone
gfp_mask
__GFP_NO_KSWAPD
kswapd
kswapd