Si vous mappez un fichier à l'espace mémoire virtuelle avec MAP_SHARED en utilisant mMap (2), ce que tout le monde aime, l'écriture dans cette mémoire sera reflétée dans le fichier. Quand et qui détecte l'écriture dans cette mémoire?
Compte tenu du mécanisme de mmap (2) et de la fonction du processeur, il existe à peu près deux mécanismes possibles. Avant cela, j'expliquerai brièvement le fonctionnement de l'OS et les fonctions du CPU.
L'un des rôles importants du système d'exploitation est de donner à chaque tâche (processus dans la terminologie Unix) un espace mémoire virtuel indépendant. L'adresse 100 d'un processus se voit (sauf indication contraire) allouée à une mémoire physique différente de l'adresse 100 d'un autre processus. La MMU (Memory Management Unit) est le matériel qui mappe l'espace de mémoire virtuelle et l'espace de mémoire physique dans un processeur qui exécute Linux complet, tel qu'un PC, un serveur ou un smartphone. De nombreux processeurs modernes ont une MMU intégrée, mais dans le passé, ils étaient parfois connectés en externe à proximité du processeur. Linux suppose une MMU de type pagination qui gère la mémoire en la divisant en pages d'une certaine taille.
Dans de nombreux processeurs grand public actuels tels que x86_64, ARM et Power, la taille de page est en principe de 4 Ko. Puisque 4KiB est de 12 bits, l'adresse de mémoire virtuelle de 64 bits peut être représentée par le décalage de page des 12 bits inférieurs et le numéro de page (numéro de page virtuelle) de l'espace de mémoire virtuelle des 52 bits supérieurs.
MMU utilise une table appelée table de pages placée en mémoire pour convertir des pages dans l'espace mémoire virtuelle en pages dans l'espace mémoire physique. Une table de pages peut être considérée comme un tableau indexé par des numéros de page virtuels. En réalité, l'espace mémoire virtuelle est peu utilisé, et la taille de la table de pages est compressée en la rendant multi-étapes (4 à 5 étapes en x86-64). De plus, dans x86-64, seuls 48 bits sont réellement utilisés sur 64 bits. Chaque entrée de table de page x86-64 (PTE: Page Table Entry) a une largeur de 64 bits, le côté supérieur est le numéro de page physique (PFN: Page Frame Number) et les 12 bits inférieurs sont diverses informations sur la page dans un bitmap Avoir. Ce qui est important dans cet article, ce sont les informations sur la face inférieure. x86-64 contient les informations suivantes, mais d'autres architectures (avec des noms différents) ont des informations similaires.
Bit P (Présent): lorsqu'il est mis à 1, il indique que la page physique correspondant à la page virtuelle existe (mappée). Si le bit P est remis à 0, l'accès à cette page virtuelle est une erreur de page car aucune mémoire physique n'est allouée. En passant, dans x86, la vulnérabilité L1TF est que la mémoire physique correspondant au PFN représenté par le bit supérieur peut être accédée de manière spéculative même lorsque le bit P est effacé. .. Lorsque le bit P est effacé, il est permis par les spécifications de stocker les informations utilisées par le système d'exploitation au niveau supérieur, et sous Linux, les informations de la position dans la zone d'échange peuvent être stockées.
Bit D (sale): défini automatiquement par le CPU lorsque la page virtuelle dispose d'un accès en écriture. Il est de la responsabilité du système d'exploitation de l'effacer, car il n'est pas effacé automatiquement.
Bit A (consulté): défini automatiquement par la CPU lors de l'accès à la page. Il est de la responsabilité du système d'exploitation de l'effacer, car il n'est pas effacé automatiquement.
Bit R / W (lecture / écriture): lorsqu'il est effacé, la page est en lecture seule et l'écriture n'est pas autorisée.
Lorsqu'un processus tente d'accéder à une page dont le bit P est effacé, le système d'exploitation est notifié avec l'exception Page Fault (#PF dans la terminologie x86). Le système d'exploitation peut recevoir #PF et réécrire la table de pages de manière appropriée pour reprendre l'exécution du processus, ou il peut provoquer une erreur en tant qu'accès mémoire illégal (généralement un signal d'erreur de segmentation dans le système d'exploitation Unix). Lorsqu'un processus essaie d'écrire sur une page où le bit R / W est effacé, #PF (erreur d'écriture) est notifié au système d'exploitation et la table de pages peut être réécrite de manière appropriée et l'exécution du processus peut être reprise. Et cela peut être une erreur.
Une caractéristique majeure de D et A est que le processeur peut changer. La CPU ne modifie pas les autres parties de la table des pages, uniquement le système d'exploitation. En outre, bien que la CPU de D et A puisse être modifiée de 0 à 1, elle n'est pas effacée de 1 à 0.
Dans la plupart des systèmes d'exploitation basés sur Unix tels que Linux, les données de stockage sont mises en cache dans la mémoire, mais cela se fait page par page, c'est ce qu'on appelle un cache de page. La mmap du fichier est réalisée en mappant ce cache de page sur l'espace mémoire virtuel. L'écriture normale (2) (y compris pwrite (2), writev (2), etc.) crée un cache de page avant l'écriture (c'est-à-dire lit tout le contenu de la page à partir du stockage) et réécrit ce cache de page. .. Au moment de l'écriture (2) etc., le système d'exploitation peut comprendre que le cache de page a été réécrit et que la cohérence avec l'original sur le stockage a été perdue. Une page qui n'est pas cohérente avec l'original est appelée une page sale, mais la page sale est écrite dans le stockage à un moment approprié et prend le même état que l'original, c'est-à-dire une page vierge. Sous Linux, l'ancien bdflush, pdflush et le récent bdi flusher sont en charge de cette écriture, et le paramètre de réglage vm.dirty_ratio de sysctl, qui est devenu un secret des sociétés SIer, et ainsi de suite, modifient le comportement de ce flusher bdi. C'est une chose.
Dans le cas de mmap, la situation devient difficile. Le processus écrit dans la mémoire sans autorisation, y compris la zone où le fichier est mappé. Normalement, le système d'exploitation (noyau) ne sait pas quand et où il a été écrit. D'une certaine manière, vous devez détecter ce que le processus a écrit dans la zone mmappée et sur quelle page il a écrit, et rendre cette page sale.
En passant, certaines anciennes implémentations utilisent un cache de page pour mMap (2), mais utilisent un mécanisme de cache tampon séparé pour write (2) et autres, ce qui est superflu entre les deux caches. Il y avait également des problèmes tels que la copie et la corruption du contenu des fichiers lors de l'utilisation de mMap (2) et de l'écriture (2) en parallèle (jusqu'à Linux-2.2, NetBSD-1.5, etc.). Actuellement, il a été amélioré d'utiliser le cache de page pour write (2) etc., et un tel problème ne se produit pas.
Sur la base de la fonction MMU que j'ai écrite plus tôt, je pense qu'il existe deux façons de compléter l'écriture dans la zone mMap.
Trouvez celui avec le bit D de PTE réglé et rendez-le sale. Effacez le bit D lors de l'exportation vers le stockage. Scannez et trouvez toutes les tables de pages (correspondant à l'espace virtuel de tous les processus). 1b. Pour chaque page physique, recherchez le PTE qui pointe vers elle.
Effacez le bit R / W, interceptez le #PF généré (erreur d'écriture), rendez la page sale et en même temps définissez le bit R / W pour redémarrer le processus. Effacez le bit R / W lors de l'écriture dans le stockage.
J'ai déjà fait des recherches sur le tableau des pages et je regarde Un peu. Le cache de page est libéré s'il n'est pas utilisé pendant une longue période (sinon il remplit la mémoire), mais il est utilisé pour déterminer qu'il n'a pas été utilisé pendant une longue période. Cela s'appelle [Algorithme de remplacement de page](https://ja.wikipedia.org/wiki/Page Replacement Algorithm).
Par exemple, l'algorithme suivant détermine le cache à libérer. Tout d'abord, enregistrez toutes les pages utilisées dans le cache de pages dans la liste. Les pages nouvellement sécurisées en tant que cache de pages seront ajoutées à la fin de la liste. Lorsque vous manquez de mémoire libre, parcourez cette liste depuis le début pour trouver le bit A du PTE qui fait référence à la page. Les pages qui restent effacées sont considérées comme n'ayant pas été utilisées pendant un certain temps, elles sont donc libérées, mais si elles sont définies, elles se déplacent à la fin de la liste. En conséquence, la liste sera organisée avec des pages qui sont utilisées moins récemment vers le début et des pages qui sont fréquemment utilisées vers la fin (LRU: ordre des moins récemment utilisés). Bien que j'aie écrit sur le cache de page ici, les pages à enregistrer dans la zone d'échange peuvent être déterminées en effectuant le même traitement dans la mémoire anonyme.
En réalité, il est courant de le supprimer de la liste, de l'ajouter à une autre liste dite inactive et d'attendre un moment. La liste expliquée en premier est appelée liste active et le rapport entre actif, inactif et libre est maintenu constant. De plus, si ce qui précède est implémenté de manière simple, l'analyse sera fortement biaisée, de sorte qu'un dispositif permettant de scanner efficacement et équitablement l'ensemble sera réalisé. De plus, il existe de petites différences selon le système d'exploitation et la version, telles que quand et dans quelles conditions l'analyse est exécutée, combien de pages sont déplacées, combien le cache de page et la mémoire anonyme sont traités de la même manière ou différemment. Il y en a, mais l'opération approximative est comme ça.
Revenons à mmap. La méthode 1a (appelée provisoirement la méthode d'analyse de la table des pages) est une enquête PTE simple. Vous pouvez étudier D bit et A bit en même temps. Les zones qui sont mappées à partir de plusieurs espaces virtuels (fichiers exécutifs, bibliothèques partagées, etc.) seront examinées plusieurs fois au cours d'un tour de toute la mémoire utilisée.
La méthode 1b (temporairement appelée méthode d'analyse de page physique) nécessite une méthode pour vérifier l'espace virtuel et l'adresse virtuelle qui le mappe à partir de la page physique. Comme A bit peut être étudié en même temps, la liste active / inactive mentionnée précédemment peut être utilisée pour tracer toutes les pages physiques en cours d'utilisation. Il semble que la numérisation puisse être optimisée en fonction de la fréquence d'utilisation.
Qu'il s'agisse de 1a ou 1b, vous devez examiner toutes les pages en cours d'utilisation sur une période de temps. Sinon, les pages sales seront indétectables pendant de longues périodes. Cependant, dans le cas de 1a, une optimisation telle que sauter le balayage de l'espace virtuel du processus qui n'a pas été planifié récemment peut être envisagée en coopération avec l'ordonnanceur de processus.
Dans la deuxième méthode (temporairement appelée méthode de capture des erreurs d'écriture), le problème est que le traitement de #PF peut être lourd. Trop d'erreurs d'écriture peuvent ralentir le processus. On sait qu'il y a beaucoup d'accès dans la zone proche de la mémoire (localité d'accès), mais une fois qu'un défaut d'écriture se produit et que le bit R / W est défini, la page est laissée sale pendant un certain temps. En effaçant le bit R / W juste avant l'écriture, il est nécessaire de le réduire en écrivant plusieurs écritures en mémoire à la fois. D'autre part, comme vous pouvez capturer le moment où il devient sale, vous pouvez garantir qu'il sera écrit dans le stockage (commencer à écrire) 30 secondes après qu'il devienne sale, ou maintenir le nombre de pages sales dans un certain niveau. Sera plus facile.
C'est la fin de cet article car il est devenu long. Lorsque j'ai examiné le code source du système d'exploitation de certains OSS, j'ai trouvé le système d'exploitation qui utilise réellement la méthode de numérisation de page physique de 1b et la méthode de capture d'erreur d'écriture de 2, donc [Article suivant] Je voudrais le présenter à https://qiita.com/SIGABRT/items/667b24a809f1575a2640).