Brutus Processus
Niveau :
Résumé : ps ; top ; htop ; atop ;
Suite à un article sur les processus, j’ai trouvé une excellente série d’article vous permettant de découvrir le fonctionnement interne du noyau sur les processus et la mémoire :
- memory-translation-and-segmentation
- getting-physical-with-memory
- anatomy-of-a-program-in-memory
- how-the-kernel-manages-your-memory
- page-cache-the-affair-between-memory-and-files
Maintenant, essayons de résumer la gestion des processus d’un point de vue utilisateur et développeur.
Naissance, vie et mort d’un processus
Un processus est créé lorsque l’appel système fork est appelé et seulement dans ce cas. Les autres méthodes possibles se basent toutes sur fork (et clone, mais il ne faut pas le dire). Fork ne fait qu’une chose (et il le fait bien), il duplique un processus existant, entièrement, à l’identique, y compris ses droits.
Ensuite, durant sa vie un processus peut être modifié de nombreuses façons :
- Changement d’utilisateur
- Changement de code
- Changement de père
- Changement de priorité
- etc.
Enfin un processus meurt dans 2 cas :
- Il se termine tout seul comme un grand (appel système exit)
- Il reçoit un signal qui lui est fatal
Informations sur un processus
Informations générales
Pour récupérer des informations sur un processus vous disposez de plusieurs commandes. Ces commandes ou toutes pour source /proc/<pid> qui est un répertoire virtuel peuplé directement par le noyau.
Les commandes les plus courantes sont :
- ps : liste des processus et de leurs caractéristiques
- htop : liste dynamique des processus et de ce qu’ils consomment
- pgrep : récupération d’une liste de processus par expression régulière
- pidof : récupération du pid d’un processus recherché
- fuser : informations sur les file descriptor d’un processus
- lsof : idem
- pmap : afficher le mapping mémoire d’un processus
Données sur l’activité
Un processus a une activité somme toute limitée, c’est pourquoi il est parfaitement faisable de tout tracer. Le plus évident est de tracer les appels système, mais cela ne révèle que ses interactions avec le reste du monde. Il est aussi possible de tracer les appels de fonctions voire chacune des instructions.
- strace : liste les appels système du processus
- ltrace : liste les appels de fonction de bibliothèques dynamiques (.so) du processus
- pstack : affiche la pile d’appel du processus
- gdb : avec gdb on peut tout savoir et même modifier l’action d’un processus. gdb utilise l’appel système ptrace pour cela.
Agir sur un processus
Les actions sur un processus peuvent être effectuées par plusieurs entités :
- Le processus lui -même
- Le processus père du processus
- Un autre processus ayant les droits pour effectuer une action (c’est-à-dire même propriétaire ou root … en l’absence de patch sur le noyau)
Les actions qu’on peut effectuer sur un processus sont toutes liées à un appel système. En général il existe une commande shell fournissant une fonctionnalité équivalente.
Envoi d’un signal
Les signaux permettent de communiquer de façon basique avec un processus. Il peuvent être envoyé par un autre processus ou par le noyau. man 1 kill vous indique la liste des signaux et l’action par défaut associée pour les processus qui ne les surchargent pas.
Pour envoyer un signal à un processus il existe plusieurs commandes (qui utilisent toutes l’appel système kill) :
- kill : envoyer un signal à un processus connaissant son pid
- killall : envoie un signal à tous les processus portant un certain nom
- pkill : envoie un signal aux processus matchant une expression régulière
- ctrl-z : envoie le signal STOP au processus en avant plan du shell en cours
- fg, bg : envoie le signal CONT à un processus stoppé du shell en cours
Répartir les tâches
Les processus peuvent être plus ou moins consommateurs de ressource processeur. La partie du noyau nommée ordonnanceur (scheduler) s’occupe d’allouer ces ressources aux différents processus.
Pour influencer l’activité de l’ordonnanceur, il existe plusieurs méthodes. Tout d’abord chaque processus dispose d’une priorité, le niveau de nice. Pour altérer son propre niveau, il y a l’appel système nice qui a donné la commande nice. Pour altérer celui d’un autre processus il y a l’appel système setpriority qui a donné la commande renice.
L’ordonnanceur a la charge de répartir l’activité de plusieurs processus, de plus en plus souvent entre plusieurs processeurs. Il est possible d’influencer la répartition des processus entre processeurs avec la commande taskset (utilisant l’appel système sched_setaffinity). Cette commande permet de limiter un processus à un ou plusieurs processeurs donnés.
Il existe aussi avec CFS, un moyen de contrôler finement le temps alloué à chaque processus à travers /sys/kernel/uids ou à travers les cgroups (voir Documentation/scheduler/sched-design-CFS.txt).
De plus il existe dans le noyau un deuxième ordonnanceur qui, cette fois, organise les tâches d’accès au disque. Si c’est le CFQ qui a été choisi (car il est modifiable), il est possible de l’influencer. Le grand intérêt de ceci est de limiter les processus qui pourraient phagocyter les autres en faisant énormément d’accès disque (pensez aux backups). L’appel système ioprio_set est implémenté dans la commande ionice pour gérer cette fonctionnalité. Lisez Documentation/block/ioprio.txt pour plus de détails.
Gestion des droits
Il n’est pas possible de changer les droits d’un processus de l’extérieur. Seul lui-même en est capable. Mais le mécanisme standard d’héritage lors du fork et de changement de droits lors de l’exec permet de faire tout ce qu’on veut.
Il n’existe qu’un seul cas où les droits peuvent être augmentés (en supposant l’absence de patch de sécurité spécifique) : lancer la commande exec sur un fichier disposant du bit suid (et éventuellement de capabilities).
Ensuite pour réduire ses droits, le processus peut utiliser :
- L’appel système chroot qui a donné la commande chroot
- L’appel système setXXuid qui est utilisé dans pam (commande login par exemple)
- Les capabilities à travers l’appel système setcap qui permet de limiter la liste des appels systèmes disponibles pour un processus (ceci est une option du noyau)
Devenir indépendant
Un processus reçoit des signaux mortels lorsque son tty est coupé. C’est pourquoi un processus peut vouloir obtenir son indépendance.
Il appelle alors l’appel système setsid qui le détache de son terminal. De plus il crée un nouveau groupe de processus pour lui et ses fils, ce qui en pratique le détache de son père (lequel ne pourra donc plus le tuer lorsqu’il mourra).
La commande setsid fait la même chose en ligne de commande.
Limitations
Il est possible d’imposer des limitations à un processus ainsi qu’à ses fils, il existe un appel système pour cela nommée setrlimit. Bash propose une implémentation en ligne de commande pour cet appel nommée ulimit.
Les limitations incluent des limitations en nombre de fichiers ouverts, en consommation CPU, en occupation mémoire … vous trouverez les détails dans le manuel de bash à la commande ulimit.
IPC et mémoire partagée
Une ancienne méthode chamanique utilisée pour communiquer entre processus est constituée des IPC (inter process communication). Les ipc permettent à des processus de :
- s’envoyer des messages (appels système msgXXX)
- poser des sémaphores, une technique de lock pour l’accès à des données (appels systèmes semXXX)
- partager de la mémoire (appels système shmXXX)
Ces 3 catégories de méthodes génèrent des données qui sont indépendantes des processus de par le fait qu’elle sont destinées à d’autres processus. C’est pourquoi lorsqu’un processus utilisant des ipc plante, il laisse des traces dans le système. Ces traces consomment de la mémoire inutilement et parfois bloquent d’autres processus.
C’est pourquoi les commandes ipcs et ipcrm permettent de manipuler ces données de façon extérieures. Pensez à lire le manuel, c’est très court et vous pourrez en profiter pour monitorer les données en question.
Communication avec le processus
Enfin, un processus est isolé et communique avec le reste du monde de très peu de façons différentes :
- avec des file descriptor (réseau, fichier, pipe, tty, device …)
- à travers la mémoire partagée et les IPC
- avec des signaux
- par la ligne de commande (en lecture seule)
- et avec quelques appels système ayant une action sur le système lui-même