05 - Processus Florent Madelaine [email protected] ce cours suit très littéralement celui de Gaétan Richard lui-même hérité de nombreux autres intervenants tempus passim L2 – S4 2014/2015 1. Présentation Lancement Rappel. Sur le disque, on dispose de fichiers contenant du code exécutable. Pour pouvoir exécuter le code correspondant, il est nécessaire de disposer du droit d’exécution (bit x). Nous verrons le mécanisme permettant le lancement de nouveau processus en détail plus loin. 1. Présentation 1 / 35 Mise en mémoire (dans la RAM) Le processus dispose d’un espace d’adressage qui lui est propre, alloué et initialisé lorsque le système charge puis exécute le programme associé. 1. Présentation 2 / 35 Mise en mémoire (dans la RAM) Le processus dispose d’un espace d’adressage qui lui est propre, alloué et initialisé lorsque le système charge puis exécute le programme associé. Le processus ne peut donc pas voir l’espace mémoire des autres processus. 1. Présentation 2 / 35 Mise en mémoire (dans la RAM) Le processus dispose d’un espace d’adressage qui lui est propre, alloué et initialisé lorsque le système charge puis exécute le programme associé. Le processus ne peut donc pas voir l’espace mémoire des autres processus. La mémoire du processus est organisée de manière standardisée et contient en particulier : I le code binaire du programme exécuté par le processus ; I des données qui ne varient pas (variables globales) ; et, I lorsque le code est en cours d’exécution des données qui évoluent puisqu’elles concernent le calcul en cours (variables locales, appels en attente de fonctions, leurs arguments etc). 1. Présentation 2 / 35 Mise en mémoire L’organisation en mémoire d’un processus est représentée dans la figure ci-dessous. I env, args : environnement ($HOME ...) et arguments. I stack : la pile (appels, variables locales etc) I heap : variables dont on ne connaı̂t pas la taille en avance (tableau) ou grosses données (image). I data : emplacement des variables globales du programme I text : code binaire du programme. env, args stack heap data text 1. Présentation 3 / 35 Méta-informations d’un processus I un propriétaire, un groupe ; I un identifiant (pid) ; I l’identifiant de son père (ppid) ; I une table des descripteurs de fichiers ; I des informations pour l’ordonnancement (priorité, . . . ) ; I des informations pour les ressources (limites) ; I une table des signaux reçus (cf cours sur les signaux) ; I ... 1. Présentation 4 / 35 bit setuid / setgid Pour déterminer les droits d’un processus, on regarde le propriétaire et le groupe effectif . En général, ceux-ci sont les mêmes que le propriétaire et groupe sauf en cas de bit setuid ou setgid. 1. Présentation 5 / 35 Méta-informations du shell Il est possible d’accéder à ces informations aux travers des variables : I UID et GID contiennent l’identifiant de l’utilisateur et du groupe ; I $ contient le PID ; I PPID contient le PID du père. Il est également possible d’avoir accès à l’identifiant de l’utilisateur par les commandes id et whoami. Il est possible de changer le groupe d’un processus avec les commandes su et sudo. 1. Présentation 6 / 35 Pour les autres processus Il est possible d’obtenir les méta-informations des processus par l’intermédiaire de la commande ps. Il existe également la commande pstree qui permet d’afficher le résultat sous forme d’un arbre. Procfs : Sous Linux, ces informations sont également accessibles par l’intermédiaire du système de fichiers monté dans /proc. Chaque processus disposant d’un répertoire au nom de son pid. 1. Présentation 7 / 35 Getpid and co En python, il existe les fonctions pour récupérer ces informations : I os.getpid(), I os.getppid(), I os.getuid(),os.geteuid(), I os.getgid(),os.getegid(),os.getpgid(pid) Il est également possible de modifier ces valeurs (sous réserve d’en avoir les droits) à l’aide des versions setuid,seteuid, setegid, . . . 1. Présentation 8 / 35 2. États États En simplifiant, un processus peut être dans les états suivants : nouveau prêt actif zombie en attente Un processus peut également être envoyé dans le swap. 2. États 9 / 35 C’est quoi un processus Zombie ? A definition from Wikipedia. The term zombie process derives from the common definition of zombie — an undead person. In the term’s metaphor, the child process has ”died” but has not yet been ”reaped”. Also, unlike normal processes, the kill command has no effect on a zombie process. 2. États 10 / 35 C’est quoi un processus Zombie ? A definition from Wikipedia. The term zombie process derives from the common definition of zombie — an undead person. In the term’s metaphor, the child process has ”died” but has not yet been ”reaped”. Also, unlike normal processes, the kill command has no effect on a zombie process. reap = harvest. The grim reaper 2. États 10 / 35 Processus Zombie Il s’agit normalement d’un processus qui a terminé son traitement mais dont la valeur de retour n’a pas été lue. 2. États 11 / 35 Processus Zombie Il s’agit normalement d’un processus qui a terminé son traitement mais dont la valeur de retour n’a pas été lue. Le processus qui doit lire cette valeur est son père (le processus qui a lancé ce processus). 2. États 11 / 35 Processus Zombie Il s’agit normalement d’un processus qui a terminé son traitement mais dont la valeur de retour n’a pas été lue. Le processus qui doit lire cette valeur est son père (le processus qui a lancé ce processus). Remarque. Parfois, des processus restent longtemps des zombies : dans ce cas, il s’agit a priori d’un problème. Normalement, même si son père meurt, un processus (dit orphelin) est rattaché au processus numéro 1 (init) qui est l’ancêtre de tous les processus. 2. États 11 / 35 C’est quoi le swap ? Traditionnellement il s’agissait d’une partition sur le disque, permettant de prétendre qu’on avait plus de RAM. RAM (prétendue) = RAM + SWAP Depuis que les ordinateurs ont beaucoup de RAM, le swap est devenu plus ou moins désuet. On peu dorénavant utiliser des fichiers swap, pas forcément des partitions. Une utilisation pour les portables : suspend-to-disk. Attention. 6= suspend-to-RAM Un peu d’anglais : swap = échanger. 2. États 12 / 35 Observer les processus La commande top permet d’afficher les informations sur les processus en cours d’exécution sur la machine. Il existe également parfois des variantes plus évoluées de cette commande (htop). 2. États 13 / 35 Limites Il est possible et habituel d’imposer des limites sur les processus. Ces limites peuvent prendre la forme : I du temps CPU utilisé ; I du nombre de fichiers ouverts ; I de la taille des fichiers ouverts ; I de la taille du tas et de la pile ; I du nombre de processus fils ; I ... Il existe deux types de limites : 2. États I les soft limits qui sont celles par défaut d’un processus créé ; I les hard limits qui sont celles maximales que l’utilisateur peut fixer. 14 / 35 Manipulation des limites Les limites en shell se manipulent à l’aide de la commande ulimit. En python, ces fonctionnalités sont disponibles par l’intermédiaire du module resource et notamment des fonctions : 2. États I resource.getrlimit(resource) I resource.setrlimit(resource, limits) 15 / 35 3. Ordonnancement Principes Un ordinateur possède un (ou quelques) processeur(s). Chaque processeur peut être composé d’une ou plusieurs unité(s) de calcul : les cœurs Le système d’exploitation permet d’avoir évidement plus de processus que juste le nombre de cœurs. Comme on l’a déjà évoqué, il s’agit d’un système multi-tâches : sur chaque cœur on donne l’illusion d’une exécution parallèle aux utilisateurs en commutant rapidement entre différents processus. Fonctionnement. Le système d’exploitation partage une unité de calcul entre plusieurs processus, une gestion qu’on appelle l’ordonnancement. 3. Ordonnancement 16 / 35 Découpage Une fois le processus sur une unité de calcul, il existe plusieurs cas dans lesquels le processus s’en sépare : I le processus s’arrête (devient zombie) ; I le processus le demande explicitement (sleep) ; I le processus est en attente (cas d’un appel système, exemple : accès disque) ; I le système choisit d’arrêter le processus (système préemptif). Dans ce cas, le système d’exploitation détermine un nouveaux processus à affecter à l’unité de calcul. 3. Ordonnancement 17 / 35 Modèles simples Il existe plusieurs techniques pour déterminer comment affecter les tâches aux unités de calculs. On choisit l’une ou l’autre en fonction de l’utilisation. I FIFO : on affecte dans l’ordre d’apparition ; I Round Robin : on effectue un bloc de chaque processus à tour de rôle ; I Priorité : chaque processus dispose d’une valeur de priorité et on choisit le processus de plus forte priorité à chaque fois. 3. Ordonnancement 18 / 35 Modèle évolués Le modèle actuel dans la plupart des systèmes d’exploitation est basé sur les principes suivants : I chaque processus possède une priorité de base ; I cette priorité augmente quand le processus est inactif et diminue quand il est actif (le taux de changement dépend de la priorité de base) ; I le système choisit parmi les processus de plus forte priorité. 3. Ordonnancement 19 / 35 Nice Sous UNIX, il est possible de changer la priorité de base d’un processus. Cette valeur est comprise entre −20 et 20 (plus le nombre est bas, plus la priorité est grande). Le défaut est 0. Il est toujours possible de diminuer la priorité d’un processus, l’augmentation demande des droits particuliers (root). Avec le shell, il est possible d’effectuer ces opérations à l’aide des commandes nice au lancement et renice durant l’exécution. Sous python, cette fonctionnalité est accessible par l’appel système : os.nice(increment). 3. Ordonnancement 20 / 35 Sleep / yield Un processus peut demander à dormir pendant un certain temps à l’aide de la commande sleep. En python, cette fonctionnalité est accessible par la commande time.sleep(secs). En python, il existe aussi le mot-clé yield qui permet d’interrompre une fonction. Il est alors possible de la reprendre à l’aide de la méthode .next(). 3. Ordonnancement 21 / 35 Exemple def yrange(n): i = 0 while i < n: yield i i += 1 3. Ordonnancement 22 / 35 Exemple def yrange(n): i = 0 while i < n: yield i i += 1 >>> y = yrange(3) >>> y <generator object yrange at 0x401f30> >>> y.next() 0 >>> y.next() 1 >>> y.next() 2 >>> y.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 3. Ordonnancement 22 / 35 4. Manipulation Duplication de processus Le seul moyen de créer un nouveau processus est de dupliquer un processus existant. Cette opération est réalisé à l’aide de l’appel système fork. p (pid=13) père (pid=13) 4. Manipulation fils (ppid=13) 23 / 35 En python La fonction os.fork() permet de dupliquer des processus. La fonction renvoie : I 0 pour le fils ; I le pid du fils pour le père. Exemple : from os import * swith fork(): case 0: ... fils ... exit 0 else: ... père ... 4. Manipulation 24 / 35 Recouvrement Pour lancer un autre programme, la méthode consiste à remplacer le programme courant par un autre : le recouvrement. Ceci s’effectue à l’aide de l’appel système exec. p exec p’ 4. Manipulation 25 / 35 En python Il existe différentes versions suivant que l’on souhaite donner les arguments un à un (l) ou sous forme de tableau (v) ; que l’on souhaite avoir l’environnement en argument (e) ; ou que l’on souhaite chercher dans le path (p). os.execl(path, arg0, arg1, ...), os.execle(path, arg0, arg1, ..., env), os.execlp(file, arg0, arg1, ...), os.execlpe(file, arg0, arg1, ..., env), os.execv(path, args), os.execve(path, args, env), os.execvp(file, args), os.execvpe(file, args, env) Exemple : ... os.execlp("monscript.sh", "monscript.sh", "arg1") ... jamais atteint ... 4. Manipulation 26 / 35 Fin d’un processus Pour terminer un processus, il est possible de sortir à l’aide de la fonction exit. Cette fonction prend un entier en argument. Le processus devient alors un zombie. Cette valeur peut-être récupérée par le père à l’aide de la commande wait qui attend au besoin la fin d’un processus. Le processus zombie correspondant est alors définitivement détruit. père fils exit wait 4. Manipulation 27 / 35 En python Il existe plusieurs versions de wait selon les options que l’on souhaite : I os.wait() ; I os.waitpid(pid, options) ; I os.wait3([options]) ; I os.wait4(pid, options) ; 4. Manipulation 28 / 35 Orphelins Principe : tout processus possède un père. Exception : le premier processus (init) n’a pas de père. Orphelin : quand le père d’un processus meurt, soit celui-ci est détruit ; soit, il reste en vie et est alors récupéré par init. 4. Manipulation 29 / 35 Fonctionnement en shell En shell, chaque commande lancée effectue un fork et un exec de la commande. Il attend ensuite la fin de l’exécution de la commande. fork exec exit wait 4. Manipulation 30 / 35 Sous-shell et arrière plan Il est possible de lancer un sous-shell en utilisant les parenthèses. Les commandes sont alors réalisées dans un processus fils du shell. Il est aussi possible de lancer une commande en arrière-plan en ajoutant le symbole & à la fin d’un commande. Dans ce cas, le shell n’attend pas la fin de l’exécution (pas de wait). 4. Manipulation 31 / 35 5. Threads Remarques sur les processus Lors de l’utilisation d’un fork, on doit dupliquer physiquement les méta-données du processus ainsi que sont contenu. Les deux processus obtenu sont donc complètement indépendants. Note. Dorénavant, de nombreux systèmes d’exploitations utilisent du copy on write qui permet d’éviter le copie physique de la mémoire tant que cela n’est pas nécessaire. 5. Threads 32 / 35 Principe Dans certains cas, on souhaiterait disposer du parallélisme sans avoir recours à la lourdeur des processus. Pour ce cas, il existe des threads (processus légers) qui permettent d’effectuer du parallélisme à l’intérieur d’un même processus. Dans ce cas, on a deux unités en train de s’exécuter qui partagent la même mémoire. 5. Threads 33 / 35 En python En python, il existe le module threading permet de gérer les threads. La création se fait à l’aide du constructeur : threading.Thread(group=None, target=None, name=None, args=(), kwargs={}). Il est ensuite possible de manipuler ces éléments de façon similaire au processus. 5. Threads 34 / 35 Chercher les problèmes Comme la mémoire est partagée, les threads posent de nombreux problèmes de concurrence. Ceci fera l’objet d’un cours par la suite. La combinaison usuelle est de d’abord faire des fork et ensuite des threads. Utiliser les deux en même temps demande une bonne réflexion pour comprendre les comportements obtenus (lorsque ceux-ci ne sont pas buggés). 5. Threads 35 / 35