web-dev-qa-db-fra.com

Comment garder le code exécutable en mémoire même sous pression mémoire? sous Linux

Le but ici est de garder en mémoire le code exécutable de tous les processus en cours pendant la pression de la mémoire, sous Linux.
Sous Linux, je peux instantanément provoquer (1 sec) une pression importante sur la mémoire et déclencher le tueur OOM par stress --vm-bytes $(awk '/MemAvailable/{printf "%d\n", $2 + 4000;}' < /proc/meminfo)k --vm-keep -m 4 --timeout 10s (code depuis ici ) Avec 24000 Mo maximum RAM dans un Qubes OS R4.0 Fedora 28 AppVM. EDIT4: Peut-être pertinent, et pourtant j'ai oublié de mentionner, c'est le fait que je n'ai pas activé l'échange (c.-à-d. CONFIG_SWAP n'est pas défini) 

rapports de 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

Les parties intéressantes sont active_file:94 inactive_file:72, elles sont en kilo-octets et sont très basses. 

Le problème ici est que, durant cette période de pression de la mémoire, le code exécutable est relu à partir du disque, ce qui provoque une saturation du disque qui conduit à gelé OS . (mais dans le cas ci-dessus, cela ne se produit que pendant moins d'une seconde) 

Je vois un code intéressant dans le noyau 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;
                }
        }

Je pense que si quelqu'un pouvait indiquer comment changer cela afin qu'au lieu de give them one more trip around the active list nous le transmettions à give them infinite trips around the active list, alors le travail devrait être fait. Ou peut-être y a-t-il un autre moyen? 

Je peux patcher et tester un noyau personnalisé. Je n'ai tout simplement pas le savoir-faire sur ce qu'il faut changer dans le code afin de toujours garder en mémoire le code exécutable actif (ce qui, en fait, permettrait d'éviter les ruptures de disque).

EDIT: Voici ce que j'ai obtenu jusqu'à présent (appliqué sur le noyau 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;
        }

Voir aussi ici sur github car dans le code ci-dessus, les onglets ont été transformés en espaces! ( mirror1 , mirror2 )
J'ai testé le correctif ci-dessus (sur 4000 Mo maximum RAM maintenant, oui 20 G de moins qu'avant!), Même avec une compilation Firefox connue du disque pour écraser le système d'exploitation dans un blocage permanent. arrive plus maintenant (oom-killer tue presque instantanément le (s) processus incriminé (s), également avec la commande stress ci-dessus qui donne maintenant: 

[  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

C'est active_file:26925 inactive_file:76, près de 27 Mo de fichier actif ...
Alors, je ne sais pas à quel point c'est bon. Est-ce que je garde tous les fichiers actifs au lieu de simplement des fichiers exécutables en mémoire? Pendant la compilation de firefox, j'ai eu comme 500meg de Active(file) (EDIT2: mais c'est selon: cat /proc/meminfo|grep -F -- 'Active(file)' qui montre une valeur différente de celle ci-dessus active_file: de dmesg !!!) ce qui me fait douter qu'il s'agissait uniquement d'exes/libs .. .
Peut-être que quelqu'un peut suggérer comment garder UNIQUEMENT du code exécutable (si ce n'est pas ce qui se passe déjà)
Pensées? 

EDIT3: avec le patch ci-dessus, il semble peut-être nécessaire d'exécuter (périodiquement?) Sudo sysctl vm.drop_caches=1 pour libérer de la mémoire périmée (?), De sorte que si j'appelle stress après une compilation firefox, je reçois: active_file:142281 inactive_file:0 isolated_file:0 (142megs), puis je lâche les caches de fichiers (d'une autre manière: echo 1|Sudo tee /proc/sys/vm/drop_caches) puis exécutez à nouveau stress, je reçois: active_file:22233 inactive_file:160 isolated_file:0 (22megs) - je ne suis pas sûr ... 

Résultats sans le patch ci-dessus: ici
Résultats avec le patch ci-dessus: ici

8
Marcus Linsner

AVERTISSEMENT: N'utilisez pas ce correctif si le swap est activé, car deux utilisateurs ont signalé des effets pires. J'ai seulement testé ce patch avec le swap désactivé dans le noyau! (c'est-à-dire que CONFIG_SWAP n'est pas défini)

Jusqu'à nouvel ordre (ou quelqu'un propose quelque chose de mieux), j'utilise (et cela fonctionne pour moi) le correctif suivant afin d'éviter tout blocage du disque/blocage du système d'exploitation lorsque vous êtes sur le point de manquer de mémoire et donc le tueur OOM se déclenche dès que possible (max 1 sec): 

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;
    }

Malheureusement, les tabulations ci-dessus ont été converties en espaces, donc si vous voulez le patch brut, c'est ici

Ce correctif ne supprime pas les pages Active(file) lorsque la mémoire est insuffisante et ne provoque donc pas kswapd0 (mais vu dans iotop comme chaque programme lui-même) pour relire les pages exécutables de chaque processus en cours chaque fois qu'il existe un commutateur de contexte dans l'ordre pour permettre au programme de (continuer à) s'exécuter. Ainsi, une tonne de disques durs est évitée et le système d'exploitation ne reste pas figé. 

Ce qui précède a été testé avec le noyau 4.18.5 (et maintenant à l’essai 4.18.7) à l’intérieur du dom0 de Qubes OS 4.0 (Fedora 25) et de tous les ordinateurs virtuels (Fedora 28) que j’utilise. 

Pour la première version de ce correctif, qui fonctionne aussi (apparemment) aussi, voyez la variable EDIT à la question même dont il s'agit d'une réponse. 

UPDATE: Après avoir utilisé ce correctif pendant un certain temps sur un ordinateur portable ArchLinux avec 16G RAM (moins de 512 Mo réservé à la carte graphique intégrée) et sans échange (désactivé dans le noyau également), je peux dire que le système peut fonctionner mémoire insuffisante plus tôt que sans le9d.patch (rév. 3), et ainsi OOM-killer se déclenche pour Xorg ou chrome ou autre alors qu’il n’aurait pas pu le faire sans le correctif. Et donc, comme une solution qui semble fonctionner pour moi jusqu’à présent, j’exécute echo 1 > /proc/sys/vm/drop_caches chaque fois que le numéro Active(file) dans/proc/meminfo est supérieur à 2G, à savoir 2000000 Ko (par exemple, obtenir le nombre de Ko via ce code: grep 'Active(file):' /proc/meminfo|tr -d ' '|cut -f2 -d:|sed 's/kB//') et faire cette vérification avec un sleep 5 après. Mais dernièrement, afin de compiler firefox-hg dans/tmp, qui est tmpfs et qui utilise finalement 12G et s’assure qu’il ne soit pas tué par MMO, j’utilise 500 000 au lieu de 2000000 Ko. C’est mieux que de geler l’ensemble du système (c’est-à-dire sans le9d.patch), ce qui serait arrivé dans cette affaire de compilation firefox. Sans cette vérification, Active(file) n’est pas supérieur à 4G, mais c’est suffisant pour supprimer OOR de Xorg si quelque chose demande plus de mémoire, comme dans ce cas de compilation firefox ou même lors de la copie de plusieurs gigaoctets via Midnight Commander (si je me souviens bien de cela). 

2
Marcus Linsner