Gestion des process et des schedulers
Contents
Software version | Kernel 2.6.32+ |
---|---|
Operating System | Red Hat 6.3 Debian 7 |
Website | Kernel Website |
Last Update | 04/09/2012 |
Others |
1 Les processus
Pour lister les processus, c'est très simple :
ps |
ps aux |
Si l'on souhaites que sélectionner certaines colonnes :
ps |
ps axo pid,comm,stat --sort=stat |
Voici les 6 modes opératoires que nous pouvons trouver :
- Exécution en mode utilisateur
- Exécution en mode kernel
- Prêt à être lancé
- En sommeil
- Nouvellement créer, pas encore prêt à être lancé et pas en sommeil
- Problème lors de la fermeture (zombie)
Voici la liste officielle des état des process :
- TASK_RUNNING: Lorsqu'un process est en train de tourner ou prêt à être lancé
- TASK_INTERRUPTABLE: Cet état est un état bloquant qui attends un événement ou un signal depuis un autre process
- TASK_UNINTERRUPTABLE: Cet état est un état bloquant, le process est obligé de se fermer car le matériel était en attente d'un signal qui n'a jamais été reçu
- TASK_STOPPED: Une fois que le process est terminé, cet état apparait. Le process peut être redémarré.
- TASK_ZOMBIE: Dans cet état le process a été arrêter et les informations seront toujours disponible dans la liste des processus
2 Les caches processeurs
Les caches mémoire sont organisés en lignes. Chaque ligne correspond à un espace mémoire. Tous les ordinateurs ont des caches différents avec des instructions processeurs différentes (suivant les processeurs) dont l'I-cache, le D-cache, l'Altivec etc...
Sur un système ayant plusieurs processeurs/cores, chaque coeur à son propre cache qui est associé à un contrôleur. Lorsqu'un processeur fait référence à la mémoire principale, chaque contrôleur va d'abord vérifier si la requête ne figure pas dans son cache pour répondre à la demande. Suivant si la répondre existe (cache hit) ou n'existe pas (cache miss), la réponse sera très rapide, ou prendra un peu plus de temps car il faudra aller taper la mémoire afin d'être ramené au cache.
Le contrôleur de cache continent un tableau avec tous les entrées pour chaque ligne présent dans le cache. Le contrôleur utilise des tags et des flags pour donner un status a chaque lignes présentes dans le cache. Les processeurs lisent et écrivent leur cache en mémoire. Dans le cas d'une écriture, le cache peut être configuré comme :
- write-through : lorsqu'une ligne du cache est mise à jour, la mémoire principale doit également l'être.
- write-back : il n'y a pas d'écriture dans le cache et dans la mémoire principale tant que la ligne de cache n'est pas libérée.
Le write back est bien plus performant que le write throught. Sur les plateformes x86 sous Linux, c'est le write-back cache qui est utilisé. Chaque page mémoire a un bit pour désactiver ce cache des pages et le write-back cache.
Sur des systèmes multi processeurs, il y a besoin de maintenir une certaine cohérence entre les caches. Lorsqu'un cache met à jour un espace mémoire, il doit avertir l'occupation de cet espace aux autres caches. Ceci s'appelle le cache snooping et c'est le hardware qui gère cette partie là.
NUMA (pour Non Uniform Memory Access ou Non Uniform Memory Architecture) est une méthode de cache snooping, signifiant respectivement accès mémoire non uniforme et architecture mémoire non uniforme) est un système multiprocesseur dans lequel les zones mémoire sont séparées et placées en différents endroits (et sur différents bus). Vis-à-vis de chaque processeur, les temps d'accès diffèrent donc suivant la zone mémoire accédée.
Le système NUMA a été conçu pour pallier les limites de l'architecture SMP dans laquelle tout l'espace mémoire est accessible par un unique bus engendrant des problèmes d'accès concurrents par les différents processeurs. C'est particulièrement nécessaire pour les systèmes ayant de nombreux processeurs.[3]
2.1 Les types
Il existe aujourd'hui 3 types de cache :
- L1 (Level 1) : Les caches de niveau 1 (Fully associative cache) sont les plus flexibles et également ceux qui coutent le plus cher car ils requièrent beaucoup de circuits pour leur implémentation. Il ne peut contenir que quelques kB
- L2 (Level 2) : Les caches de niveau 2 (Set associative cache), ils sont un bon compromis (coût/rapidité) entre le L1 et le L3.
- L3 (Level 3) : Les caches de niveau 3 (Cache). Ce cache est le moins rapide de tous, mais tout de même des accès bien plus rapide que la RAM : ~8n/sec
Aujourd'hui, les mémoire les plus rapides sont les registres processeurs. Ces registres vont à la même vitesse que l'horloge processeur.
Pour connaitre vos caches processeurs :
Sur ce processeur, j'ai 32KB de cache en L1 et 6MB de cache en L2. Je ne possède pas de L3 (ni de L4 comme vous pouvez le voir, mais ce cache est très peu répandu). Vous pouvez également regarder sur le site du fondeur et obtenir les informations sur celui ci.
2.2 Localiser les pertes
Il est possible de localiser les pertes de mémoire et l'utilisation des caches processeurs via un outil qui s'appelle Valgrind[4] (Pour avoir un bon tutoriel pour l'utiliser suivez ce lien[5])
Avec Valgrind, il est possible de spécifier le cache à profiler avec --l1, --D1 ou --L2. Attention toute fois, le programme sera plus lent lors d'une analyse de Valgrind.
Un programme apporte apporte de bonnes performances lorsque les accès en cache sont conclus. Le 'cache stride' est utilisé pour faire référence à un certain nombre de mémoire pouvant être caché dans une seule ligne de cache. La majorité des programmes ont tendance à utiliser x blocks mémoire + 1 dans leurs prochains cycles d'exécution. C'est pour cela qu'il est plus intéressant de déplacer du disque vers la mémoire un bloc de données, plutôt que de le faire bit à bit. Lorsqu'un pgramme accès plusieurs fois aux mêmes donnée en mémoire pendant une certaine période de temps, cela s'appelle la "Temporal locality of reference".
Certains processeurs disposent d'un accès spéciale à la mémoire pour passer à travers le cache. Dans certains cas, si le programme obtient beaucoup de "cache miss", des ralentissement se feront sentir. C'est pourquoi cette option est disponible dans certains processeurs.
3 Optimisations de compilation
Il est possible de faire du code optimisé avec GCC. Par défaut c'est désactivé pour des soucis de temps de compilation et pour produire du code d'erreur plus facile à débugger.
Voici quelques options[6] :
- O0: Ce niveau (la lettre "O" suivie d'un zéro) désactive entièrement l'optimisation, c'est le niveau par défaut si aucun niveau -O n'est spécifié dans les variables CFLAGS ou CXXFLAGS. Votre code ne sera pas optimisé : ce n'est généralement pas ce qui est voulu.
- O1: C'est le niveau le plus classique d'optimisation. Le compilateur va essayer de générer un code plus rapide et plus léger sans prendre plus de temps à compiler. C'est relativement classique, mais cela devrait fonctionner dans tous les cas. que le travail soit fait.
- O2: Un étape au-dessus du -O1. C'est le niveau recommandé d'optimisation sauf si vous avez des besoins spécifiques. Le niveau -O2 va activer quelques options en plus de celles du -O1. Avec le niveau -O2, le compilateur va tenter d'augmenter les performances du code sans faire de compromis sur la taille et sans prendre trop de temps à compiler.
- O3: Il s'agit du plus haut niveau d'optimisation possible mais aussi du plus risqué. Le temps de compilation sera plus long avec cette option qui en fait ne devrait pas être utilisée de façon globale avec GCC 4.x. Le comportement de GCC a changé de façon significative depuis la version 3.x. Dans la version 3.x, -O3 a montré que son utilisation conduisait à des temps d'exécution marginaux plus rapides qu'avec -O2, mais ce n'est plus le cas avec GCC 4.x. Compiler tous vos paquets avec -O3 produira des plus gros binaires qui demanderont plus de mémoire et va augmenter de façon significative les étranges erreurs de compilation ou provoquer des comportements inattendus pour les programmes (y compris des erreurs). Les inconvénients sont plus nombreux que les avantages ; souvenez-vous du principe des rendements décroissants. L'utilisation du niveau -O3 n'est pas recommandé pour GCC 4.x.
- Os: Ce niveau va optimiser votre code en taille. Il active toutes les options du niveau -O2 qui n'influent pas sur la taille du code généré. Il peut être utile pour les machines qui ont une taille très limitée d'espace libre sur le disque dur et/ou qui ont des processeurs avec une petite taille de cache. Toutefois, ce niveau peut tout à fait causer d'autres problèmes, c'est pourquoi il est filtré par beaucoup d'ebuilds dans l'arbre. L'utilisation de -Os n'est pas recommandé.
4 Les Run Queues
Le kernel créer 2 run queues pour chaque core, une "active" et une "expired". Les 2 run queues sont des tableaux avec des listes de liens entre eux dont une représente le niveau de priorité des taches. Lorsqu'un process devient "runnable", il est placé dans la queue "active". Lorsque dans l'active queue une tache a dépassé son délai d'expiration, une nouvelle priorité est calculée et lui est assignée, puis un lien est placé vers celui ci dans l'expired queue. Lorsque tous les process de l'active queue ont dépassés leur délai, toutes les tâches sont déplacées dans l'expired queue et celle ci devient la run queue.
Le scheduler du kernel fait de son mieux pour maintenir de bonnes performances et des temps de réponses correcte lorsqu'il y a plusieurs processeurs et un grand nombre de process.
4.1 Les priorités
Le kernel dispose de de 140 niveaux de priorité allant de 0 à 139 (de la plus haute à la plus faible). Les priorités de 0 à 99 correspondent à du temps réel. Pour les 40 derniers, ce sont les processus dynamique ordinaires. La priorité standard étant 120 qui correspond à ce que l'on voit généralement : 0. Les priorités visibles vont donc de -20 à +20 qui correspondent de 99 à 139.
Ce que vous devez savoir sur les schedulers :
- SCHED_FIFO pour un processus temps réel non préemptible,
- SCHED_RR pour un processus temps réel préemptible,
- SCHED_OTHER pour un processus ordinaire (non temps réel).
4.1.1 SCHED_FIFO
Pour modifier la priorité d'un processus :
chrt |
chrt -f [1-99] binary_path |
4.1.2 SCHED_RR
Pour modifier la priorité d'un processus :
chrt |
chrt -r [1-99] binary_path |
4.1.3 SCHED_OTHER
Ce scheduler est utilisé soit par le kernel, soit par un utilisateur qui fera varier sa priorité suivant certains critères. Chaque processus lancé à une priorité à 0 par défaut qui peut être changé avec les outils nice (au lancement de l'applicatif) ou renice (une fois l'application lancée). Le scheduler va analyser les process qui sont en sommeil depuis trop longtemps et va leur augmenter la priorité de -5 afin de leurs données une priorité plus élevée et qu'ils se lancent plus rapidement. A l'inverse, les process qui passent leur temps à tourner sont pénaliser de +5. Le scheduler va également donner un coup de boost à des process qui demandent à ce que d'autres process se lancent tel que les interfaces graphiques.
5 Voir l'état des process
Bien évidement, vous connaissez la commande top qui vous permet de voir tous les process de la machine. Il existe aussi cela pour voir la priorité et la politique d'un scheduler (indiquez le numéro du PID, ici 8998) :
chrt |
> chrt -p 8998 pid 8998's current scheduling policy: SCHED_OTHER pid 8998's current scheduling priority: 0 |
Vous avez également la commande ps :
ps |
ps axo pid,comm,rtprio,policy ou ps -Amo user,pid,tid,psr,pcpu,pri,vsz,rss,stat,time,comm |
6 References
- ^ http://www.linux-tutorial.info/modules.php?name=MContent&pageid=84
- ^ http://en.wikipedia.org/wiki/Cache_coherence
- ^ http://fr.wikipedia.org/wiki/Non_Uniform_Memory_Access
- ^ http://valgrind.org/
- ^ http://www.unixgarden.com/index.php/gnu-linux-magazine/corriger-votre-utilisation-memoire-avec-valgrind
- ^ http://www.gentoo.org/doc/fr/gcc-optimization.xml