Maison  >  Article  >  Opération et maintenance  >  Introduction à la gestion de la mémoire Linux

Introduction à la gestion de la mémoire Linux

青灯夜游
青灯夜游avant
2019-03-28 13:52:213547parcourir

Le contenu de cet article est de présenter la gestion de la mémoire Linux pour vous permettre de comprendre les connaissances pertinentes de la gestion de la mémoire Linux. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il vous sera utile.

Sous Linux, lorsque vous utilisez les commandes top, vmstat, free et autres pour vérifier l'utilisation de la mémoire du système ou du processus, vous voyez souvent la mémoire buff/cache, swap,ava Mem, etc. Que signifient-ils ? Cet article parlera de la gestion de la mémoire sous Linux et répondra à cette question.

Parler de la gestion de la mémoire sous Linux, c'est en fait discuter de l'implémentation de la mémoire virtuelle sous Linux. Je ne suis pas un expert du noyau, donc cet article ne présentera que quelques éléments conceptuels et n'entrera pas dans les détails d'implémentation. pas être précis.

Au début, la mémoire physique était relativement limitée. Les gens espéraient que l'espace mémoire que les programmes pouvaient utiliser pourrait dépasser la mémoire physique réelle, c'est pourquoi le concept de mémoire virtuelle est apparu. La signification de la mémoire virtuelle a largement dépassé l'idée originale.

1. Mémoire virtuelle

La mémoire virtuelle est une technologie permettant à Linux de gérer la mémoire. Cela fait penser à chaque application qu'elle dispose d'un espace mémoire disponible indépendant et continu (un espace d'adressage continu et complet), mais en fait, il est généralement mappé sur plusieurs segments de mémoire physique, et certains sont temporairement stockés sur disque externe puis chargés. en mémoire en cas de besoin.

La taille de l'adresse virtuelle que chaque processus peut utiliser est liée au nombre de bits du processeur. Sur un système 32 bits, la taille de l'espace d'adressage virtuel est de 4 Go. Sur un système 64 bits, elle est de 4 Go. 2^64=? (Je n'arrive pas à comprendre). La mémoire physique réelle peut être bien inférieure à la taille de l'espace d'adressage virtuel.

Les adresses virtuelles sont étroitement liées aux processus. La même adresse virtuelle dans différents processus ne pointe pas nécessairement vers la même adresse physique, cela n'a donc aucun sens de parler d'adresses virtuelles sans quitter le processus.

Remarque : De nombreux articles sur Internet assimilent la mémoire virtuelle à l'espace d'échange. En fait, la description n'est pas assez rigoureuse. L'espace d'échange n'est qu'une partie de l'ensemble de la mémoire virtuelle.

2.La relation entre la mémoire virtuelle et la mémoire physique

La suivant Le tableau exprime la relation entre eux de manière très intuitive

  进程X                                                                      进程Y
+-------+                                                                  +-------+
| VPFN7 |--+                                                               | VPFN7 |
+-------+  |       进程X的                                 进程Y的           +-------+
| VPFN6 |  |      Page Table                              Page Table     +-| VPFN6 |
+-------+  |      +------+                                +------+       | +-------+
| VPFN5 |  +----->| .... |---+                    +-------| .... |<---+  | | VPFN5 |
+-------+         +------+   |        +------+    |       +------+    |  | +-------+
| VPFN4 |    +--->| .... |---+-+      | PFN4 |    |       | .... |    |  | | VPFN4 |
+-------+    |    +------+   | |      +------+    |       +------+    |  | +-------+
| VPFN3 |--+ |    | .... |   | | +--->| PFN3 |<---+  +----| .... |<---+--+ | VPFN3 |
+-------+  | |    +------+   | | |    +------+       |    +------+    |    +-------+
| VPFN2 |  +-+--->| .... |---+-+-+    | PFN2 |<------+    | .... |    |    | VPFN2 |
+-------+    |    +------+   | |      +------+            +------+    |    +-------+
| VPFN1 |    |               | +----->| FPN1 |                        +----| VPFN1 |
+-------+    |               |        +------+                             +-------+
| VPFN0 |----+               +------->| PFN0 |                             | VPFN0 |
+-------+                             +------+                             +-------+
 虚拟内存                               物理内存                               虚拟内存


PFN(the page frame number): 页编号

Lorsqu'un processus exécute un programme, il doit lire les instructions du processus dans la première mémoire puis l'exécuter. Il est utilisé pour obtenir le. instructions. C'est l'adresse virtuelle. Cette adresse est déterminée lorsque le programme est lié (la plage d'adresses de la bibliothèque dynamique sera ajustée lorsque le noyau charge et initialise le processus. Afin d'obtenir les données réelles, le CPU doit). convertir l'adresse virtuelle en adresse physique. Le processeur doit convertir l'adresse. La table des pages du processus est utilisée et les données de la table des pages sont conservées par le système d'exploitation.

Remarque : le code du noyau Linux utilise des adresses physiques réelles lors de l'accès à la mémoire, il n'y a donc pas de conversion de l'adresse virtuelle en adresse physique, seuls les programmes de la couche application en ont besoin.

Afin de faciliter la conversion, Linux divise la mémoire virtuelle et la mémoire physique en pages de taille fixe. La taille générale des pages de mémoire des systèmes x86 est de 4 Ko, et chaque page se voit attribuer un numéro unique. Il s'agit du numéro de page (PFN).

Comme le montre la figure ci-dessus, le mappage entre les pages de mémoire virtuelle et de mémoire physique se fait via la table des pages. La mémoire virtuelle des processus X et Y est indépendante l'une de l'autre, et la table des pages est également indépendante. La mémoire physique est partagée entre eux. Le processus peut accéder à volonté à son propre espace d'adressage virtuel, tandis que la table des pages et la mémoire physique sont gérées par le noyau. Lorsqu'un processus doit accéder à la mémoire, le processeur traduira l'adresse virtuelle en une adresse physique basée sur la table des pages du processus, puis y accédera.

Remarque : toutes les pages de l'espace d'adressage virtuel ne sont pas associées à une table de pages correspondante uniquement après que l'adresse virtuelle a été attribuée au processus, c'est-à-dire après que le processus a appelé une fonction similaire à malloc. , le système ajoutera-t-il un enregistrement à la table de pages pour l'adresse virtuelle correspondante. Si le processus accède à une adresse virtuelle qui n'est pas associée à la table de pages, le système émettra un signal SIGSEGV, provoquant la sortie du processus. une erreur de segment se produit souvent lorsque nous accédons à des pointeurs sauvages. En d'autres termes, bien que chaque processus dispose d'un espace d'adressage virtuel 4G (système 32 bits), seuls les espaces d'adressage appliqués au système peuvent être utilisés, et des erreurs de segment se produiront lors de l'accès aux espaces d'adressage non alloués. Linux ne mappera l'adresse virtuelle 0 nulle part, donc lorsque nous accéderons au pointeur nul, une erreur de segmentfault sera certainement signalée.

3. Avantages de la mémoire virtuelle

● Espace d'adressage plus grand : il est continu, ce qui facilite l'écriture et la liaison des programmes.

● Isolation des processus : il n'y a aucune relation entre les adresses virtuelles des différents processus, donc le fonctionnement d'un processus n'affectera pas les autres processus.

● Protection des données : chaque élément de mémoire virtuelle a un correspondant les attributs de lecture et d'écriture peuvent protéger les segments de code du programme contre la modification, les blocs de données contre l'exécution, etc., ce qui augmente la sécurité du système

● Mappage de la mémoire : avec la mémoire virtuelle, les fichiers (fichiers exécutables ou bibliothèques dynamiques) sur le disque peuvent être directement mappés sur l'espace d'adressage virtuel. Cela peut permettre une allocation retardée de la mémoire physique, et uniquement lorsque le fichier correspondant doit l'être. read Lorsque la mémoire est limitée, elle est en fait chargée du disque dans la mémoire. Lorsque la mémoire est limitée, cette partie de la mémoire peut être effacée pour améliorer l'efficacité de l'utilisation de la mémoire physique, et tout cela est transparent pour l'application.

● Mémoire partagée : par exemple, une bibliothèque dynamique n'a besoin que de stocker une copie dans la mémoire, puis de la mapper à l'espace d'adressage virtuel d'un processus différent, donnant ainsi au processus l'impression qu'il en est la propriété exclusive. le fichier. Le partage de mémoire entre les processus peut également être réalisé en mappant la même mémoire physique sur différents espaces d'adressage virtuels du processus

  ● Gestion de la mémoire physique : l'espace d'adressage physique est entièrement géré par le système d'exploitation et le processus ne peut pas être directement alloué et recyclé. De cette façon, le système peut mieux utiliser la mémoire et équilibrer les besoins en mémoire entre les processus

● Autres : Avec l'espace d'adressage virtuel, des fonctions telles que l'espace d'échange et COW (copie en écriture). ) peut être facilement implémenté

4. Table des pages

La table des pages peut être simplement comprise comme une liste chaînée de mappage de mémoire (bien sûr la la structure réelle est très complexe), dont chacun Chaque mappage de mémoire mappe une adresse virtuelle à une ressource spécifique (mémoire physique ou espace de stockage externe). Chaque processus possède sa propre table de pages, qui n'a rien à voir avec les tables de pages des autres processus.

5. Mappage de la mémoire

Chaque mappage de la mémoire est une description d'une section de mémoire virtuelle, y compris la position de départ et la longueur de la mémoire virtuelle. adresse. Autorisations (par exemple si les données de cette mémoire peuvent être lues, écrites et exécutées) et ressources associées (telles que les pages de la mémoire physique, les pages sur l'espace d'échange, le contenu des fichiers sur le disque, etc.).

Lorsqu'un processus demande de la mémoire, le système renverra l'adresse de la mémoire virtuelle, créera un mappage de mémoire pour la mémoire virtuelle correspondante et le placera dans la table des pages, mais le système n'allouera pas nécessairement la mémoire physique correspondante à cette fois-ci. Généralement, la mémoire physique est allouée et associée au mappage mémoire correspondant lorsque le processus accède réellement à cette mémoire. C'est ce qu'on appelle l'allocation différée/allocation à la demande.

Chaque mappage de mémoire possède une balise pour indiquer le type de ressource physique associé. Il est généralement divisé en deux catégories, à savoir anonyme et sauvegardé sur fichier. Au sein de ces deux catégories, il existe des classes plus petites, par exemple. il existe des types de partage et de copie en écriture plus spécifiques sous anonyme, et il existe des types plus spécifiques sauvegardés sur un périphérique sous sauvegardés sur un fichier. Voici ce que représente chaque type :

fichier sauvegardé

Ce type indique que les ressources physiques correspondant au mappage mémoire sont stockées dans des fichiers sur le disque. inclus inclut l'emplacement du fichier, le décalage, les autorisations rwx, etc.

Lorsque le processus accède à la page virtuelle correspondante pour la première fois, parce que la mémoire physique correspondante est introuvable dans le mappage mémoire, le processeur signalera une interruption de défaut de page, puis le système d'exploitation gérera l'interruption. et enregistrez le fichier. Le contenu est chargé dans la mémoire physique, puis le mappage de la mémoire est mis à jour afin que le CPU puisse accéder à cette adresse virtuelle la prochaine fois. Les données chargées en mémoire de cette manière sont généralement placées dans le cache de pages. Le cache de pages sera présenté plus tard.

Les fichiers exécutables du programme général et les bibliothèques dynamiques sont ainsi mappés aux processus de l'espace d'adressage virtuel.

le périphérique sauvegardé

est similaire au fichier sauvegardé, sauf que le backend est mappé à l'adresse physique du disque. Par exemple, lorsque la mémoire physique est échangée. , il sera marqué comme périphérique sauvegardé.

anonyme

Le segment de données et l'espace de pile utilisés par le programme lui-même, ainsi que la mémoire partagée allouée via mmap, ne trouvent pas les fichiers correspondants sur le disque, donc Cette partie de la page mémoire est appelée une page anonyme. La plus grande différence entre une page anonyme et un fichier sauvegardé est que lorsque la mémoire est limitée, le système supprimera directement la mémoire physique correspondant au fichier sauvegardé, car elle pourra être chargée du disque dans la mémoire la prochaine fois que cela sera nécessaire, mais la page anonyme ne peut pas être supprimé et peut uniquement être échangé.

partagé

Plusieurs mappages de mémoire dans la table des pages de différents processus peuvent être mappés à la même adresse physique via des adresses virtuelles (les adresses virtuelles de différents processus peuvent ne pas être les mêmes). idem) Le même) peut accéder au même contenu. Lorsque le contenu de la mémoire est modifié dans un processus, il peut être lu immédiatement dans un autre processus. Cette méthode est généralement utilisée pour obtenir des données partagées à grande vitesse entre des processus (tels que mmap). Lorsque le mappage de mémoire marqué comme partagé est supprimé et recyclé, le décompte de références sur la page physique doit être mis à jour afin que la page physique puisse être recyclée une fois que le décompte devient 0.

copie en écriture

la copie en écriture est basée sur une technologie partagée lors de la lecture de ce type de mémoire, le système n'a pas besoin d'effectuer d'opérations particulières, mais quand. écriture Lors de l'utilisation de cette mémoire, le système générera une nouvelle mémoire et copiera les données de la mémoire d'origine dans la nouvelle mémoire, puis associera la nouvelle mémoire au mappage de mémoire correspondant, puis effectuera l'opération d'écriture. De nombreuses fonctions sous Linux s'appuient sur la technologie de copie sur écriture pour améliorer les performances, comme fork, etc.

Grâce à l'introduction ci-dessus, nous pouvons simplement résumer le processus d'utilisation de la mémoire comme suit :

1. Le processus envoie une demande d'application de mémoire au système

2. adresse virtuelle du processus Si l'espace est utilisé, s'il en reste, attribuez une adresse virtuelle au processus

3. Le système crée le mappage mémoire correspondant (éventuellement multiple) pour cette adresse virtuelle et met dans la table des pages du processus

4. Le système renvoie l'adresse virtuelle au processus, et le processus commence à accéder à l'adresse virtuelle

5. Le CPU trouve le mappage mémoire correspondant. dans la table des pages du processus en fonction de l'adresse virtuelle, mais le mappage Il n'est pas associé à la mémoire physique, donc une interruption de défaut de page se produit

6 Une fois que le système d'exploitation a reçu l'interruption de défaut de page, il l'attribue. mémoire physique réelle et l'associe au mappage de mémoire correspondant

7. Une fois le traitement de l'interruption terminé, le CPU peut accéder à la mémoire

Bien entendu, les interruptions de page manquantes ne se produisent pas à chaque fois. Ils ne sont utilisés que lorsque le système estime qu'il est nécessaire de retarder l'allocation de mémoire, c'est-à-dire plusieurs fois au cours de l'étape 3 ci-dessus. Le système allouera de la mémoire physique réelle et l'associera au mappage de mémoire.

6. Autres concepts

Tant que le système d'exploitation réalise la relation de mappage entre la mémoire virtuelle et la mémoire physique, il peut fonctionner normalement. Mais pour rendre l’accès à la mémoire plus efficace, nous pouvons examiner ici de nombreux éléments liés à la mémoire et à ses fonctions.

MMU (Memory Management Unit)

MMU est un module du CPU qui est utilisé pour convertir l'adresse virtuelle d'un processus en un adresse physique. Simple Pour faire simple, l'entrée de ce module est la table des pages et l'adresse virtuelle du processus, et la sortie est l'adresse physique. La vitesse de conversion des adresses virtuelles en adresses physiques affecte directement la vitesse du système, le CPU inclut donc ce module pour l'accélération.

TLB (Translation Lookaside Buffer)

Comme introduit ci-dessus, l'entrée de MMU est la table des pages, et la table des pages est stockée dans la mémoire. Par rapport au cache du CPU, la vitesse de la mémoire est très lente, donc afin d'accélérer encore la vitesse de conversion des adresses virtuelles en adresses physiques, Linux a inventé le TLB, qui existe dans le cache L1 du CPU et est utilisé. pour mettre en cache les adresses virtuelles trouvées vers les adresses physiques, vérifiez donc le TLB avant la prochaine conversion. S'il y est déjà, il n'est pas nécessaire d'appeler le MMU.

Allouer. pages physiques à la demande

Étant donné que la mémoire physique est bien inférieure à la mémoire virtuelle dans les situations réelles, le système d'exploitation doit allouer la mémoire physique très soigneusement pour maximiser l'utilisation de la mémoire. Une façon d'économiser de la mémoire physique consiste à charger en mémoire uniquement les données correspondant à la page virtuelle actuellement utilisée. Par exemple, dans un grand programme de base de données, si vous utilisez uniquement des opérations de requête, il n'est pas nécessaire de charger les segments de code responsables de l'insertion, de la suppression, etc. dans la mémoire. Cette méthode peut économiser beaucoup de mémoire physique. appelées pages de mémoire physique. L'allocation à la demande peut également être appelée chargement différé.

Le principe de mise en œuvre est très simple, c'est-à-dire que lorsque le CPU accède à une page de mémoire virtuelle, si les données correspondant à la page de mémoire virtuelle n'ont pas été chargées dans la mémoire physique, le CPU en informera le système d'exploitation qu'une erreur de page s'est produite, puis le système d'exploitation est responsable du chargement des données dans la mémoire physique. Étant donné que le chargement des données en mémoire prend du temps, le processeur n'attendra pas là-bas, mais planifiera d'autres processus. Lorsqu'il planifiera le processus la prochaine fois, les données seront déjà dans la mémoire physique.

Linux utilise principalement cette méthode pour charger des fichiers exécutables et des bibliothèques dynamiques. Lorsque le programme commence à être programmé pour être exécuté par le noyau, le noyau mappe les fichiers exécutables et les bibliothèques dynamiques du processus à l'espace d'adressage virtuel de. le processus, et chargez uniquement la petite partie des données qui seront utilisées immédiatement dans la mémoire physique, et les autres parties ne seront chargées que lorsque le CPU y accédera.

Espace d'échange

Lorsqu'un processus doit charger des données dans la mémoire physique, mais que la mémoire physique réelle a été utilisée, le système d'exploitation Certaines pages de la mémoire physique doivent être récupérées pour répondre aux besoins du processus en cours.

Pour les données de mémoire sauvegardées sur fichiers, c'est-à-dire que les données dans la mémoire physique proviennent de fichiers sur le disque, le noyau supprimera directement cette partie des données de la mémoire pour libérer plus de mémoire la prochaine fois. Lorsqu'un processus doit accéder à cette partie des données, elle est chargée du disque en mémoire. Cependant, si cette partie des données a été modifiée et n'a pas été écrite dans le fichier, alors cette partie des données devient des données sales. Les données sales ne peuvent pas être supprimées directement et ne peuvent être déplacées que vers l'espace d'échange. (Les fichiers exécutables et les fichiers de bibliothèque dynamique ne seront pas modifiés, mais les fichiers disque mappés en mémoire via mmap+private peuvent être modifiés. La mémoire mappée de cette manière est spéciale. Avant modification, elle est sauvegardée sur un fichier. Après modification, elle ne sera plus modifié. Il devient anonyme avant d'être réécrit sur le disque)

对于anonymous的内存数据,在磁盘上没有对应的文件,这部分数据不能直接被删除,而是被系统移到交换空间上去。交换空间就是磁盘上预留的一块特殊空间,被系统用来临时存放内存中不常被访问的数据,当下次有进程需要访问交换空间上的数据时,系统再将数据加载到内存中。由于交换空间在磁盘上,所以访问速度要比内存慢很多,频繁的读写交换空间会带来性能问题。

关于swap空间的详细介绍请参考Linux交换空间

共享内存

有了虚拟内存之后,进程间共享内存变得特别的方便。进程所有的内存访问都通过虚拟地址来实现,而每个进程都有自己的page tables。当两个进程共享一块物理内存时,只要将物理内存的页号映射到两个进程的page table中就可以了,这样两个进程就可以通过不同的虚拟地址来访问同一块物理内存。

从上面的那个图中可以看出,进程X和进程Y共享了物理内存页PFN3,在进程X中,PFN3被映射到了VPFN3,而在进程Y中,PFN3被映射到了VPFN1,但两个进程通过不同的虚拟地址访问到的物理内存是同一块。

访问控制

page table里面的每条虚拟内存到物理内存的映射记录(memory mapping)都包含一份控制信息,当进程要访问一块虚拟内存时,系统可以根据这份控制信息来检查当前的操作是否是合法的。

为什么需要做这个检查呢?比如有些内存里面放的是程序的可执行代码,那么就不应该去修改它;有些内存里面存放的是程序运行时用到的数据,那么这部分内存只能被读写,不应该被执行;有些内存里面存放的是内核的代码,那么在用户态就不应该去执行它;有了这些检查之后会大大增强系统的安全性。

huge pages

由于CPU的cache有限,所以TLB里面缓存的数据也有限,而采用了huge page后,由于每页的内存变大(比如由原来的4K变成了4M),虽然TLB里面的纪录数没变,但这些纪录所能覆盖的地址空间变大,相当于同样大小的TLB里面能缓存的映射范围变大,从而减少了调用MMU的次数,加快了虚拟地址到物理地址的转换速度。

Caches

为了提高系统性能,Linux使用了一些跟内存管理相关的cache,并且尽量将空闲的内存用于这些cache。这些cache都是系统全局共享的:

  • Buffer Cache
    用来缓冲块设备上的数据,比如磁盘,当读写块设备时,系统会将相应的数据存放到这个cache中,等下次再访问时,可以直接从cache中拿数据,从而提高系统效率。它里面的数据结构是一个块设备ID和block编号到具体数据的映射,只要根据块设备ID和块的编号,就能找到相应的数据。

  • Page Cache
    这个cache主要用来加快读写磁盘上文件的速度。它里面的数据结构是文件ID和offset到文件内容的映射,根据文件ID和offset就能找到相应的数据(这里文件ID可能是inode或者path,本人没有仔细去研究)。

从上面的定义可以看出,page cache和buffer cache有重叠的地方,不过实际情况是buffer cache只缓存page cache不缓存的那部分内容,比如磁盘上文件的元数据。所以一般情况下和page cache相比,Buffer Cache的大小基本可以忽略不计。

当然,使用cache也有一些不好的地方,比如需要时间和空间去维护cache,cache一旦出错,整个系统就挂了。

七、总结

有了上面介绍的知识,再来看看我们刚开始提出来的问题,以top命令的输出为例:

KiB Mem :   500192 total,   349264 free,    36328 used,   114600 buff/cache
KiB Swap:   524284 total,   524284 free,        0 used.   433732 avail Mem

KiB Mem代表物理内存,KiB Swap代表交换空间,它们的单位都是KiB。

total、used和free没什么好介绍的,就是总共多少,然后用了多少,还剩多少。

buff/cached代表了buff和cache总共用了多少,buff代表buffer cache占了多少空间,由于它主要用来缓存磁盘上文件的元数据,所以一般都比较小,跟cache比可以忽略不计;cache代表page cache和其它一些占用空间比较小且大小比较固定的cache的总和,基本上cache就约等于page cache,page cache的准确值可以通过查看/proc/meminf中的Cached得到。由于page cache是用来缓存磁盘上文件内容的,所以占有空间很大,Linux一般会尽可能多的将空闲物理内存用于page cache。

avail Mem indique la quantité de mémoire physique qui peut être utilisée pour la prochaine allocation du processus. Cette taille est généralement un peu plus grande que libre, car en plus de l'espace libre, le système peut également libérer immédiatement de l'espace.

Alors, comment déterminer si l'utilisation actuelle de la mémoire est anormale ? Voici les points de référence suivants :

● La valeur de Mem free est relativement faible, et la valeur de buff/cache est également faible
La valeur relativement faible de free ne signifie pas nécessairement qu'il y en a. un problème, car Linux fera de son mieux pour que la mémoire soit utilisée pour le cache des pages, mais si la valeur de buff/cache est également faible, cela signifie que la mémoire est limitée et que le système n'a pas assez de mémoire pour le cache actuel. le déploiement du serveur est une application qui nécessite une lecture et une écriture fréquentes des disques, comme un serveur FTP, l'impact sur les performances sera alors très important.

● La valeur du Swap utilisée est relativement grande.
Cette situation est plus grave que celle ci-dessus. Dans des circonstances normales, le swap devrait être rarement utilisé. Une valeur utilisée relativement grande indique que l'espace de swap est utilisé. plus. Si vous voyez fréquemment des échanges d'entrée/sortie via la commande vmstat, cela signifie que la mémoire système est sérieusement insuffisante et que les performances globales ont été sérieusement affectées

Tutoriels vidéo associés recommandés : "Tutoriel Linux. "

Ce qui précède représente l'intégralité du contenu de cet article, j'espère qu'il sera utile à l'étude de chacun. Pour un contenu plus passionnant, vous pouvez prêter attention aux colonnes de didacticiels pertinentes du site Web PHP chinois ! ! !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer