15
Prof. C.EL AMRANI Programmation système sous UNIX Chapitre 1. 1. Les appels système pour les processus 1.1. Les processus Un processus est un programme qui s’exécute et qui possède son compteur ordinal, ses registres et ses variables. Conceptuellement, chaque processus a son propre processeur virtuel. En réalité, le vrai processeur commute entre plusieurs processus. Cette commutation rapide s’appelle multiprogrammation. 1.1.1. Hiérarchie entre les processus Les systèmes d’exploitation qui font appel au concept de processus doivent permettre de créer les processus requis. Dans la plupart des systèmes, il faut pouvoir créer et détruire dynamiquement les processus. En Unix, un processus particulier init est lancé au démarrage de la machine. Il crée un processus par terminal, lorsque la connexion est validée, il lance un nouveau processus chargé de lire et d’interpréter les commandes de l’utilisateur (Shell). Chacune de ces commandes peut elle-même créer un nouveau processus et ainsi de suite. Notez qu’un processus n’a qu’un seul père mais peut avoir plusieurs fils. Tous les processus du système font donc partie d’un seul arbre dont la racine est init. 1.1.2. Les différents états d’un processus Les processus, bien qu’étant des entités indépendantes, doivent parfois interagir avec d’autres processus. Les résultats d’un processus peuvent, par exemple, être les données d’un autre. Exemple : ls –l | more Les états possibles d’un processus sont les suivants : 1. Elu : en cours d’exécution. Un processus élu peut être arrêté, même s’il peut poursuivre son exécution, si le SE décide d’allouer le processeur à un autre processus 2. Bloqué : attend un événement extérieur pour pouvoir continuer, par exemple une ressource, lorsque la ressource est disponible, il passe à l’état prêt 3. prêt : suspendu provisoirement pour permettre l’exécution d’un autre processus Quatre transitions peuvent avoir lieu entre ces différents états : Elu Bloqué Prêt 2 1 3 4

prog systéme

Embed Size (px)

DESCRIPTION

programmation linux

Citation preview

Page 1: prog systéme

Prof. C.EL AMRANI Programmation système sous UNIX

Chapitre 1. 1. Les appels système pour les processus

1.1. Les processus Un processus est un programme qui s’exécute et qui possède son compteur ordinal, ses registres et ses variables. Conceptuellement, chaque processus a son propre processeur virtuel. En réalité, le vrai processeur commute entre plusieurs processus. Cette commutation rapide s’appelle multiprogrammation.

1.1.1. Hiérarchie entre les processus Les systèmes d’exploitation qui font appel au concept de processus doivent permettre de créer les processus requis. Dans la plupart des systèmes, il faut pouvoir créer et détruire dynamiquement les processus. En Unix, un processus particulier init est lancé au démarrage de la machine. Il crée un processus par terminal, lorsque la connexion est validée, il lance un nouveau processus chargé de lire et d’interpréter les commandes de l’utilisateur (Shell). Chacune de ces commandes peut elle-même créer un nouveau processus et ainsi de suite. Notez qu’un processus n’a qu’un seul père mais peut avoir plusieurs fils. Tous les processus du système font donc partie d’un seul arbre dont la racine est init.

1.1.2. Les différents états d’un processus Les processus, bien qu’étant des entités indépendantes, doivent parfois interagir avec d’autres processus. Les résultats d’un processus peuvent, par exemple, être les données d’un autre. Exemple : ls –l | more Les états possibles d’un processus sont les suivants : 1. Elu : en cours d’exécution. Un processus élu peut être arrêté, même s’il peut poursuivre

son exécution, si le SE décide d’allouer le processeur à un autre processus 2. Bloqué : attend un événement extérieur pour pouvoir continuer, par exemple une

ressource, lorsque la ressource est disponible, il passe à l’état prêt 3. prêt : suspendu provisoirement pour permettre l’exécution d’un autre processus Quatre transitions peuvent avoir lieu entre ces différents états :

Elu

Bloqué Prêt

2 1

3

4

Page 2: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

2

1 : Le processus se bloque en attente de données 2 : L’ordonnanceur choisit un autre processus 3 : L’ordonnanceur choisit ce processus 4 : Les données deviennent disponibles Le modèle des processus permet de mieux comprendre ce qui se passe à l’intérieur du système. Une partie des processus exécutent les programmes qui traitent les commandes tapées par les utilisateurs. Les autres processus font partie du système et gèrent les tâches telles que les requêtes au gestionnaire de fichiers ou les opérations sur les disques. Ces processus se bloquent lorsqu’ils attendent un évènement. La couche la plus basse d’un SE est l’ordonnanceur (scheduler). Il est surmonté par une multitude de processus. La gestion des interruptions, la suspension et la relance des processus sont reportées dans l’ordonnanceur.

1.1.3. Les processus en UNIX La commande ps liste, avec plus ou moins de détails, tous les processus ou seulement ceux de l’utilisateur, suivant différentes options. Ainsi : ps –fH –uetudiant1 présente les champs :

UID Nom du propriétaire du processus

PID Numéro du processus

PPID Numéro du processus père

STIME Heure de lancement du processus

TTY Le terminal ou la fenêtre du processus

CMD La commande qui a permis de lancer le processus

ps –e : affiche tous les processus de tous les utilisateurs Exemple : ps –fH –uetudiant

UID PID PPID C STIME TTY TIME CMD

etudiant10 533 1 0 09:44 pts/0 00:00:03 xemacs

etudiant10 531 1 0 09:44 pts/0 00:00:01 konsole

etudiant10 535 531 0 09:44 pts/1 00:00:00 /bin/bash

etudiant10 …

602 535 0 09:50 pts/1 00:00:00 more essai1.dat

0

1

n-2

n-1

Ordonnanceur (scheduler)

Page 3: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

3

La hiérarchie des processus est représentée par l’arbre suivante : Le processus 1 (init) est le père des processus 533 et 531 correspondant à des fenêtres X Window.

1.2. Les appels système (processus)

1.2.1. Caractéristiques d’un processus Les appels systèmes :

int getpid () fournit le numéro du processus en cours d’exécution int getppid () fournit le numéro du père du processus

#include <stdio.h> #include <unistd.h> main() { printf("pid: %d, ppid: %d\n",getpid(), getppid()); } Un utilisateur non propriétaire d’un programme exécutable peut obtenir, le temps de l’exécution du programme, l’identité du propriétaire du programme de façon à bénéficier des droits d’accès du propriétaire. Pour cela, le propriétaire doit positionner un droit s au lieu du x pour les droits d’accès du programme Lors de l’exécution, il y a un utilisateur réel (uid) et un utilisateur effectif (euid). La même possibilité existe pour les groupes, un utilisateur peut prendre le groupe du propriétaire du fichier, le temps de l’exécution du programme. Il y a donc un groupe réel (gid) et un groupe effectif (egid). Les appels systèmes correspondant sont :

int getuid () fournit le numéro du propriétaire réel int geteuid () fournit le numéro du propriétaire effectif int getgid () fournit le numéro du groupe réel int getegid () fournit le numéro du groupe effectif

Exemple ;

1

533 (xemacs) 531 (konsole)

535 (/bin/bash)

602 (more)

Page 4: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

4

L’exécutable /usr/bin/passwd, dont ‘root’ est propriétaire, a un droit d’accès s (-r-s--x--x). Les utilisateurs de /usr/bin/passwd acquièrent, le temps de l’exécution du programme, les droits d’accès root sur /etc/passwd (-rw-r--r--).

1.2.2. L’appel système pipe Un fichier est constitué de plusieurs descripteurs. Un descripteur est un entier compris entre 0 et 255 pour Linux. 0, 1 et 2 sont réservés pour l’entrée, la sortie standard la sortie en cas d’erreurs. (3 pour la sortie d’un tube, 4 pour l’entrée d’un tube) Un tube de communication est un tuyau dans lequel un processus écrit des données qu’un autre processus peut lire. L’appel système pipe () crée un tube anonyme et fournit un double descripteurs utilisé pour lire ou écrire dans le tube : int pipe (int descripteur [2]) déclaré dans <unistd.h> Le tube est entièrement sous le contrôle du noyau et réside en mémoire. Le processus reçoit les deux descripteurs correspondant à l’entrée et à la sortie du tube. Le descripteur d’indice 0 est la sortie du tube, il est ouvert en lecture seule. Le descripteur 1 est l’entrée, ouverte en écriture seule. En effet, les tubes sont des systèmes de communication unidirectionnels.

Tube de communication #include<stdio.h> #include<unistd.h> // pour certaines distributions de Linux, exit() est définit dans main() // <stdlib.h>, il faudrait donc rajouter cette bibliothèque pour que { // ça fonctionne int tube[2]; unsigned char buffer[256]; int i; printf("creation du tube\n"); if(pipe(tube)!=0) { perror("pipe"); exit(1);

NOYAU

Entée Sortie

Processus read() write()

Processus 0

2

tube[1] tube[0]

1

Page 5: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

5

} for (i=0;i<256;i++) buffer[i]=i; printf("ecriture dans le tube\n"); if(write(tube[1],buffer,256)!=256) { perror("ecriture"); exit(1); } printf("lecture depuis le tube\n"); if(read(tube[0],buffer,256)!=256) { perror("lecture"); exit(1); } printf("tube[0]: %d\n",tube[0]); /* =3 (descripteur 3 du processus), lecture, sortie du tube */ printf("tube[1]: %d\n",tube[1]); /* =4 (descripteur 4 du processus), écriture, entrée du tube */ } les descripteurs pointent vers les inodes du tube. La création d’un tube pour un seul processus ne présente aucun intérêt. On utilise ce mécanisme pour faire communiquer deux processus (ou plus), en invoquant les appels systèmes exec et fork.

Tube du père vers le fils

1.2.3. L’appel système execve et les fonctions de la famille exec L’appel système execve() exécute un programme exécutable ou un script shell. La syntaxe est la suivante : int execve (const char* filename, char* const argv[ ], char* const envp[ ]) ;

filename Nom ou chemin du fichier à exécuter argv Tableau contenant les arguments du programme envp Tableau contenant les variables d’environnement

Rappel :

Rappel sur main : main (int argc, char* argv[ ], char* envp [ ])

0

1

2

3 tube[0]

4 tube[1]

Père

Fils

tube[1] tube[0]

Page 6: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

6

le père

argc Nombre de paramètres sur la ligne de commande (c : counter) argv Pointeur vers un tableau de pointeurs qui désignent des données de type char

(v : vector) envp Pointeur vers un tableau de pointeurs qui désignent les variables

d’environnement (p : pointer) Rappel sur un pointeur avec indice : Soit: char *pt; On a: pt[ i ] = *(pt+i) avec pt=&t[k] par exemple Et donc pt[ i ] est de type char Il existe 5 variantes de execve () Les paramètres sont données dans une liste (d’où l), terminée par NULL : int execl (const char* filename, char* const arg, …) ; int execlp (const char* filename, char* const argv, …) ; p indique que l’exécutable doit être cherché en utilisant la variable PATH int execle (const char* filename, char* const argv, …, char* const envp[ ]) ; e indique que le tableau des variables d’environnement suit la liste des paramètres int execv (const char* filename, char* const argv[ ]) ; v indique que les paramètres sont dans un tableau (v : vector) int execvp (const char* filename, char* const argv[ ]) ; Exemple : /* execls.c */ #include<stdio.h> #include<unistd.h> main() { execlp("ls","ls",NULL); perror("erreur sur execlp"); } L’exécution donne le listing des fichiers/répertoires du répertoire courant.

1.2.4. L’appel système fork de création de processus L’appel système fork () (de <unistd.h>) crée un processus fils qui diffère de son père uniquement par son numéro de PID et PPID. Fork() fournit 0 pour le fils. La syntaxe est : pid_t fork(void)

fork()

exit(0)

le fils

=0

!=0

Page 7: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

7

fork1.c #include<stdio.h> #include<unistd.h> main() { if (fork()==0) { //sleep (5) ; /* attendre 5 secondes */ printf ("processus fils: PID = %d , PPID = %d\n",getpid(), getppid()); exit(0); } printf("processus pere: PID = %d , PPID = %d\n",getpid(),getppid()); } Exécution : processus fils: PID = 2024 , PPID = 2023 processus pere: PID = 2023 , PPID = 1924 Rq : si l’instruction sleep(5) est prise en compte :

processus pere: PID = 2033 , PPID = 1924 autres commandes… processus fils: PID = 2034 , PPID = 1 le processus fils est adopté par le processus

initial init de PID = 1, car le père se termine avant le fils

1.2.5. Autres appels système sur les processus l’appel système wait bloque un processus en attente de la fin de l’exécution d’un fils qu’il a créé. La syntaxe est : pid_t wait (int * status) ; la fonction retourne le PID du fils qui vient de se terminer. Status, s’il est non NULL, reçoit des informations sur la façon dont s’est terminée le processus (valeur du exit du fils). Il existe aussi l’appel système : waitpid qui permet d’attendre la fin d’un fils particulier. L’appel système _exit termine le processus courant. S’il reste des fils non terminés, ceux-ci reçoivent le processus 1 pour père. Sa syntaxe : void _exit (int status) ; La fonction exit réalise l’appel système _exit : void exit (int status) ; La fonction sleep () suspend un processus pendant le nombre de secondes indiqué en paramètre. Sa syntaxe : unsigned int sleep (unsigned int seconds) ; Si la fonction est interrompue (par un signal) elle fournit le nombre de secondes restant avant la fin programmée, 0 sinon.

Page 8: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

8

le père

wait attendre la fin du 1er fils

wait attendre la fin du 2ème fils

1.3. Utilisation de fork, exec, pipe, dup2

1.3.1. Exécution de deux processus séquentiels indépendants Pour exécuter les deux commandes : pwd et ls -li l’interpréteur de commandes (bash) doit créer un processus, lui faire exécuter pwd et attendre la fin du processus fils. Il peut alors créer un nouveau processus et lui faire exécuter la commande ls -l Le programme en C fork1.c est le suivant : #include<stdio.h> #include<unistd.h> #include<wait.h> main() { if(fork()==0) { printf("execution de pwd\n"); execlp("pwd","pwd",NULL); perror ("Pb pwd"); exit(1); } wait(0); if(fork()==0) { printf("execution de ls -l\n"); execlp("ls","ls","-l",NULL);

fork()

exit(1) en cas d’erreur

Le 1° fils exécute

pwd et se termine

=0

!=0

fork()

exit(1) en cas d’erreur

Le 2° fils exécute ls -l et se termine

=0

!=0

Création de 2 processus séquentiels indépendants

Page 9: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

9

perror("Pb ls -li"); exit(1); } wait(0); } Exécution : $ ./fork2 $ execution de pwd $ /home/compte6/syst $ execution de ls -l total 88 62662 -rwxr--r-- 1 compte6 groupe2 14 avr 11 15:33 c 62658 -rwxr-xr-x 1 compte6 groupe2 11795 avr 11 16:52 execls …

1.3.2. Redirection des entrées et des sorties sur un fichier

Filtres en C Un filtre Unix lit les données sur son entrée standard, les modifie et les envoie sur sa sortie standard. Les entrées et sorties standards peuvent être redirigées vers un fichier ou un tube. Les messages d’erreurs doivent être écrits sur la sortie d’erreur (N° 2, stderr au niveau de la bibliothèque standard, pour les filtres en C) Exemples : soit le filtre majuscul : majuscul.c #include<stdio.h> #include<ctype.h> /* toupper */ main() { int c; while((c=getchar())!=EOF) putchar(toupper(c)); } exécution : (soit un fichier de texte essai1.txt : bonjour) $ ./majuscul < essai1.txt (dans essai1.txt : bonjour) BONJOUR soit le filtre par :

par.c #include<stdio.h> #include<stdlib.h> main(int argc, char* argv[])

2

0

filtre 1

Page 10: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

10

{ int largeur=10; /*par defaut*/ int n=0; /*nombre de caracteres*/ char c; if (argc==2)largeur= atoi(argv[1]); while((c=getchar()) != EOF) { if(c=='\n')c=' '; putchar(c); if(++n >= largeur) { putchar('\n'); n=0; } } putchar('\n'); } exécution : (soit un fichier de texte essai2.txt : bonjour monsieur) $ ./par 4 < essai2.txt bonj our mons ieur Voici un programme en shell bash qui exécute ces deux programmes, et communique les informations de sortie du premier, en entrée du second : majparsh #!/bin/sh if [ $# -ne 1 ]; then echo "preciser le fichier a traiter" exit 1 fi nomfe=$1 nomfm=temp.tmp ./majuscul < $nomfe > $nomfm ./par 8 < $nomfm rm -f $nomfm exécution : $ ./majparsh bonjour1.txt BONJOUR MONSIEUR

Page 11: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

11

fils

0

1

2

fe fm

fils

Il est possible de créer un programme C qui effectue la même fonction. Il faut rediriger l’entrée et la sortie du premier fils (exécutant majuscul) et l’entrée standard du second fils (exécutant par). Ouverture d’un fichier int open (const char* pathname, int flags, mode_t mode) (voir : man open)

pathname nom du fichier à ouvrir

flags options dans l’ouverture du fichier :

O_RDONLY ouverture en lecture

O_WRONLY ouverture en écriture

O_CREAT création du fichier s’il n’existe pas

O_TRUNC vider le fichier s’il n’est pas vide

… …

mode Précise les droits d’accès du fichier à créer : (avec des constantes symboliques comme : S_IRUSR ≡ 400, S_IWGRP ≡ 20, … définit dans <sys/stat.h>)

u g o

r w x r w x r w x

400 200 100 40 20 10 4 2 1

dup2(d,1) : redirige le descripteur 1 (sortie standard) vers le descripteur d

unlink : supprime un fichier (lien)

1 0

majuscul

1 0

par

Page 12: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

12

0

1

2

fm

majparc.c #include<stdio.h> #include<fcntl.h> /* O_RDONLY, O_WRONLY, 0_CREAT */ #include<unistd.h> /* fork, dup2, execlp */ #include<sys/wait.h> /* wait */ main(int argc, char* argv[]) { char* nomfe; char nomfm[100]; /* nom du fichier temporaire */ if (argc !=2) {printf ("specifier le fichier a traiter");exit(1);} nomfe=argv[1]; sprintf(nomfm,"temp.tmp"); if(fork()==0){ int fe=open(nomfe,O_RDONLY); if(fe==-1){perror("pb ouverture de fe"); exit(1);} dup2(fe,0); int fm=open(nomfm,O_WRONLY | O_TRUNC | O_CREAT, 0666); if(fm==-1){perror("pb ou verture de fm"); exit(1);} dup2(fm,1); execlp("./majuscul","./majuscul",NULL); perror ("pb de execlp"); exit(1); } wait(0); if (fork()==0){ int fm=open(nomfm,O_RDONLY); if (fm==-1){perror("pb ouverture de fm");exit(1);} dup2(fm,0); unlink(nomfm); execlp("./par","./par","8",NULL); perror("pb de execlp"); exit(1);

Page 13: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

13

} wait(0); } exécution : $ ./majparc bonjour1.txt BONJOUR MONSIEUR

1.3.3. Redirection des entrées et des sorties sur un tube

1.3.3.1. Faire communiquer deux processus

Les deux processus majuscul et par peuvent communiquer à l’aide d’un tube. Le script bash serait : majparsh #!/bin/sh if [ $# -ne 1 ]; then echo "preciser le fichier a traiter" exit 1 fi nomfe=$1 ./majuscul < $nomfe | ./par 8 exécution : $ ./majparsh2 bonjour1.txt BONJOUR MONSIEUR

Communication par tube entre 2 processus Le programme majparc2.c réalise la même fonction : majparc2.c #include<stdio.h> #include<fcntl.h> #include<unistd.h> main(int argc, char* argv[]) { char* nomfe; int p[2];

par 1 0 majuscul 0 1

père

0

1

2

p[0]

p[1]

fils 0

fe

p[0]

p[1]

1

2

tube p

Page 14: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

14

if(argc !=2) {printf ("specifier le fichier traiter"); exit(1);} nomfe=argv[1]; if(pipe(p)==-1) {perror("pipe"); exit (1);} if(fork()==0){ /* fils */ int fe=open(nomfe,O_RDONLY); if(fe==-1){perror("ouverture de fe");exit(1);} dup2(fe,0); /* entree standard = nomfe */ dup2(p[1],1); /* sortie standard = tube */ close(fe); /* fe est devenu inutil */ close(p[0]); /* le fils ne lit pas dans le tube */ close(p[1]); /* p[1] est devenu inutil */ execlp("./majuscul","./majuscul",NULL); perror("Pb avec execlp majuscul"); exit(1); } /* pere */ dup2(p[0],0); /* entree standard = tube */ close(p[0]); /* p[0] est devenu inutil */ close(p[1]); /* le pere n’ecrit pas dans le tube */ execlp("./par","./par","8",NULL); perror("Pb avec execlp par"); exit(1); } Exécution $ ./majparc2 bonjour1.txt BONJOUR MONSIEUR

1.3.3.2. Récupérer un résultat d’exécution

tube unidirectionnel

date 0 1

père

0

1

2

p[0]

p[1]

fils

0

p[0]

p[1]

1

2

tube p

Page 15: prog systéme

Chapitre 1. Les appels système pour les processus

Prof. C.EL AMRANI Programmation système sous UNIX

15

lecture et écriture de caractères : read, write ssize_t read (int fd, void* buf, size_t count) ; ssize_t write (int fd, void* buf, size_t count) ; avec : fd Numéro de descripteur buf Adresse mémoire count Nombre de caractères lu tubedate.c #include<stdio.h> #include<unistd.h> #include<wait.h> main() { int i, p[2]; char c, res[50]; if(pipe(p)==-1){perror("Pb tube");exit(1);} /* fils */ if(fork()==0){ dup2(p[1],1); close(p[0]); close(p[1]); execlp("date","date","+%d/%m/%Y",NULL); perror("Pb avec execlp date"); exit(1); } /* pere */ close(p[1]); i=0; while(read(p[0],&c,1)!=0) {res[i++]=c;} res[i-1]=0; /* supprime le \n */ printf("Date du jour: %s\n",res); close(p[0]); //wait(0); }