这里的目标是在内存不足的情况下,在Linux中将每个正在运行的进程的可执行代码保存在内存中。
在Linux中,我能够立即(1秒)造成高内存压力并通过以下方式触发OOM杀手
stress --vm-bytes $(awk '/MemAvailable/{printf "%d\n", $2 + 4000;}' < /proc/meminfo)k --vm-keep -m 4 --timeout 10s
(来自here的代码)
Qubes OS R4.0 Fedora 28 AppVM内部最大RAM为24000MB。 EDIT4:也许相关,但我忘了提及,是我没有启用交换功能(即未设置CONFIG_SWAP
)
dmesg报告:
[ 867.746593] Mem-Info:
[ 867.746607] active_anon:1390927 inactive_anon:4670 isolated_anon:0
active_file:94 inactive_file:72 isolated_file:0
unevictable:13868 dirty:0 writeback:0 unstable:0
slab_reclaimable:5906 slab_unreclaimable:12919
mapped:1335 shmem:4805 pagetables:5126 bounce:0
free:40680 free_pcp:978 free_cma:0
有趣的部分是active_file:94 inactive_file:72
,它们以千字节为单位,并且非常低。
这里的问题是,在那段内存不足的时期内,正在从磁盘重新读取可执行代码,从而导致磁盘抖动,从而导致frozen OS。 (但在上述情况下,它只会发生不到1秒)
我在内核mm/vmscan.c
:中看到了一个有趣的代码
if (page_referenced(page, 0, sc->target_mem_cgroup,
&vm_flags)) {
nr_rotated += hpage_nr_pages(page);
/*
* Identify referenced, file-backed active pages and
* give them one more trip around the active list. So
* that executable code get better chances to stay in
* memory under moderate memory pressure. Anon pages
* are not likely to be evicted by use-once streaming
* IO, plus JVM can create lots of anon VM_EXEC pages,
* so we ignore them here.
*/
if ((vm_flags & VM_EXEC) && page_is_file_cache(page)) {
list_add(&page->lru, &l_active);
continue;
}
}
我认为,如果有人可以指出如何进行更改,以便我们将其改为give them one more trip around the active list
而不是give them infinite trips around the active list
,那么就应该完成工作。也许还有其他方法?
我可以修补和测试自定义内核。我只是不知道如何更改代码,以便始终将活动的可执行代码保留在内存中(我相信这样可以避免磁盘抖动)。
编辑:到目前为止,这是我正在工作的内容(应用于内核4.18.5):
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 32699b2..7636498 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -208,7 +208,7 @@ enum lru_list {
#define for_each_lru(lru) for (lru = 0; lru < NR_LRU_LISTS; lru++)
-#define for_each_evictable_lru(lru) for (lru = 0; lru <= LRU_ACTIVE_FILE; lru++)
+#define for_each_evictable_lru(lru) for (lru = 0; lru <= LRU_INACTIVE_FILE; lru++)
static inline int is_file_lru(enum lru_list lru)
{
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 03822f8..1f3ffb5 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -2234,7 +2234,7 @@ static void get_scan_count(struct lruvec *lruvec, struct mem_cgroup *memcg,
anon = lruvec_lru_size(lruvec, LRU_ACTIVE_ANON, MAX_NR_ZONES) +
lruvec_lru_size(lruvec, LRU_INACTIVE_ANON, MAX_NR_ZONES);
- file = lruvec_lru_size(lruvec, LRU_ACTIVE_FILE, MAX_NR_ZONES) +
+ file = //lruvec_lru_size(lruvec, LRU_ACTIVE_FILE, MAX_NR_ZONES) +
lruvec_lru_size(lruvec, LRU_INACTIVE_FILE, MAX_NR_ZONES);
spin_lock_irq(&pgdat->lru_lock);
@@ -2345,7 +2345,7 @@ static void shrink_node_memcg(struct pglist_data *pgdat, struct mem_cgroup *memc
sc->priority == DEF_PRIORITY);
blk_start_plug(&plug);
- while (nr[LRU_INACTIVE_ANON] || nr[LRU_ACTIVE_FILE] ||
+ while (nr[LRU_INACTIVE_ANON] || //nr[LRU_ACTIVE_FILE] ||
nr[LRU_INACTIVE_FILE]) {
unsigned long nr_anon, nr_file, percentage;
unsigned long nr_scanned;
@@ -2372,7 +2372,8 @@ static void shrink_node_memcg(struct pglist_data *pgdat, struct mem_cgroup *memc
* stop reclaiming one LRU and reduce the amount scanning
* proportional to the original scan target.
*/
- nr_file = nr[LRU_INACTIVE_FILE] + nr[LRU_ACTIVE_FILE];
+ nr_file = nr[LRU_INACTIVE_FILE] //+ nr[LRU_ACTIVE_FILE]
+ ;
nr_anon = nr[LRU_INACTIVE_ANON] + nr[LRU_ACTIVE_ANON];
/*
@@ -2391,7 +2392,8 @@ static void shrink_node_memcg(struct pglist_data *pgdat, struct mem_cgroup *memc
percentage = nr_anon * 100 / scan_target;
} else {
unsigned long scan_target = targets[LRU_INACTIVE_FILE] +
- targets[LRU_ACTIVE_FILE] + 1;
+ //targets[LRU_ACTIVE_FILE] +
+ 1;
lru = LRU_FILE;
percentage = nr_file * 100 / scan_target;
}
也可以在github上看到here,因为在上面的代码中,制表符转换为空格! (mirror1,mirror2)
我已经测试了上述补丁(现在最大内存为4000MB,是以前的20G!),即使使用了已知的Firefox编译程序,该磁盘也将操作系统永久冻结,并且不再发生(oom-killer是上面的stress
命令现在几乎可以立即杀死令人讨厌的进程),现在会产生:
[ 745.830511] Mem-Info:
[ 745.830521] active_anon:855546 inactive_anon:20453 isolated_anon:0
active_file:26925 inactive_file:76 isolated_file:0
unevictable:10652 dirty:0 writeback:0 unstable:0
slab_reclaimable:26975 slab_unreclaimable:13525
mapped:24238 shmem:20456 pagetables:4028 bounce:0
free:14935 free_pcp:177 free_cma:0
这是active_file:26925 inactive_file:76
,几乎是27兆的活动文件...
所以,我不知道这有多好。我是否将所有活动文件而不只是可执行文件保留在内存中?在Firefox编译过程中,我有500兆的Active(file)
( EDIT2:,但这是根据:cat /proc/meminfo|grep -F -- 'Active(file)'
显示的值与上面dmesg的active_file:
不同! !!)这让我怀疑只是exes / libs ...
也许有人可以建议如何只保留可执行代码?(如果那还没有发生的话)
有什么想法吗?
EDIT3:使用上述补丁,似乎有必要(定期吗?)运行sudo sysctl vm.drop_caches=1
来释放一些过时的内存(?),以便如果我调用{{1} }在执行Firefox编译后,我得到:stress
(142兆),然后删除文件缓存(另一种方式:active_file:142281 inactive_file:0 isolated_file:0
),然后再次运行echo 1|sudo tee /proc/sys/vm/drop_caches
,我得到:stress
(22兆) -我不确定...
答案 0 :(得分:2)
在没有进一步通知之前(或有人提出更好的建议),我正在使用(并且对我而言有效)以下patch,以避免在磁盘快用完时磁盘崩溃或操作系统死机内存,因此OOM杀手尽快触发(最多1秒):
revision 3
preliminary patch to avoid disk thrashing (constant reading) under memory pressure before OOM-killer triggers
more info: https://gist.github.com/constantoverride/84eba764f487049ed642eb2111a20830
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 32699b2..7636498 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -208,7 +208,7 @@ enum lru_list {
#define for_each_lru(lru) for (lru = 0; lru < NR_LRU_LISTS; lru++)
-#define for_each_evictable_lru(lru) for (lru = 0; lru <= LRU_ACTIVE_FILE; lru++)
+#define for_each_evictable_lru(lru) for (lru = 0; lru <= LRU_INACTIVE_FILE; lru++)
static inline int is_file_lru(enum lru_list lru)
{
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 03822f8..1f3ffb5 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -2086,9 +2086,9 @@ static unsigned long shrink_list(enum lr
struct scan_control *sc)
{
if (is_active_lru(lru)) {
- if (inactive_list_is_low(lruvec, is_file_lru(lru),
- memcg, sc, true))
- shrink_active_list(nr_to_scan, lruvec, sc, lru);
+ //if (inactive_list_is_low(lruvec, is_file_lru(lru),
+ // memcg, sc, true))
+ // shrink_active_list(nr_to_scan, lruvec, sc, lru);
return 0;
}
@@ -2234,7 +2234,7 @@ static void get_scan_count(struct lruvec *lruvec, struct mem_cgroup *memcg,
anon = lruvec_lru_size(lruvec, LRU_ACTIVE_ANON, MAX_NR_ZONES) +
lruvec_lru_size(lruvec, LRU_INACTIVE_ANON, MAX_NR_ZONES);
- file = lruvec_lru_size(lruvec, LRU_ACTIVE_FILE, MAX_NR_ZONES) +
+ file = //lruvec_lru_size(lruvec, LRU_ACTIVE_FILE, MAX_NR_ZONES) +
lruvec_lru_size(lruvec, LRU_INACTIVE_FILE, MAX_NR_ZONES);
spin_lock_irq(&pgdat->lru_lock);
@@ -2345,7 +2345,7 @@ static void shrink_node_memcg(struct pglist_data *pgdat, struct mem_cgroup *memc
sc->priority == DEF_PRIORITY);
blk_start_plug(&plug);
- while (nr[LRU_INACTIVE_ANON] || nr[LRU_ACTIVE_FILE] ||
+ while (nr[LRU_INACTIVE_ANON] || //nr[LRU_ACTIVE_FILE] ||
nr[LRU_INACTIVE_FILE]) {
unsigned long nr_anon, nr_file, percentage;
unsigned long nr_scanned;
@@ -2372,7 +2372,8 @@ static void shrink_node_memcg(struct pglist_data *pgdat, struct mem_cgroup *memc
* stop reclaiming one LRU and reduce the amount scanning
* proportional to the original scan target.
*/
- nr_file = nr[LRU_INACTIVE_FILE] + nr[LRU_ACTIVE_FILE];
+ nr_file = nr[LRU_INACTIVE_FILE] //+ nr[LRU_ACTIVE_FILE]
+ ;
nr_anon = nr[LRU_INACTIVE_ANON] + nr[LRU_ACTIVE_ANON];
/*
@@ -2391,7 +2392,8 @@ static void shrink_node_memcg(struct pglist_data *pgdat, struct mem_cgroup *memc
percentage = nr_anon * 100 / scan_target;
} else {
unsigned long scan_target = targets[LRU_INACTIVE_FILE] +
- targets[LRU_ACTIVE_FILE] + 1;
+ //targets[LRU_ACTIVE_FILE] +
+ 1;
lru = LRU_FILE;
percentage = nr_file * 100 / scan_target;
}
@@ -2409,10 +2411,12 @@ static void shrink_node_memcg(struct pgl
nr[lru] = targets[lru] * (100 - percentage) / 100;
nr[lru] -= min(nr[lru], nr_scanned);
+ if (LRU_FILE != lru) { //avoid this block for LRU_ACTIVE_FILE
lru += LRU_ACTIVE;
nr_scanned = targets[lru] - nr[lru];
nr[lru] = targets[lru] * (100 - percentage) / 100;
nr[lru] -= min(nr[lru], nr_scanned);
+ }
scan_adjusted = true;
}
不幸的是,以上将制表符转换为空格,因此,如果要原始补丁程序,则为here。
此修补程序所执行的操作不会在内存压力下逐出Active(file)
页,因此不会导致kswapd0
(但在iotop
中被视为每个程序本身)重新读取每次运行每次有context switch时,该进程的可执行页面都将运行,以允许程序运行(继续)。因此,可以避免大量磁盘颠簸,并且OS不会冻结到爬网中。
以上内容是在Qubes OS 4.0的dom0(Fedora 25)和我使用的所有VM(Fedora 28)中使用内核4.18.5(现在正在测试4.18.7)进行测试的。
对于此补丁程序的first version(显然也可以使用),请参见EDIT
,这是一个答案。
更新:在具有16G RAM(为集成显卡保留512M保留)且没有交换(也已在内核中禁用)的ArchLinux笔记本电脑上使用此修补程序一段时间后,我可以说该系统可以比没有le9d.patch(修订版3)更早地耗尽内存,因此OOM-killer会在没有补丁的情况下触发Xorg或Chrome或其他工具。因此,作为缓解措施,到目前为止似乎对我仍然有效,只要/ proc / meminfo中的echo 1 > /proc/sys/vm/drop_caches
数超过2G(即2000000 KB)(例如,获取KB,其代码为Active(file)
,然后使用grep 'Active(file):' /proc/meminfo|tr -d ' '|cut -f2 -d:|sed 's/kB//'
进行此检查。但是最近为了在/ tmp中编译firefox-hg,它是tmpfs并最终使用12G并确保它不会被OOM杀死,我一直使用500000而不是2000000 KB。这肯定比冻结整个系统(即,在没有le9d.patch的情况下)更好,在这种情况下,这种情况可能会发生。如果没有此项检查,sleep 5
的最高存储容量将不会超过4G,但是如果某些东西想要更多的内存(例如在这种firefox编译情况下,或者甚至只是通过午夜指挥官复制了许多GB数据,则足以杀死Xorg)(如果我记得,正确)。
答案 1 :(得分:1)
要回答这个问题,这是一个简单的/初步的补丁程序,如果它小于256 MiB,则不驱逐Active(file)
(如/proc/meminfo
所示),似乎可以正常运行(无磁盘抖动) linux-stable 5.2.4:
diff --git a/mm/vmscan.c b/mm/vmscan.c
index dbdc46a84f63..7a0b7e32ff45 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -2445,6 +2445,13 @@ static void get_scan_count(struct lruvec *lruvec, struct mem_cgroup *memcg,
BUG();
}
+ if (NR_ACTIVE_FILE == lru) {
+ long long kib_active_file_now=global_node_page_state(NR_ACTIVE_FILE) * MAX_NR_ZONES;
+ if (kib_active_file_now <= 256*1024) {
+ nr[lru] = 0; //don't reclaim any Active(file) (see /proc/meminfo) if they are under 256MiB
+ continue;
+ }
+ }
*lru_pages += size;
nr[lru] = scan;
}
请注意,regression上的某些尚未找到 kernel 5.3.0-rc4-gd45331b00ddb会导致系统冻结(没有磁盘抖动,并且sysrq仍然可以工作),即使没有此操作补丁。
(与此有关的任何新发展都应该在here.内发生)
答案 2 :(得分:0)
cgroups-v2内存控制器中的memory.min参数应该有所帮助。
即,让我引用一下
:“硬内存保护。如果cgroup的内存使用量在其范围内 有效的最小边界,cgroup的内存将不会在 任何条件。如果没有不受保护的可回收内存 可用时,将调用OOM杀手。”
https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html