[LINUX] Quand le fichier mmap (2) sera-t-il mis à jour? (2)

Dernière fois décrit deux types de méthodes de détection pour écrire dans la zone mMap, et trois types en détail (1a méthode de balayage de table de page, 1b). Méthode de numérisation de page physique, méthode de capture de 2 erreurs d'écriture) a été prévue. Dans cet article, je vais examiner le code du système d'exploitation de l'OSS largement utilisé pour voir quelle méthode est utilisée ou quelle méthode est utilisée.

Pour Linux

Le code lié à la mémoire virtuelle Linux se trouve dans le répertoire de l'arborescence source mm. Le code de remplacement de page peut être trouvé sur vmscan.c.

Une caractéristique du code mm Linux est que la partie indépendante du modèle exploite directement la table des pages. Bien entendu, la structure de la table des pages varie en fonction du modèle, mais elle est exploitée via la macro définie dans la partie dépendante du modèle. Je pense que c'est une caractéristique rare que même la structure du tableau de page à plusieurs étapes apparaisse dans la partie indépendante du modèle. On pense que cela est dû au fait que Linux était à l'origine un noyau dédié à x86, et parce qu'il est relativement nouveau, les MMU excentriques ont été éliminées et ont été développées après que seules des MMU avec des configurations similaires aient été développées. Être terminé.

Tableau des pages de numérisation

La macro d'opération MMU x86 est [arch / x86 / include / asm / pgtable.h](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch Il est défini dans /x86/include/asm/pgtable.h) et ainsi de suite. La macro qui vérifie si le bit D d'une page est défini est pte_dirty ().

vmscan est un code très complexe. L'échange de pages fonctionne de deux manières, l'une est lorsqu'un processus d'arrière-plan (kthread) s'exécutant à l'intérieur du noyau appelé kswapd s'exécute, et l'autre est lorsque vous essayez d'obtenir de la mémoire sans Linux. Lors de l'exécution au premier plan (récupération directe) lorsqu'il n'y a pas (très peu) de mémoire libre Les deux finiront par [shrink_node ()](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/vmscan.c?h=v5 Venez à l'endroit appelé .7 # n2683).

Il n'y a aucun endroit qui appelle directement ou indirectement pte_dirty () même si vous courez après cela (c'est douloureux comme ça). Par conséquent, on peut voir que Linux semble être une méthode de capture des défauts d'écriture.

erreur d'écriture et écriture de page

Au cas où, jetons un coup d'œil au code d'erreur d'écriture et à la partie export de la page sale. Le code d'erreur de page Linux passe par la routine d'erreur de page dans la partie dépendante du modèle et passe par handle_mm_fault (). /mm/memory.c?h=v5.7#n4348). A ce moment, en cas d'erreur d'écriture, FAULT_FLAG_WRITE est spécifié dans l'argument flags (bitmap). Après cela, \ _ \ _ handle_mm_fault () → handle_pte_fault () vient (je me fiche de HugePages ou du traitement de la partie supérieure de la table de pages à plusieurs étapes en cours de route). [Lorsque FAULT_FLAG_WRITE et la page sont protégées en écriture (! Pte_write (entrée))](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/memory. c? h = v5.7 # n4230) est [do_wp_page ()](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/memory. Aller à c? H = v5.7 # n2878). Wp_page_shared () Si le fichier est mappé avec MAP_SHARED? h = v5.7 # n2844).

vma-> vm_ops-> page_mkwrite est [filemap_page_mkwrite ()](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/filemap.c?h = v5.7 # n2656), qui met à jour l'horodatage du fichier et rend la page sale (ce dirty est un attribut de page contrairement au bit PTE D dirty) Si une page est mappée à partir de plusieurs processus, il existe plusieurs PTE correspondants et le bit D du PTE dans la table de pages correspondant à l'espace virtuel dans lequel l'écriture a été effectuée est défini. Par exemple, write (2). C'est le PTE dans la table de page dans l'espace noyau qui est sale dans l'appel système et n'est pas soumis à vmscan, mais comme il est clair quand il sera sale, l'attribut de page sale au moment de l'appel système Il peut être défini. De plus, set_page_dirty () gère également la connexion des pages à la liste qui gère les pages sales).

La personne qui écrit cette page sale est une personne nommée bdi flusher. L'exportation par bdi flusher est contrôlée par la variable sysctl vm.dirty_expire_centisecs etc. , Il est conçu pour stocker certaines écritures en mémoire, puis les écrire dans le stockage. Actuellement, il est implémenté en utilisant workqueue. Lorsque les données sont enregistrées dans la file d'attente, la fonction de rappel est exécutée ultérieurement. Dans ce cas, les données sont créées pour chaque périphérique bloc (pour être exact, pour chaque memcg) [struct bdi_writeback (wb)](https: / /git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/backing-dev-defs.h?h=v5.7#n130), la fonction de rappel est wb_workfn ..

Puisque l'avenir est compliqué, je vais l'omettre, mais l'écriture du cache de page est [do_writepages ()](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/ La méthode writepages préparée pour chaque système de fichiers avec page-writeback.c? H = v5.7 # n2336 ([ext4_writepages ()] for ext4](https://git.kernel.org/pub/scm/linux/kernel) /git/torvalds/linux.git/tree/fs/ext4/inode.c?h=v5.7#n2610), pour XFS [xfs_vm_writepages ()](https://git.kernel.org/pub/scm/ linux / kernel / git / torvalds / linux.git / tree / fs / xfs / xfs_aops.c? h = v5.7 # n570) etc.) semble être fait en premier. Ici, nous suivrons generic_writepages (), qui est un usage général (appelé lorsque la méthode writepages pour chaque système de fichiers n'est pas enregistrée, et est également un code de type template). Le corps principal est [write_cache_pages ()](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/page-writeback.c?h=v5.7 # n2127). Écrit le cache de page sale pour le fichier spécifié.

Dans la boucle while, pagevec_lookup_range_tag () recherche les pages de cache sales. La page de structure vec qui contient les résultats est un tableau de longueur fixe de pages de structure qui représentent des pages physiques (longueur 15 /tree/include/linux/pagevec.h?h=v5.7#n15)), la boucle for est pour chaque élément de pagevec. pagevec_lookup_range_tag () est conçu pour rechercher uniquement les pages sales, mais après un traitement tel que le saut si le vidage est effectué sur un autre processeur, etc.

                        if (!clear_page_dirty_for_io(page))
                                goto continue_unlock;

                        trace_wbc_writepage(wbc, inode_to_bdi(mapping->host));
                        error = (*writepage)(page, wbc, data);

Cette partie est le miso. Je verrai clear_page_dirty_for_io () plus tard, mais il est appelé pour supprimer la marque sale parce que je vais l'écrire. writepage est une méthode writepage dépendante du système de fichiers qui écrit des pages comme son nom l'indique.

clear_page_dirty_for_io () est un peu en retard dans le même fichier = v5.7 # n2639). Ce qui suit est Kimo.

                if (page_mkclean(page))
                        set_page_dirty(page);

page_mkclean () est [dans rmap.c](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/rmap.c?h=v5. C'est dans 6 # n986). rmap est une abréviation de reverse map, qui est une structure permettant d'obtenir PTE à partir d'une page physique. Les pages physiques peuvent être référencées par plusieurs PTE, donc pour chacun d'eux [page_mkclean_one ()](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/ Appelez linux.git / tree / mm / rmap.c? H = v5.6 # n1979). Alors enfin

			entry = ptep_clear_flush(vma, address, pte);
			entry = pte_wrprotect(entry);
			entry = pte_mkclean(entry);
			set_pte_at(vma->vm_mm, address, pte, entry);

Je suis arrivé à. En même temps que l'abandon du bit D, le bit R / W est effacé et protégé en écriture. Maintenant, je sais que je n'ai lu que avant de commencer à écrire le cache de page.

Pour NetBSD

NetBSD est un système d'exploitation basé sur 4.4BSD. 4.4BSD semble avoir porté le système de mémoire virtuelle à partir du micro-noyau de CMU Mach (marque), mais il semble qu'il était impossible de continuer à utiliser le système de mémoire virtuelle de Mach avec des exigences différentes, NetBSD a son propre système de mémoire virtuelle Remplacé par. Ce code s'appelle UVM et est détaillé dans la thèse de Charles D. Cranor liée à la [page Documents] du projet NetBSD (https://www.netbsd.org/docs/kernel/uvm.html). (C'est un article d'il y a plus de 20 ans, et il faut noter qu'il est différent de la situation actuelle).

Dans NetBSD UVM, la partie dépendante du modèle (MD: Machine Dependent dans la terminologie NetBSD) est appelée couche pmap. Ce qui apparaît dans la partie indépendante de la machine (MI) n'est pas la table des pages elle-même comme Linux, mais une structure opaque appelée struct pmap (implémentée dans la couche pmap de MD), adresse virtuelle (VA) et physique. Par exemple, adresse (PA). La structure en plusieurs étapes du tableau des pages est masquée dans la couche pmap. UVM a été implémenté pour maintenir l'interface entre le système de mémoire virtuelle Mach et la couche pmap.

Peu de gens connaissent le code source NetBSD, voici donc un petit glossaire.

Le code source NetBSD est géré par CVS, mais il est lié à une ligne spécifique, ce qui suit est Github Mirror Voir //github.com/NetBSD/src/tree/netbsd-9).

Tableau des pages de numérisation

Dans l'interface pmap, c'est pmap_is_modified () pour vérifier si le bit D d'une page est défini. /pmap.h#L332). Puisque l'argument est une page physique, il peut y avoir plusieurs PTE qui pointent vers elle, auquel cas true sera retourné si un PTE a un ensemble de bits D. Un autre PTE pmap_clear_modify () pointe vers une page Efface le bit D de (possible) et retourne en même temps s'il y avait ou non un ensemble PTE (équivalent à page_mkclean () sous Linux). UVM est responsable de la publication des pages par un thread du noyau appelé page daemon (pdaemon). Cependant, loin de là, en fait, même si je grep tout le code source d'UVM, il n'y a aucun endroit qui appelle pmap_is_modified () (ou pmap_clear_modify ()). Par conséquent, il s'avère que NetBSD semble également être une méthode de capture d'erreur d'écriture.

Fait intéressant, NetBSD dispose d'un algorithme de remplacement de page enfichable qui vous permet de choisir entre deux types: Clock et Clock Pro.

écrire le défaut et écrire la page

Au cas où, examinons le code d'erreur d'écriture et la partie export de la page sale. La gestion des erreurs de page NetBSD peut être trouvée dans uvm / uvm_fault.c. En cas d'erreur d'écriture, l'argument access_type contient VM_PROT_WRITE, qui est stocké dans la structure uvm_faultctctx flt.access_type. Selon Commentaire en haut, accédez à la partie où vous avez collé le fichier avec MAP_SHARED dans mMap Le défaut d'écriture de est classé comme CAS 2A. Il est traité dans uvm_fault_lower (). uobj représente le fichier mappé.

Pour les fichiers normaux, uobj-> pgops-> pgo_fault est NULL, uobj-> pgops-> pgo_get est uvn_get () /uvm_vnode.c#L155) et vop_getpages lorsque le système de fichiers est BSD FFS est genfs_getpages () Si vous suivez le code en gardant à l'esprit qu'il s'agit de .c # L102), uvm_fault_lower () → uvm_fault_lower_lookup () /uvm_fault.c#L1764) → uvm_get () → VOP_GETPAGES () → genfs_getpages () Procéder à. genfs_getpages () est sur le point de charger les données du stockage dans le cache de pages. VM_PROT_WRITE de access_type est Reflect dans la variable memwrite. Aussi les horodatages ont été mis à jour. Et ligne 261.

                if (error == 0 && memwrite) {
                        genfs_markdirty(vp);
                }

Ici, vnode est rendu sale et enregistré dans la liste des cibles de réécriture par le synchroniseur décrit plus loin. Il ne semble pas y avoir de partie qui rend la page sale (supprime le drapeau PG_CLEAN des indicateurs de page) (en lisant le processus d'exportation d'une page sale ci-dessous, cela semble certainement inutile). Si vous êtes sale quelque part, faites-le moi savoir.

Ensuite, le processus d'exportation de la page sale. Ceci est fait par un thread du noyau appelé syncer. Le corps principal du synchronisateur est sched_sync (). De là lazy_sync_vnode () → [VOP_FSYNC ()](https://github.com /NetBSD/src/blob/netbsd-9/sys/kern/vnode_if.c#L798) → [ffs_fsync ()](https://github.com/NetBSD/src/blob/netbsd-9/sys/ufs/ ffs / ffs_vnops.c # 325) → ffs_full_fsync ()vflushbuf () → [VOP_PUTPAGES ()](https://github.com/NetBSD/src/blob /netbsd-9/sys/kern/vnode_if.c#L1614) → [genfs_putpages ()](https://github.com/NetBSD/src/blob/netbsd-9/sys/miscfs/genfs/genfs_io.c# Écrit en L777). wapbl est équivalent au journal ext3 / 4, mais ne le disons pas ici. Puisque PGO_CLEANIT est défini dans vflushbuf (), si vous suivez le flux, commencez par démarrer la transaction avec un mécanisme appelé fstrans et [réessayer](https://github.com/NetBSD/src/blob/netbsd-9/ Revenez à sys / miscfs / genfs / genfs_io.c # L885) et while loop entrer. La boucle while semble basculer entre by_list (suivant la liste des pages) et les pages en séquence, en fonction de la densité des pages en mémoire. Dans tous les cas, la page cible actuelle est dans la structure vm_page * pg.

Je suis un peu incertain dans le futur. Ligne 1112

		if (flags & PGO_CLEANIT) {
 			needs_clean = pmap_clear_modify(pg) ||
			    (pg->flags & PG_CLEAN) == 0;
			pg->flags |= PG_CLEAN;
		} else {

Avec pmap_clear_modify (), le bit D de chaque PTE pointant vers pg est abandonné, et le bit D avant l'abandon est reflété dans needs_clean.

De plus, il semble qu'il n'ait été lu que peu de temps auparavant.

			if (cleanall && wasclean &&
			    gp->g_dirtygen == dirtygen) {

				/*
				 * uobj pages get wired only by uvm_fault
				 * where uobj is locked.
				 */

				if (pg->wire_count == 0) {
					pmap_page_protect(pg,
					    VM_PROT_READ|VM_PROT_EXECUTE);
				} else {
					cleanall = false;
				}
			}

wasclean est défini lorsque l'attribut vp_numoutput du vnode est égal à 0. Ce membre a été augmenté au début de la rédaction de la page suivante, mais est-il toujours 0 au moment de venir ici? dirtygen est copié à partir de l'attribut du même nom dans vnode au début de genfs_do_putpages (). Cet attribut a été augmenté à genfs_markdirty (). Si cela semble être une génération sale et qu'une erreur d'écriture se produit sur une autre page du même fichier derrière l'écriture de la page, la condition gp-> g_dirtygen == dirtygen est supprimée. Dans ce cas, le processus d'exclusion de ce vnode de la cible du syncer n'est pas effectué dans le processus ultérieur, donc cela semble certainement être bon.

De manière suspecte, il s'avère que NetBSD autorise également l'écriture de la page lors d'une erreur d'écriture, la salit en même temps et la rend en lecture seule lors de son écriture. Cependant, le bit D est utilisé pour déterminer quelle page du cache de page d'un certain fichier est sale.

Cela fait longtemps, c'est donc la fin de cet article. Dans Article suivant, nous en verrons deux autres et ce sera le dernier. Deux OS d'origines différentes, Linux et NetBSD, ont utilisé la même méthode. Veuillez attendre avec impatience s'il s'agit du courant dominant ou s'il existe d'autres types de systèmes d'exploitation.

Recommended Posts

Quand le fichier mmap (2) sera-t-il mis à jour? (3)
Quand le fichier mmap (2) sera-t-il mis à jour? (2)
Quand le fichier mmap (2) sera-t-il mis à jour? (1)
Prédire quand l'ISS sera visible
Soyez prudent lorsque vous travaillez avec des fichiers texte compressés au format gzip
[Python] Quand les variables sont-elles créées? Quand les instances de classe seront-elles effacées?
Reverse pull Numpy / Pandas (sera mis à jour à tout moment)
Lorsque l'USB n'a pas pu être formaté
Lorsque pydub ne peut pas être installé