Как сохранить исполняемый код в памяти даже под давлением памяти? в Linux


Цель здесь состоит в том, чтобы сохранить исполняемый код каждого запущенного процесса в памяти во время сжатия памяти, в Linux.
В Linux, я могу мгновенно (1 сек) вызвать высокое давление памяти и вызвать OOM-killer с помощью stress --vm-bytes $(awk '/MemAvailable/{printf "%dn", $2 + 4000;}' < /proc/meminfo)k --vm-keep -m 4 --timeout 10s (код из здесь ) с 24000 МБ оперативной памяти внутри Qubes OS R4.0 Fedora 28 AppVM. EDIT4: возможно, уместным, и все же я забыл упомянуть, является тот факт, что у меня не включен swap (т. е. 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, тогда работа должна быть сделана. Или, может быть, есть какой-то другой способ?

Я могу исправить и протестировать пользовательское ядро. У меня просто нет ноу-хау относительно того, что нужно изменить в коде, чтобы всегда держать активный исполняемый код в памяти(что, по моему мнению, позволит избежать дисковой трэшировки).

EDIT: вот что я получил, работая до сих пор (применяется поверх ядра 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 потому что в приведенном выше коде вкладки превратились в пробелы! (mirror1, mirror2 )
Я протестировал вышеописанный патч(на 4000MB max RAM сейчас, да на 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 у меня было около 500meg Active(file)(EDIT2: но это согласно: cat /proc/meminfo|grep -F -- 'Active(file)', который показывает другое значение, чем выше active_file: из dmesg!!!) что заставляет меня сомневаться, что это были только бывшие / либы...
Может быть, кто-то подскажет, как сохранить только исполняемый код ?(если это не то, что уже происходит)
Мысли?

EDIT3: с вышеуказанным патчем, возможно, необходимо (периодически?) выполнить 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 (22megs) - я не уверен...

Результаты без вышеуказанного патча: здесь
Результаты с вышеуказанным патчем: здесь

1 4

1 ответ:

До дальнейшего уведомления(или кто-то придумает что-то получше), я использую (и это работает, для меня) следующий патч , чтобы избежать любого дискового трэша / замораживания ОС, когда вот-вот закончится память и, таким образом, ООМ-киллер срабатывает как можно скорее(максимум 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) внутри Qubes OS 4.0 's dom0 (Fedora 25) и всех виртуальных машин (Fedora 28), которые я использую.

Для первой версии об этом патче, который также работает(по-видимому), смотрите EDIT на тот самый вопрос, на который это ответ.