Как сохранить исполняемый код в памяти даже под давлением памяти? в линуксе
Цель здесь - сохранить исполняемый код каждого запущенного процесса в памяти во время нехватки памяти в Linux.
В Linux я могу мгновенно (1 секунда) вызвать высокое давление памяти и запустить OOM-killer с помощью stress --vm-bytes $(awk '/MemAvailable/{printf "%d\n", $2 + 4000;}' < /proc/meminfo)k --vm-keep -m 4 --timeout 10s
(код отсюда) с максимальным объемом оперативной памяти 24000 МБ в операционной системе Qubes OS R4.0 Fedora 28. РЕДАКТИРОВАТЬ 4: Возможно, уместно, и все же я забыл упомянуть, является тот факт, что у меня не включен своп (т.е. 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
они в килобайтах и очень низки.
Проблема здесь заключается в том, что во время этого периода нехватки памяти исполняемый код перечитывается с диска, вызывая перегрузку диска, что приводит к зависанию ОС. (но в приведенном выше случае это происходит не более 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, потому что в приведенном выше коде вкладки превратились в пробелы! ( зеркало1, зеркало2)
Я тестировал вышеупомянутый патч (на максимальной оперативной памяти 4000 МБ, да на 20 ГБ меньше, чем раньше!) Даже с известной компиляцией 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)'
который показывает другое значение, чем выше active_file:
от dmesg!!!), что заставляет меня сомневаться, что это были только exes/libs...
Может быть, кто-то может подсказать, как сохранить ТОЛЬКО исполняемый код?(Если это не то, что уже происходит)
Мысли?
РЕДАКТИРОВАТЬ3: с вышеупомянутым патчем, возможно, необходимо (периодически?) Запустить sudo sysctl vm.drop_caches=1
освободить немного устаревшую память (?), чтобы, если я позвоню stress
после компиляции Firefox я получаю: active_file:142281 inactive_file:0 isolated_file:0
(142megs) затем удаляют файловые кэши (другой способ: echo 1|sudo tee /proc/sys/vm/drop_caches
) тогда беги stress
опять получаю: active_file:22233 inactive_file:160 isolated_file:0
(22 мг) - я не уверен...
Результаты без вышеуказанного патча: здесь
Результаты с вышеуказанным патчем: здесь
1 ответ
До дальнейшего уведомления (или когда кто-то придумает что-то лучшее), я использую (и это работает для меня) следующий патч, чтобы избежать перегрузки диска или зависания ОС при запуске из памяти и, следовательно, OOM-killer срабатывает как можно быстрее (максимум 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;
}
К сожалению, вышеупомянутые преобразованные вкладки в пробелы, так что если вы хотите сырой патч, он здесь
То, что делает этот патч, не выселяет Active(file)
страницы, когда под давлением памяти и, следовательно, не вызывают kswapd0
(но видно в iotop
как каждая программа) перечитывать исполняемые страницы каждого запущенного процесса каждый раз, когда происходит переключение контекста, чтобы позволить программе (продолжать) работать. Таким образом, можно избежать тонны перебивания диска, и ОС не зависает при сканировании.
Выше был протестирован с ядром 4.18.5 (и теперь тестирование 4.18.7) внутри dom0(Fedora 25) Qubes OS 4.0 и всех виртуальных машин (Fedora 28), которые я использую.
Для первой версии этого патча, который также работает (по-видимому), см. EDIT
на тот самый вопрос, на который это ответ.
Параметр memory.min в контроллере памяти cgroups-v2 должен помочь.
А именно, позвольте мне процитировать:
"Защита жесткой памяти. Если использование памяти cgroup находится в пределах ее эффективной минимальной границы, память cgroup не будет восстановлена ни при каких условиях. Если нет доступной незащищенной исправимой памяти, вызывается OOM killer".
https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
Чтобы ответить на вопрос, вот простой / предварительный патч, чтобы не выселить Active(file)
(как видно в /proc/meminfo
) если он меньше 256 МБ, кажется, что он работает нормально (без перегрузки диска) с 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;
}
Обратите внимание, что некоторые ожидаемые регрессии в ядре 5.3.0-rc4-gd45331b00ddb приведут к зависанию системы (без перезаписи диска, и sysrq все равно будет работать) даже без этого патча.
(любые новые события, связанные с этим, должны происходить здесь.)