Aller au contenu principal

Unix & Shell · L1 · Section 3/7

Processus et jobs

Progression

Points d’expérience : XPSérie de jours consécutifs : · —Progression du module : — / —compris

#Processus et jobs

Un processus est un programme en cours d'exécution. Il possède un identifiant (PID), des descripteurs de fichiers, de la mémoire et un état. Le shell peut lancer des processus au premier plan (le terminal attend) ou en arrière-plan (le terminal rend la main).

#Prérequis et objectifs

Prérequis: bases du shell (codes de sortie, redirections). Objectifs: piloter un job entre premier plan et arrière-plan, choisir le bon signal, et interpréter un code de sortie de la forme 128+n.

#Premier plan, arrière-plan, suspension

Ajoutez & pour démarrer en arrière-plan:

bashbash

1# Lance un serveur HTTP simple au second plan2python -m http.server 8000 &3# Observation: [1] 12345  (numéro de job, PID)4jobs            # [1]+  Running   python -m http.server 8000 &5fg %1           # ramène le job 1 au premier plan

Ctrl+Z suspend le processus de premier plan (signal SIGTSTP), bg %1 le reprend en arrière-plan, fg %1 le ramène au premier plan. Pour détacher durablement un processus d'un terminal, utilisez nohup cmd & ou, mieux, une unité systemd dédiée. Quand vous devez fermer le shell mais garder le job, disown évite l'envoi de SIGHUP.

#Qu'est-ce qu'un processus, exactement ?

Un processus est un programme en cours d'exécution. La plupart des commandes exécutent un programme, et donc lancent un processus; le programme de démarrage, lui, correspond à un processus présent pendant toute la session. Des dizaines ou centaines de processus sont en fonctionnement à tout moment.

Chaque processus est identifié par un numéro entier et associé à un ensemble d'informations appelé son image: le code du programme en cours d'exécution, les données qu'il traite, l'identification des fichiers en cours de traitement et leur état, le répertoire courant, l'identité de son propriétaire, le terminal associé. Le cours distingue deux propriétaires: le propriétaire réel, l'utilisateur qui a lancé le processus, et le propriétaire effectif, l'utilisateur dont les droits s'appliquent. Ils coïncident en général; ils diffèrent quand le processus s'exécute avec des privilèges élevés.

Les états d'un processus sont au nombre de quatre: en attente d'un événement extérieur (une action de l'utilisateur), en attente d'exécution (une tranche de temps), en attente de l'arrivée d'une partie de la mémoire virtuelle, et en exécution — un seul à la fois. Tout se passe comme s'ils s'exécutaient simultanément parce que le processeur est partagé en tranches très courtes.

Tout processus est lancé par un processus père, ce qui dessine un arbre généalogique. Deux conséquences observables: un processus hérite du répertoire courant et des descripteurs de fichiers de son père, et quand un processus se termine, tous ses processus fils se terminent aussi.

bashbash

1echo "PID du shell : $$"     # le shell lui-même2sleep 300 &                  # un fils, lancé en arrière-plan3echo "PID du fils : $!"      # le PID du dernier processus d'arrière-plan

#ps, uptime et top

ps sert à obtenir des informations sur les processus en cours. La forme utilisée dans ce cours est celle du système BSD, dont les paramètres s'écrivent sans tiret initial. Le choix des processus dépend des options: ceux qui appartiennent à l'utilisateur, sauf si l'option a est présente; ceux qui sont associés à un terminal, sauf si l'option x est présente.

bashbash

1ps x         # tous les processus de l'utilisateur2ps aux       # tous les processus de l'ordinateur, en détail3ps aux | less        # indispensable: la sortie de ps aux est volumineuse4ps aux | grep emacs  # ou un filtre ciblé

Sans option, ps affiche le numéro, le terminal associé, l'état, le temps machine utilisé et la commande. Avec l'option u, il ajoute le nom de l'utilisateur et la consommation des ressources en temps et en mémoire.

uptime complète le tableau avec trois informations: la durée de fonctionnement sans arrêt du système, le nombre d'utilisateurs connectés, et la charge moyenne de la dernière minute, des 5 et des 15 dernières minutes.

top affiche les processus les plus actifs, le plus actif en tête. Le haut de la fenêtre reprend la ligne d'uptime, puis la répartition des processus par catégorie, l'utilisation du processeur, l'utilisation de la mémoire et celle de la zone d'échange. La commande est interactive: q quitte, k permet de tuer un processus, u filtre sur un utilisateur. Le TP fait constater qu'essayer de tuer le processus d'un autre utilisateur échoue: c'est le contrôle de propriété du noyau.

#Commandes intrinsèques, exec et coût des processus

Le shell résout un nom de commande dans un ordre strict: fonction du shell, puis opération prédéfinie (commande intrinsèque), puis fichier exécutable trouvé dans PATH. Une commande intrinsèque s'exécute dans le processus du shell et ne crée aucun processus; une commande externe en crée un.

bashbash

1whence cd     # builtin2whence ls     # /usr/bin/ls

exec commande remplace le processus du shell par celui de la commande. L'expérience du TP 5 est sans ambiguïté: lancez gnome-terminal en premier plan depuis un terminal, quittez-le par exit — vous retrouvez le shell d'origine; recommencez avec exec gnome-terminal, et la fenêtre d'origine se ferme, puisqu'elle a été remplacée.

Le coût d'un processus se mesure directement. Le TP 6 compare deux boucles qui affichent les mêmes nombres: l'une incrémente avec la commande externe expr, l'autre avec la construction interne (( )).

bashbash

1nombre=02while [[ $nombre -lt 5000 ]]3do4  echo $nombre5  nombre=$(expr $nombre + 1)     # un PROCESSUS par itération6done
bashbash

1nombre=02while [[ $nombre -lt 5000 ]]3do4  echo $nombre5  ((nombre=$nombre + 1))         # évaluation interne au shell6done

Pour 10 itérations l'écart est invisible; pour 5 000, la première version lance 5 000 processus et devient beaucoup plus lente. La leçon est générale: tout ce qui crée un processus a un coût, et une boucle répète ce coût autant de fois qu'elle itère.

#Fils, terminal et déconnexion

Puisque les fils se terminent avec leur père, tous les processus d'arrière-plan lancés depuis une fenêtre de terminal doivent se terminer quand on tape exit. Le shell signale alors les processus non terminés; si l'on tape à nouveau exit, ils se terminent. Le cours ajoute qu'« il vaut mieux les faire se terminer individuellement ».

bashbash

1xclock &              # un job d'arrière-plan ordinaire2exit                  # observation: le shell signale un job en cours3exit                  # observation: le job est terminé

Pour qu'un processus survive à la fermeture du terminal et même à la déconnexion, on le lance avec nohup (no hang up, « ne pas raccrocher »):

bashbash

1nohup xclock &2nohup make install &

La sortie standard et la sortie d'erreur sont alors envoyées dans le fichier ./nohup.out, et le processus continue de fonctionner après la déconnexion. C'est la solution pour lancer une tâche longue et peu prioritaire — une installation logicielle, un gros chargement — depuis une connexion lente.

#Planifier : at et crontab

nohup exige d'être présent au moment du lancement. La commande at choisit le moment:

bashbash

1at 2200 Sat2at now +2 days3at 3:30 tomorrow4at now +1 hour5at noon Apr 17

at lit sur son entrée standard le script à exécuter, terminé par une fin de fichier. La commande crontab va plus loin: elle exécute un processus de manière régulière. Chaque utilisateur autorisé dispose d'une table, consultée à intervalles réguliers par le démon cron; crontab -e la modifie, l'éditeur étant indiqué par la variable VISUAL. Cinq champs précèdent la commande: minute, heure, jour du mois, mois, jour de la semaine.

texttext

1# exécuter chaque nuit, 5 minutes après minuit25 0 * * * $HOME/bin/daily.job >> $HOME/tmp/out 2>&13# exécuter à 14h15 le premier de chaque mois415 14 1 * * $HOME/bin/monthly5# 23 minutes après les heures paires, tous les jours623 0-23/2 * * * echo "Il est 23 minutes après une heure paire"7# 4h05 tous les dimanches85 4 * * sun echo "Rien d'interessant"

Un point de vigilance: la redirection >> $HOME/tmp/out 2>&1 est indispensable dans une tâche cron, car sans elle la sortie du programme est perdue — et une tâche silencieuse qui échoue est le pire des diagnostics.

#Signaux et codes de sortie

Les signaux permettent de communiquer avec un processus. TERM (15) demande un arrêt propre, KILL (9) force l'arrêt immédiat (irréversible), INT (2) correspond à Ctrl+C, HUP (1) signifie souvent « recharge ta configuration ». Préférez toujours TERM et donnez au programme une chance de nettoyer.

bashbash

1kill -TERM 12345     # arrêt gracieux du PID 123452kill -TERM %1        # arrêt gracieux du job 1

Convention observable: un processus terminé par un signal renvoie le code 128 + numéro du signal. Un processus tué par TERM renvoie donc 143, un processus interrompu par Ctrl+C renvoie 130.

Le cours donne une formulation plus opérationnelle: kill envoie un signal « plus ou moins fort », -9 correspondant à la destruction inconditionnelle du processus et -19 à une suspension. Le tableau de correspondance avec les raccourcis du terminal:

  • C-c fait se terminer immédiatement le processus de premier plan;
  • C-z suspend son exécution — le cours note que « le processus peut résister à la première commande, pas à la deuxième »;
  • un processus suspendu se reprend au premier plan avec fg, en arrière-plan avec bg, et reçoit un signal avec kill.

#Observer la vie du système

bashbash

1ps aux | grep http.server     # cherche le processus et son PID2pgrep -af http.server         # même idée, sans le grep parasite3ss -tlnp | grep 8000          # qui écoute sur le port 80004top                           # vue dynamique; htop si installé

Sur les distributions modernes, systemd gère des services pérennes et des timers. Les journaux se consultent avec journalctl -u mon-service.

Les commandes d'information sur les utilisateurs complètent cette vue, et le cours les distingue nettement.

bashbash

1who                # qui est connecté: nom, terminal, date, machine d'origine2who -a             # la même chose, avec davantage de renseignements3who jojo           # restreint à un utilisateur4w                  # état de la machine + inactivité, temps machine, commande en cours5finger             # informations comparables à who6finger utilisateur # informations détaillées sur un utilisateur7finger @machine    # informations sur une machine distante, si elle veut bien

Deux usages pratiques: who et finger servent à savoir sur quel terminal se trouve un interlocuteur avant de lui écrire, et w sert à repérer un utilisateur dont la session consomme anormalement de temps machine. Le cours signale que finger sur une machine distante n'est pas toujours servi, pour des raisons de sécurité.

#Priorités

Ajuster la priorité (nice) laisse de la place aux tâches interactives:

bashbash

1nice -n 10 longue_tache      # priorité plus basse (plus gentil avec les autres)2renice -n 5 -p 12345         # ajuste un processus déjà lancé

Astuce: concevez vos scripts pour qu'ils tolèrent d'être relancés (idempotence) et qu'ils gèrent proprement TERM (nettoyage via trap).

#Animation: contrôle des jobs

Avant-plan
Commande en cours (occupe le TTY)
Ctrl+Z (STOP)
Processus suspendu, devient un job
bg %n
Reprendre en arrière-plan
fg %n
Ramener au premier plan
kill
TERM (gracieux) ou KILL (forcé)

#Diagramme: signaux et TTY

TTY
Shell
Processus
Noyau
1. Ctrl+C (SIGINT)
2. Transmet SIGINT
3. Exit code
4. Ctrl+Z (SIGTSTP)
5. SIGTSTP (suspend)
6. bg → SIGCONT

#Exercice (10 minutes)

Lancez sleep 300, suspendez-le avec Ctrl+Z, reprenez-le en arrière-plan, listez les jobs, ramenez-le au premier plan, puis terminez-le proprement. Notez les codes de sortie.

Correction, séquence par séquence:

bashbash

1sleep 300          # puis Ctrl+Z2# Observation: [1]+  Stopped   sleep 3003bg %1              # Observation: [1]+ sleep 300 &4jobs               # Observation: [1]+  Running   sleep 300 &5kill -TERM %16wait %1; echo $?7# Observation attendue: 143 (128 + 15, arrêt par SIGTERM)

Variante de vérification: interrompez plutôt le job au premier plan avec Ctrl+C, puis echo $? affiche 130 (128 + 2). Si vous obtenez ces deux valeurs, vous avez compris la convention.

#Exercices complémentaires

1. Écrire la commande qui affiche tous les processus de l'utilisateur, puis celle qui affiche de manière détaillée tous les processus de l'ordinateur. Pourquoi faut-il faire suivre la seconde d'un tube?

2. Qu'affichent uptime et top? Que désigne précisément la « charge moyenne »?

3. Un xclock & lancé dans un terminal disparaît à la fermeture de la fenêtre. Comment le faire survivre? Où vont ses sorties?

4. Que fait exec gnome-terminal lancé depuis un shell interactif? En quoi diffère-t-il de gnome-terminal lancé seul?

5. Écrire les deux boucles qui affichent les entiers de 0 à 9, l'une incrémentant par expr, l'autre par (( )). Laquelle est la plus rapide pour 5 000 itérations, et pourquoi?

Corrections détaillées

1.

bashbash

1ps x            # tous les processus de l'utilisateur2ps aux          # tous les processus de l'ordinateur, en détail3ps aux | less   # ou ps aux | grep motif

La forme des paramètres est celle du système BSD: ils s'écrivent sans tiret initial. a étend la sélection à tous les utilisateurs, x à tous les processus non attachés à un terminal, u demande l'affichage détaillé (utilisateur, temps, mémoire). ps aux produit une sortie volumineuse, d'où la nécessité de la filtrer ou de la paginer.

2. uptime affiche la durée de fonctionnement sans arrêt du système, le nombre d'utilisateurs connectés, et la charge moyenne des 1, 5 et 15 dernières minutes. top affiche les processus les plus actifs, le plus actif en tête, avec en haut de fenêtre la ligne d'uptime, la répartition des processus, l'utilisation du processeur, de la mémoire et de la zone d'échange. La charge moyenne est la longueur de la file d'attente pour exécution: elle compte les processus prêts à s'exécuter, pas les processus bloqués.

3.

bashbash

1nohup xclock &

nohup (no hang up) détache le processus de la fin de session: il continue de fonctionner même après la déconnexion. Sa sortie standard et sa sortie d'erreur sont redirigées vers ./nohup.out. Sans nohup, le processus d'arrière-plan est un fils du shell et se termine avec lui, puisque tout processus fils se termine quand son père se termine.

4. exec remplace le processus du shell par celui de la commande: le shell ne reprend pas la main, et la fenêtre du terminal se ferme quand gnome-terminal se termine. Lancé seul, gnome-terminal est un fils du shell: le shell attend sa fin (premier plan) puis reprend la main et réaffiche son invite.

5.

bashbash

1nombre=02while [[ $nombre -lt 10 ]]3do4  echo $nombre5  nombre=$(expr $nombre + 1)6done
bashbash

1nombre=02while [[ $nombre -lt 10 ]]3do4  echo $nombre5  ((nombre=$nombre + 1))6done

Les deux affichent 0 à 9. La seconde est la plus rapide: expr est un programme externe, donc chaque itération paie le coût de création d'un processus, alors que (( )) est une construction interne au shell. Pour 10 itérations l'écart est invisible; pour 5 000, la première version lance 5 000 processus. La règle générale: dans une boucle, tout appel à une commande externe se multiplie par le nombre d'itérations.