Cours 3



next up previous
Next: Chapitre 4 Up: Cours systèmes Previous: Chapitre 2

Plan

  • Revoir <<, cpp, file descriptors
  • Protection des fichiers (rappel)
  • Variables globales / locales. Table des symboles
  • Initialisations, Variables register
  • Compilation séparée -- Format des binaires
  • Erreurs dans un appel système
  • Processus
  • Espaces d'adressage
  • Créations de processus
  • Exercices

  • Cours 3

    Protection des fichiers (rappel)

  • La protection est rangée dans les inodes
    #include <sys/inode.h>
    
    di_mode = 0x41ed == 040755                 /* mode de "/" */
                     == IFDIR 
                      | IREAD | IWRITE | IEXEC 
                      |(IREAD | IEXEC) >> 3
                      |(IREAD | IEXEC) >> 6
    
    di_mode = 0x41ed == 0100755               /* mode de "/vmunix" */
                     == IFDIR 
                      | IREAD | IWRITE | IEXEC 
                      |(IREAD | IEXEC) >> 3
                      |(IREAD | IEXEC) >> 6
    
  • les bits de mode et de protection sont définis dans /usr/include/sys/inode.h.
    #define IFPORT          0010000         /* port (named pipe) */
    #define IFCHR           0020000         /* character special */
    #define IFDIR           0040000         /* directory */
    #define IFBLK           0060000         /* block special */
    #define IFREG           0100000         /* regular */
    #define IFLNK           0120000         /* symbolic link */
    #define IFSOCK          0140000         /* socket */
    #define IFPIPE          0160000         /* pipe */
    
    #define ISUID           04000           /* set user id on execution */
    #define ISGID           02000           /* set group id on execution */
    #define ISVTX           01000           /* save swapped text even after use */
    #define IREAD           0400            /* read, write, execute permissions */
    #define IWRITE          0200
    #define IEXEC           0100
    

  • Cours 3

    Protection des fichiers (rappel)

  • chmod permet de modifier les accès d'un fichier.
    % ls -ld Mail
    drwxr--r--  4 levy          512 Oct 30 15:48 Mail
    % chmod go-r Mail
    % ls -ld Mail
    drwx------  4 levy          512 Oct 30 15:48 Mail
    
  • chmod permet de modifier les accès d'un fichier.
  • les modes sont pour le propriétaire (user), le groupe, et les autres. Peu précis mais dense. Les modes principaux sont read, write, execute. Pour un directory, execute veut dire qu'on peut faire cd.
  • le mode suid set user id permet à un fichier exécutable de prendre provisoirement les droits du propriétaire. C'est utile pour pouvoir écrire un serveur (courrier, forum).
  • le mode sticky n'est pratiquement plus utilisé. C'est une optimisation pour ne pas lire trop souvent un fichier exécutable.

  • Cours 3

    Variables statiques -- Noms externes, Table des symboles

  • Par défaut, les variables globales sont ``externes''. Sinon, il faut leur mettre le préfixe static. Les variables externes sont connues des autres modules quand on fait cc -c xxx.c, cc -c yyy.c et cc xxx.o yyy.o. La table des symboles namelist externes (et internes) d'un binaire est obtenue par la commande nm -g sur Vax.
  • Les variables static peuvent être aussi locales. Ce sont alors des variables rémanentes, dont la valeur reste inchangée quand on sort du bloc correspondant et on y rerentre.
  • Exemple d'implantation xxx.c
    static int sp = 0;
    static stack[1000];
    void push (float x)  {stack[sp++] = x;}
    float pop (void)     {return stack[--sp];}
    
  • Exemple d'utilisation yyy.c.
    extern void push (float);
    extern float pop (void);
    
    On peut aussi le mettre dans un ``fichier d'interface'' xxx.h et faire
    #include     "xxx.h"
    

  • Cours 3

    Initialisation des variables

  • Les variables locales et globales peuvent être initialisées. Pour les globales, c'est au moment du chargement du binaire en mémoire que ces variables sont initialisées. Pour les locales, c'est à chaque entrée dans le bloc (sauf pour les static).
  • Les tableaux et structures globales peuvent être initialisées. Dans le cas des tableaux, on peut déclarer le tableau ouvert et laisser cc la déterminer en fonction du nombre d'éléments initialisés.
  • Attention, il y a 2 formats d'initialisation (le vieux et le moderne qui respecte plus les structures). Utiliser toujours le nouveau format.
  • Exemples
    int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    
    char prof[] = "levy";
    char prof[] = {'l', 'e ', 'v', 'y', '\0'}'
    char *profs[] = {"levy", "ehrlich", "rouaix", 
                     "ruget", "doligez", 
                     NULL};
    
  • Variables register. Pour signaler à cc que la variable est très souvent utilisée. Ne pas trop utiliser.

  • Cours 3

    Compilation séparée -- Format des binaires

  • On a vu que le compilateur C cc appelait l'éditeur de lien /bin/ld pour permettre la compilation séparée de plusieurs modules.
    % cc -c xxx.c
    % cc -c yyy.c
    % cc xxx.o yyy.o
    % nm -gn a.out | more
    
  • Les fichiers .o sont des binaires relogeables. Il y a toute l'information pour mettre le binaire à toute adresse.
  • Les fichiers a.out sont des binaires absolus. Sur ds5000:
    00400140 T __start ...
    
    et sur Vax:
    00000000 T start
    
  • cc -r permet de faire un fichier .o à partir de 2 fichiers .o

  • Cours 3

    Format des binaires (suite)

  • Dans un fichier binaire Unix, il y a 3 zones: text le programme, data les données initialisées, bss les données non initialisées.
  • size a.out permet de connaître la taille de ces trois zones.
    % size /vmunix
    text    data    bss     dec     hex
    1150864 122384  486672  1759920 1adab0
    
  • Quand le programme s'exécute, une 4ème zone est celle de la pile dans laquelle on met les variables locales (au fur et à mesure de leur utilisation), les stack frames pour passer les arguments aux procédures (cf cours de compilation).
  • En début d'exécution, la zone bss est mise à zéro. Ne pas trop utiliser ce fait.
  • limit en Cshell permet de connaître les tailles maximales autorisées pour ces valeurs.
    % limits
    datasize        65536 kbytes
    stacksize       512 kbytes
    
  • le type d'un binaire se vérifie par les 2 premiers octets ( magic number)

  • Cours 3

    Makefile, sccs, rcs

  • make donne un ensemble de règles pour reconstruire les modules d'un programme. (Attention: tabulation n'est pas espace!) Un tri topologique sur les dates permet de reconstruire le minimum.
    DESTDIR= /usr/local/bin
    
    all: xxx zzz
    
    xxx: xxx.o yyy.o
            cc -O -o xxx xxx.o yyy.o
    
    xxx.o: xxx.c
            cc -c xxx.c
    
    yyy.o: yyy.c
            cc -c yyy.c
    
    zzz: zzz.c
            cc -O -o zzz zzz.c
    
    install: all
            cp xxx zzz ${DESTDIR}
    
    clean:
            rm -f xxx zzz xxx.o yyy.o
    
  • sccs, rcs, cvs permettent de gérer des numéros de versions. Très important pour les gros programmes.

  • Cours 3

    Erreurs dans un appel système

  • errno est une variable qui a une valeur après tout appel-système. En Unix, l'appel système retourne une valeur anormale (-1 ou NULL par exemple), et errno permet d'avoir un diagnostic plus précis. Ses valeurs possibles sont définies dans /usr/include/errno.h
  • perror (char *s); permet d'écrire un message standard (en anglais) avec l'argument s suivi de :
  • dans un gros système , il est bon de regrouper les messages d'erreur. Sinon, il n'y a pas d'uniformité.

  • Cours 3

    Processus

  • Un programme en exécution est appelé un processus. A tout moment un même utilisateur peut avoir plusieurs processus:
    % ps
      PID TT STAT  TIME COMMAND
    21908 p0 S     0:01 -tcsh (tcsh)
    21918 p0 S     1:23 emacs cours.ftex
    22670 p0 R     0:00 ps
    21920 p1 I     0:02 -tcsh (tcsh)
    
  • Unix est multi-utilisateur, et controle les processus de plusieurs utilisateurs.
  • \vspace{-4ex} \hspace{-2em}
    % ps aux
    USER       PID %CPU %MEM   SZ  RSS TT STAT   TIME COMMAND
    figer    27300  2.4  0.4 1074  981 ?  S N   31:35 StelBot -n Mag0t_ -u arthus
    figer     2611  2.3  0.5 1166 1072 ?  S N   38:21 StelBot
    nobody   14461  2.0  0.1  407  149 ?  S      0:00 /usr/local/w3/bin/httpd
    charaya  14421  1.3  0.1  548  223 r4 S      0:00 -tcsh (tcsh)
    charaya  14413  0.9  0.5 2139 1120 ?  S      0:00 xterm -ls
    charaya  14430  0.9  0.0  129   52 r4 S      0:00 rlogin sil
    root     14433  0.4  0.0   82   36 ra S      0:00 rlogind
    potters  14395  0.2  0.1  477  204 r2 S      0:00 elm
    potters  14378  0.2  0.5 2139 1123 ?  S      0:00 xterm -ls
    potters  14387  0.2  0.1  548  221 r2 I      0:00 -tcsh (tcsh)
    root      9675  0.1  0.1  374  108 ?  S      4:11 /usr/local/w3/bin/httpd
    volkov   14078  0.1  0.5 2137 1120 ?  S      0:00 xterm -ls
    pelikan  10471  0.1  0.4 1440  798 ?  S      0:19 xmailtool
    brunetoe 12396  0.1  1.0 3161 2107 ?  S      0:00 xterm -ls -geometry 85x25+0-0
    root      9481  0.1  0.0  168   96 ?  S     17:11 /usr/etc/ypserv
    root      9692  0.1  0.0    6    4 ?  S      4:43 /etc/update
    

    Cours 3

    Processus -- Espaces d'adressage

  • Un système en temps partagé doit donner l'illusion à tout processus qu'il peut disposer de tout l'environnement.
  • Unix donne du temps d'exécution à chaque processus avec un algorithme d'ordonnancement scheduling
    TIT     .COMPUTER AND JOB-SHOP SCHEDULING THEORY
    AUT     .Coffman, Edward G.. Ed.
    EDI     .WILEY-INTERSCIENCE,NEW YORK
    DAT     .1976
    
  • protection mémoire et mémoire virtuelle. Chaque processus fonctionne dans son monde. Fondamental pour que le système soit sûr. Contrexemples: Macintosh, Alto, Dorado. Windows 95 a la protection mémoire à 90%.
  • accès social aux ressources, et donc synchronisation (ipc -- inter process communication). Unix est traditionnellement faible sur ce point, mais s'est amélioré dans ses variantes récentes (Mach).
  • les processus Unix sont lourds (à commuter).

  • Cours 3

    Créations de processus

  • fork() duplique le processus courant, et crée un processus fils par division cellulaire. fork() répond 0 pour le processus fils et le numéro (unique) du processus pour le processus père. C'est la seule manière de créer des processus Unix. Elle est lourde, mais permet de passer toute l'information au fils (comme en biologie). Tout l'espace text, data, bss, stack est copié.
    for (i = 0; i < 15; i++)
        if (fork() == 0) {
            printf ("child proc %d = %x\n", i, getpid());
            exit (1);
        }
    /* suite du pe `re */
    r = wait(&status);
    printf ("r = %x, status =%\n", r,  status);
    
  • exec permet de remplacer l'image mémoire d'un processus par celle déduite à partir d'un fichier a.out.
    if (fork() == 0) {
        execlp ("xxx", "xxx", "a", "b", (char *) 0);
        fprintf (stderr, "xxx not executed\n");
        exit (2);
        }
    
  • En BSD, on a trouvé insupportable de faire fork puis exec. D'où le nouvel appel système vfork.

  • Cours 3

    Duplication de file descriptors

  • Si on écrit un shell, on veut lancer des nouveaux programmes avec exec et avec l'entrée/sortie standard prédéfinie (à cause de < ou >). Or les file descriptors traversent exec. D'où
    fd1 = open ("fichier1", 1);
    fd2 = open ("fichier2", 2);
    fd3 = open ("fichier3", 2);
    if ((pid = fork()) == 0) {
        close (0); dup (fd1);
        close (1); dup (fd2);
        close (2); dup (fd3);
        execlp ("prog", ...);
        exit (1);
    }
    
  • dup() duplique le file descriptor sur le premier disponible. On peut aussi utiliser dup2().
  • Réciproquement, on veut pouvoir remettre au terminal les file descriptors standards avec
    fd1 = open ("/dev/ttÿ, 1);
    fd2 = open ("/dev/ttÿ, 2);
    fd3 = open ("/dev/ttÿ, 2);
    if ((pid = fork()) == 0) {
        close (0); dup (fd1);
        close (1); dup (fd2);
        close (2); dup (fd3);
        execlp ("prog", ...);
        exit (1);
    }
    

  • Cours 3

    Duplication de file descriptors (suite)

  • FILE *fdopen(int fd, char *mode)
    permet d'avoir un file pointer en fonction d'un file descriptor.
  • int fileno(FILE *fp)
    donne le file descriptor associé au file pointer.

  • Cours 3

    Exercices en TD et à la maison

  • Débutants:
    ls et ls -R. On devra parcourir la structure renvoyée par stat(). (Faire attention aux liens).
  • Chevronnés:
    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 f1, f2, ... fn les n lignes de fichier d'entrée. On met les f1, f2, ... fn dans N seaux équi-répartis.
  • 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.
  • 2 options sont possibles pour pgrep:
    • on liste toutes les lignes des fichiers contenant string,
    • on arrête le tout dès qu'on a trouvé une ligne. Utiliser kill(p,SIGKILL) pour tuer p.
  • on fera varier N et mesurera les différences de vitesse.
  • Il reste à bien comprendre comment débugger en gdb avec plusieurs processus....