Upload
tiffany-wilson
View
9
Download
0
Embed Size (px)
DESCRIPTION
programmation linux
Citation preview
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
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)
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)
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
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]
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
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.
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
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
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
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
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);
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
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
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); }