Cours 4



next up previous
Next: Chapitre 5 Up: Cours systèmes Previous: Chapitre 3

Plan

  • Goto's non locaux en C
  • Concurrence et synchronisation
  • Mémoire partagée
  • Groupes de processus
  • Signaux
  • Exercices

  • Cours 3

    Goto's non locaux en C

  • setjmp, longjmp En C, il n'y a pas de procédures emboitées. Donc pour faire un saut global, on sauve les registres et le pointeur de pile par setjmp, et on peut restituer cet environnement par longjmp. Attention, les variables globales ne sont pas restituées. Par convention, setjmp retourne 0, et longjmp la valeur de son 2nd argument (différent 0). Exemple:
    #include <setjmp.h>
    
    jmp_buf env;
    int i = 0;
    main ()
    {
        void exit();
    
        if(setjmp(env) != 0) {
            (void) printf("2eme retour de setjmp:   i=%d\n", i);
            exit(0);
        }
        (void) printf("1er retour de setjmp: i=%d\n", i);
        i = 1;
        g();
        /*NOTREACHED*/
    }
    
    g()
    {
        longjmp(env, 1);
        /*NOTREACHED*/
    }
    
    } donne i=0 et i=1.

  • Cours 3

    Processus et synchronisation

    Séquentiel -> Concurrent
    est une étape conceptuelle importante. Le monde de l'informatique est concurrent:
  • machines multi-processeurs,
  • drivers pour des périphériques lents,
  • les utilisateurs sont concurrents,
  • systèmes distribué ont de multiple clients,
  • réduire le temps d'exécution
  • Dans la programmation système, on a la concurrence pour:

  • l'accès concurrent à une ressource commune,
  • la gestion des événements asynchrones,
  • la détection de fin des processus.

  • Cours 3

    Exemple: Pile concurrente

    Comment rendre Pop et Push concurrents?

    Pile *head = NULL;
    
    Pile *Pop()
    {
        Pile *topElement;
    
        while (head == NULL) 
            Wait_nonEmpty ();
        topElement = head;
        head = head -> next;
    }
    
    void Push (Pile *newElement)
    {
        newElement -> next = head;
        head = newElement;
        Signal_nonEmpty ();
    }
    
    } Il faut d'abord que l'accès à Pop et à Push soit exclusif, et écrire Wait_nonEmpty et Signal_nonEmpty. Section critique (ou exclusion mutuelle) est un classique de la programmation concurrente.

    Cours 3

    Concurrence et mémoire partagée

  • Exclusion mutuelle
  • Producteur, Consommateur
  • Lecteurs et écrivains
  • Registres atomiques
  • TIT     .Principles of concurrent programming
    SLA     .M. Ben-Ari. - Englewood Cliffs, NJ ; London ; New
            .Delhi : Prentice-Hall, 1982. - XV-172 p. ; 22 cm.
    
    }

    Cours 3

    Section critique -1-

    turn = 1;
    
    p1()                              p2()                           
    {                                 {
        for (;;) {                         for (;;) {
            while (turn == 2)                  while (turn == 1)
                ;                                  ;
            Crit1();                           Crit2();
            turn = 2;                          turn = 1;
            Reste1();                          Reste2();
        }                                  }
    }                                 }
    
    
    } Ok, mais manque de souplesse.

    Cours 3

    Section critique -2-

    c1 = c2 = 1;
    
    p1()                              p2()                           
    {                                 {
        for (;;) {                         for (;;) {
            while (c2 == 0)                    while (c1 == 0)
                ;                                   ;
            c1 = 0;                            c2 = 0;
            Crit1();                           Crit2();
            c1 = 1;                            c2 = 1;
            Reste1();                          Reste2();
        }                                  }
    }                                 }
    
    
    } Erreur car:
                                       c1   c2
                                        1    1
    p1 teste c2                         1    1
    p2 teste c1                         1    1
    p1 positionne c1                    0    1
    p2 positionne c2                    0    0
    p1 fait Crit1()                     0    0
    p2 fait Crit2()                     0    0
    
    }

    Cours 3

    Section critique -3-

    c1 = c2 = 1;
    
    p1()                              p2()                           
    {                                 {
        for (;;) {                         for (;;) {
            c1 = 0;                            c2 = 0;
            while (c2 == 0)                    while (c1 == 0)
                ;                                   ;
            Crit1();                           Crit2();
            c1 = 1;                            c2 = 1;
            Reste1();                          Reste2();
        }                                  }
    }                                 }
    
    
    } Deadlock car:
                                       c1   c2
                                        1    1
    p1 positionne c1                    0    1
    p2 positionne c2                    0    0
    p1 teste c1                         0    0
    p2 teste c2                         0    0
    ...
    
    }

    Cours 3

    Algorithme de Dekker

    c1 = c2 = 1;
    turn = 1;
    
    p1()                              p2()                           
    {                                 {
        for (;;) {                         for (;;) {
            c1 = 0;                            c2 = 0;
            while (c2 == 0)                    while (c1 == 0)
                if (turn == 2) {                   if (turn == 1) {
                    c1 = 1;                            c1 = 1;
                    while (turn == 2)                  while (turn == 1)
                        ;                                  ;
                    c1 = 0;                            c2 = 0;
    	    }                                  }
            Crit1();                           Crit2();
            turn = 2;                          turn = 1;
            c1 = 1;                            c2 = 1;
            Reste1();                          Reste2();
        }                                  }
    }                                 }
    
    }

    Cours 3

    L'algorithme de Dekker pour n processeurs.

    enum {OUT, WANTING, IN} c[n];
    
    s = 1;
    
    pi() 
    { 
        for (;;) { 
            do {
               c[i] = WANTING;
               while (turn != i)
                    if (c[turn] == OUT)
                        turn = i;
               c[i] = IN;
               ok = TRUE;
               for (j = 0; j < n; j++)
                    if (j != i)
                        ok = ok & (c[j] != IN);
            } while (!ok);
            Crit1();
            c[i] = OUT;
            Reste1();
        }
    }
    
    } L'algorithme de Peterson est plus simple que Dekker, quoique moins intuitif.

    Cours 3

    Algorithme de Peterson (IPL Juin 81)

    c1 = c2 = 1;
    turn = 1;
    
    p1()                            p2()                           
    {                               {
      for (;;) {                       for (;;) {
        c1 = 0;                          c2 = 0;
        turn = 1;                        turn = 2;
        while (c2 == 0 && turn == 1)     while (c1 == 0 && turn == 2)   
            ;                                ;
        Crit1();                         Crit2();
        c1 = 1;                          c2 = 1;
        Reste1();                        Reste2();
      }                               }
    }                               }
    
    }

    Cours 3

    Sémaphores

  • l'algorithme de Dekker n'a pas de sens si on dispose de l'instruction machine indivisible TEST_AND_SET.
  • s est un booléen dans {0, 1}. Les opérations P(s), V(s) sont indivisibles. Dijkstra 65.
    s = 1;
    
    p1()                              p2()                           
    {                                 {
        for (;;) {                         for (;;) {
            P(s);                              P(s);
            Crit1();                           Crit2();
            V(s);                              V(s);
            Reste1();                          Reste2();
        }                                  }
    }                                 }
    
    }
  • Si s = 1, P(s) positionne s à 0. Si s = 0, P(s) se bloque.
  • V(s) met s à 1 et réveille un processus bloqué sur s.
  • Les sémaphores ont une lourde implémentation en Unix que personne n'utilise.
    TIT     .The Design of the UNIX operating system
    SLA     .Maurice J. Bach. - Englewood Cliffs, NJ :
            .Prentice-Hall, 1986. - XIV-471 p. ; 23 cm.
    
    }

    Cours 3

    Filiation des processus

  • fork établit une filiation entre processus. Tous les processus sont créés à partir de init (processus 1), cf cours 1.

  • exit termine un processus. Unix regarde si le processus à un père actif. Sinon init devient son père (processus 1). Si oui, le fils passe à l'état zombie (avec uniquement un descripteur temporaire dans la table des processus, pour qu'on puisse récupérer sa mémoire, ses fichiers ouverts, etc) et disparait complètement quand le père fait wait. (init appelle toujours wait dès qu'un de ses fils termine.)

  • le shell crée implicitement cette filiation en exécutant les commandes comme fille de lui-même. Les variables d'environnement sont passées en dernier argument de exec, et tout processus peut y accéder par getenv()
    char *getenv (char *name);
    
    }

  • Cours 3

    Groupes de processus

  • Session correspond à un login (ou remote login)

  • Dans une session, groupe de processus foreground et background.

  • Control-C interrompt les processus foreground

  • une déconnexion envoie un signal à toute une session (en fait au leader de la session).

  • /dev/tty est le terminal de la session.

  • Cours 3

    Signaux

  • Les signaux sont des interruptions logicielles. Ils permettent la communication entre processus, le traitement de phénomènes externes (donc asynchrones), ou les erreurs d'un processus. La communication entre processus se fait par les fonctions kill et signal.
      kill (pid, signum);
      signal (signum, function);
    
  • kill envoie le signal signum au processus pid. Les nombres signum sont décrits dans signal.h et valent moins que 32. Si pid < 0, le signal est envoyé au ``process group''. Si pid = -1 et l'envoyeur est le super-utilisateur, envoyé à tout le monde.

  • signal permet de dire l'action à entreprendre (handler du signal) sur la réception d'un tel signal. Valeurs conventionnelles: SIG_IGN pour ignorer, SIG_DFL pour action par défaut, ...

  • Les évènements asynchrones externes sont par exemple la réception d'un caractère C-C (interruption), C-Z (suspension), C-_ (quit) sur un terminal, la déconnexion d'un terminal, la fin de délai d'une alarme posée par un processus.

  • Cours 3

    Signaux (bis)

  • Le signal peut arriver à tout moment, et donc au cours de la modification d'une structure de donnée (éventuellement de la bibliothèque C --- par exemple, la liste libre de malloc). Il faut donc faire attention. On peut toujours positionner un flag, et le tester plus tard. De manière générale, les signaux ne feront pas de mal pour les procédures réentrantes. Très peu le cas en Unix, par exemple tout stdio n'est pas réentrant.
    #include <signal.h>
    #include <setjmp.h>
    jmp_buf sjbuf;
    
    main()
    {
        int onintr(void);
        if (signal (SIGINT, SIG_IGN) != SIG_IGN)
            signal (SIGINT, onintr);
        setjmp (sjbuf);
    
        for (;;) {
            /* boucle d'interpretation */
            ...
        }
    
    onintr()
    {
        signal (SIGINT, onintr);
        printf ("\nInterrupt\n");
        longjmp (sjbuf, 0);
    }
    
    }

  • Cours 3

    Signaux (ter)

  • alarm(n) génère un signal SIGALRM dans les n secondes.
    signal (SIGALRM, onalarm);
    alarm (sec);
    ...
    
    onalarm ()
    {
        kill (pid, SIGKILL);
    }
    
    }
  • Un shell prend les signaux pour les commandes qui ne sont pas en background. Et de manière général, il est malsain que 2 programmes père et fils qui ont tous les 2 un toplevel prennent un même signal. Ils vont tous les 2 revenir à leur boucle de lecture et se partager ce que tape l'utilisateur!
    if (fork() == 0)
        exec (...);
    signal (SIGINT, SIG_IGN);
    wait (&status);
    signal (SIGINT, onintr);
    
    }
  • Après le traitement d'un signal, on redémarre exactement à l'endroit de l'arrêt (par exemple si on positionne un flag. Si on est dans une lecture au terminal, on veut que le signal soit pris en compte tout de suite. Pour cela, un tel read s'arrête et fait l'erreur EINTR.

  • Cours 3

    Exercices en TD et à la maison

  • Débutants (cf dernier cours): grep parallèle, ie faire une commande pgrep [-N] string [file] où N est le nombre de processus forkés et file un fichier contenant des noms de fichiers (un par ligne) qu'on obtient en faisant par exemple find . -type f -print La sémantique de pgrep [-N] string [file] est:
  • soient f_1 f_2 \ldots f_n les n lignes de fichier d'entrée. On met dans N seaux équi-répartis les f_i.
  • on forke grep string sur chacun des seaux. Remarque: on peut prendre le grep du système ou écrire un grep perso trivial sans KMP ou Boyer-Moore avec bêtement strcmp et la méthode quadratique en string et input.
  • on liste toutes les lignes des fichiers contenant string,

  • Chevronnés: Faire un minishell comprenant: & ; < > et l'appel des commandes standards (exec). On s'efforcera de traiter les signaux SIGINT et SIGSTOP. Cet exercice reservira plus tard pour le remote-shell.

  • Cours 3

    Exercices en TD et à la maison -2-

  • Experts: Faire une paquet de processus légers en utilisant setjmp et longjmp pour commuter. L'ordonnancement peut être sans préemption ou avec des signaux alarme. Lancer un Dekker ou Peterson.