Cours n°4 :
Création de processus

Réseau et Prog. Bas Niveau
Victor Poupet

Hiérarchie de processus

$ ps ax

  PID  PPID S COMMAND
    1     0 S /sbin/init
  338     1 S /sbin/udevd --daemon
  481   338 S /sbin/udevd --daemon
 1200   880 S lightdm --session-child
 1301  1200 S gnome-session
 1528     1 S /usr/lib/gvfs/gvfsd
 1532  1301 S nm-applet
 1556  1301 S /usr/lib/gnome-settings
 1590     1 S /usr/lib/gvfs/gvfs-gdu
 1627     1 S /usr/lib/bamf/bamfdaemon
 1636  1635 S /usr/bin/gtk-window
 1639     1 S /usr/lib/unity/unity
 1845     1 S /usr/lib/gnome-online
 1853     1 R gnome-terminal
 2414     1 S /usr/lib/dconf/dconf
 2431  1853 S bash
 2597  2431 R ps ax

Au démarrage de l'ordinateur, le processus init est lancé

Hiérarchie (pstree)

$ pstree -p

init(1)─┬─NetworkManager(793)─┬─dhclient(911)
        │                     └─dnsmasq(1012)
        ├─accounts-daemon(1214)───{accounts-daemon}(1215)
        ├─cron(855)
        ├─cupsd(729)───dbus(3916)
        ├─dconf-service(1997)─┬─{dconf-service}(1998)
        │                     └─{dconf-service}(2000)
        ├─gnome-keyring-d(1407)─┬─{gnome-keyring-d}(1408)
        │                       └─{gnome-keyring-d}(1970)
        ├─gnome-terminal(1737)─┬─bash(1746)───pstree(20545)
        │                      ├─gnome-pty-helpe(1745)
        │                      └─{gnome-terminal}(1747)
        ├─goa-daemon(1895)───{goa-daemon}(1911)
        ├─gvfs-afc-volume(1612)───{gvfs-afc-volume}(1614)
        ├─mission-control(1890)─┬─{mission-control}(1893)
        │                       └─{mission-control}(1909)
        ├─sh(884)───initctl(886)
        └─udisks-daemon(1604)───udisks-daemon(1609)

Création

int main(int argc, char **argv) {
  pid_t pid = fork();
  if (pid == -1) {
    perror("fork");
  } else if (pid == 0) {
    for (int i=0; i<3; i++) {
      printf("F%d\n", i);
    }
  } else {
    for (int i=0; i<3; i++) {
      printf("P%d ", i);
    }
  }
}

$ ./a.out

P0 P1 P2 F0 F1 F2
// fork bomb !
int main() {
  while (1) fork();
}
            
Bob Omb

Pour créer un nouveau processus, on clone un processus existant à l'aide de l'appel fork()

Le système copie :

  • la mémoire (pile, tas, code)
  • les descripteurs de fichiers ouverts (cependant les pointeurs dans les fichiers sont partagés)
  • l'état d'exécution (pointeur d'exécution, registres du processeur)

Différences entre les processus :

  • le processus fils a un PID et un PPID différent du père (son PPID est le PID du père)
  • la fonction fork renvoie 0 dans le processus fils, et le PID du fils dans le processus père

Attente de complétion

SYNOPSIS
  #include <sys/types.h>
  #include </wait.h>

  pid_t wait(int *status);
  pid_t waitpid(pid_t pid, int *status, int options);

Les commandes wait et waitpid demandent à un processus d'attendre que ses processus fils changent d'état

Zombies

int main(int argc, char **argv) {
  pid_t pid;
  pid = fork();
  if (pid == 0) { // fils
    sleep(10);
    printf("Fin fils\n");
  } else { // père
    sleep(20);
    printf("Fin père\n");
  }
  exit(1);
}$ ps
 PID  TTY     STAT   COMMAND
 1859 pts/0   Ss     bash
 2570 pts/0   S      ./prog
 2571 pts/0   Z      [prog] <defunct>
Zombie

Lorsqu'un processus se termine, il n'est pas immédiatement supprimé de la table

Éviter les zombies

int main() {
  int pid1;
  pid1 = fork();
  if (pid1) {
    // père
    /* Tâche principale */
  } else {
    // fils
    /* Tâche secondaire */
  }
}static void handler(int signo) {
  wait(NULL);
}

int main() {
  signal(SIGCHLD, handler);
  int pid1;
  pid1 = fork();
  if (pid1) {
    // père
    /* Tâche principale */
  } else {
    // fils
    /* Tâche secondaire */
  }
}int main() {
  int pid1, pid2;
  pid1 = fork();
  if (pid1) {
    // père
    waitpid(pid1, NULL, 0);
    /* Tâche principale */
  } else {
    // fils
    pid2 = fork();
    if (pid2) {
      // fils
      exit(0);
    } else {
      // petit- fils
      /* Tâche secondaire */
    }
  }
}

Parfois, la tâche exécutée par le fils est longue et on ne veut pas bloquer le père en attente de complétion

fork

La fonction fork est appelée par un processus mais renvoie deux résultats, dans deux processus distincts

Exécution

#include <unistd.h>

extern char **environ;

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);int main(int argc, char **argv) {
  pid_t pid;
  int status;
  pid = fork();
  if (pid == 0) {
    execlp("ls", "ls", "-l", NULL);
  } else {
    printf("Père \n");
    wait(&status);
  }
}int main(int argc, char **argv) {
  pid_t pid;
  int status;
  pid = fork();
  if (pid == 0) {
    char *args[3];
    args[0] = "ls";
    args[1] = "-l";
    args[2] = NULL;
    execvp("ls", args);
  } else {
    printf("Père \n");
    wait(&status);
  }
}

La famille de fonctions exec permet de remplacer le code d'un processus par un autre

  • le premier argument est un exécutable
  • les arguments suivants permettent de passer des paramètres et des variables d'environnement
  • à l'appel de la fonction, tout le code du processus est remplacé par celui indiqué et l'état du processus est réinitialisé

Il existe plusieurs variantes :

  • l (list) : les arguments sont passés un par un à la fonction, avec un pointeur nul (NULL) en dernier
  • v (vector) : les arguments sont passés dans un unique tableau
  • p (path) : l'exécutable est cherché dans les répertoires du chemin d'exécution
  • e (environment) : permet de passer un tableau contenant des variables d'environnement pour l'exécution du nouveau programme

Exemple : ls

exec

Le shell veut exécuter la commande ls

Copie sur écriture

L'utilisation de fork/exec est le moyen le plus classique (et parfois le seul disponible) pour créer des nouveaux processus