Ce cours porte sur l’utilisation des appels système des systèmes de la famille Unix : Linux, MacOS X, AIX, LynxOS, BeOS, QNX, OpenBSD, FreeBSD,NetBSD.
Le rôle d’un système est de (vois la figure 1) :
La norme POSIX (Portable Operating System Interface uniX) est un ensemble de standards de l’IEEE (Institute of Electrical and Electronics Engineers). POSIX définit notamment :
Nous allons voir dans ce chapitre les passages d’arguments et variables d’environnement qui permettent à un shell de transmettre des informations à un programme qu’il lance. Plus généralement, ces techniques permettent à un programme de transmettre des informations aux programmes qu’il lance (processus fils ou descendants).
Parfois, un nombre nous est donné sous forme de chaîne de caractère dont les caractères sont des chiffres. Dans ce cas, la fonction atoi permet de réaliser la conversion d’une chaîne vers un int.
1 #include <s t d i o . h>
2
3 int main ( )
4 {
5 int a ;
6 char s [ 5 0 ] ;
7
8 printf ( ” S a i s i s s e z ␣ des ␣ c h i f f r e s ␣ :␣” ) ;
9 scanf ( ”%s ” , s ) ; \com{ s a i s i e d ’ une␣ chaîne ␣de␣ c a r a c t è r e s }
10 ␣␣␣a␣=␣ a t o i ( s ) ;␣␣\com{ conversion ␣en␣ e n t i e r }
11 ␣␣␣ printf (” Vous␣ avez ␣ s a i s i ␣ :␣%d\n” , ␣a ) ;
12 ␣␣␣ return ␣0 ;
13 }
Plus généralement, la fonction sscanf permet de lire des données formatées dans une chaîne de caractère (de même que scanf permet de lire des données formatées au clavier ou fscanf dans un fichier texte).
1 #include <s t d i o . h>
3 int main ( )
4 {
5 float x ;
6 char s [ 5 0 ] ;
8 printf ( ” S a i s i s s e z ␣ des ␣ c h i f f r e s ␣ ( avec ␣un␣ point ␣au␣ milieu ) ␣ :␣” ) ;
9 scanf ( ”%s ” , s ) ; /* s a i s i e d ’ une chaîne de c a r a c t è r e s */
10 s s c a n f ( s , ”%f ” , &x ) ; /* l e c t u r e dans l a chaîne */
11 printf ( ”Vous␣ avez ␣ s a i s i ␣ :␣%f \n” , x ) ;
12 return 0 ;
13 }
Inversement, la fonction sprintf permet d’écrire des données formatées dans une chaîne de caractères (de même que printf permet d’écrire dans la console ou fprintf dans un fichier texte).
1 #include <s t d i o . h>
3 void AfficheMessage ( char *message )
4 {
5 puts ( message ) ;
6 }
8 int main ( )
9 {
10 float x ;
11 int a ;
13 printf ( ” S a i s i s s e z ␣un␣ e n t i e r ␣ et ␣un␣ r é e l ␣ :␣” ) ;
14 scanf ( ”%d␣%f ” , &a , &x ) ;
15 s printf ( s , ”Vous␣ avez ␣ tapé ␣ :␣a␣=␣%d␣x␣=␣%f ” , a , x ) ;
16 AfficheMessage ( s ) ;
17 return 0 ;
18 }
La fonction main d’un programme peut prendre des arguments en ligne de commande. Par exemple, si un fichier monprog.c a permis de générer un exécutable monprog à la compilation,
1 gcc monprog . c −o monprog
on peut invoquer le programme monprog avec des arguments
1 . / monprog argument1 argment2 argument3
Exemple. La commande cp du bash prend deux arguments :
1 $ cp nomfichier1 nomfichier2
Pour récupérer les arguments dans le programme C, on utilise les paramètres argc et argv du main. L’entier argc donne le nombre d’arguments rentrés dans la ligne de commande plus 1, et le paramètre argv est un tableau de chaînes de caractères qui contient comme éléments :
Le prototype de la fonction main est donc :
1 int main ( int argc , char** argv ) ;
Exemple. Voici un programme longeurs, qui prend en argument des mots, et affiche la longueur de ces mots.
1 #include <s t d i o . h>
2 #include <s t r i n g . h>
4 int main ( int argc , char** argv )
5 {
6 int i ;
7 printf ( ”Vous␣ avez ␣ entré ␣%d␣mots\n” , argc −1) ;
8 puts ( ” Leurs ␣ longueurs ␣ sont ␣ :” ) ;
9 for ( i=1 ; i<argc ; i++)
10 {
11 printf ( ”%s ␣ :␣%d\n” , argv [ i ] , s t r l e n ( argv [ i ] ) ) ;
12 }
13 return 0 ;
14 }
Voici un exemple de trace :
1 $ gcc longueur . c −o longueur
2 $ . / longueur toto blabla
3 Vous avez entré 2 mots
4 Leurs longueurs sont :
5 toto : 4
6 blabla : 6
Les variables d’environnement sont des affectations de la forme 1 NOM=VALEUR qui sont disponibles pour tous les processus du système, y compris les shells. Dans un shell, on peut avoir la liste des varialbles d’environnement par la commande env. Par exemple, pour la variable d’environnement PATH qui contient la liste des répertoires où le shell va chercher les commandes exécutables :
1 $ echo PATH
2 / usr / local / bin :/ usr / bin :/bin :/ usr / bin /X11
3 $ PATH=PATH :.
4 $ export PATH
5 $ env | grep PATH
6 PATH=/usr / local / bin :/ usr / bin :/bin :/ usr / bin /X11 :.
la commande export permet de transmettre la valeur d’une variable d’environnement aux descendants d’un shell (programmes lancés à partir du shell).
1 #include <s t d i o . h>
3 extern char ** environ ;
5 int main ( void)
6 {
7 int i ;
8 for ( i=0 ; environ [ i ] !=NULL ; i++)
9 puts ( environ [ i ] ) ;
10 return 0 ;
11 }
Pour accéder à une variable d’environnement particulière à partir de son nom, on utilise la fonction getenv, qui prend en paramètre le nom de la variable et qui retourne sa valeur sous forme de chaîne de caractère.
1 #include <s t d i o . h>
2 #include <s t d l i b . h> /* pour u t i l i s e r getenv */
4 int main ( void)
5 {
6 char * valeur ;
7 valeur = getenv ( ”PATH” ) ;
8 i f ( valeur != NULL)
9 printf ( ”Le␣PATH␣vaut␣ :␣%s \(\ backslash \)n” , valeur ) ;
10 valeur = getenv ( ”HOME” ) ;
11 i f ( valeur != NULL)
12 printf ( ”Le␣home␣ d i r e c t o r y ␣ e s t ␣dans␣%s \(\ backslash \)n” , valeur ) ;
13 return 0 ;
14 }
Pour assigner une variable d’environnement, on utilise la fonction putenv, qui prend en paramètre une chaîne de caractère. Notons que la modification de la variable ne vaut que pour le programme lui-même et ses descendants (autres programmes lancés par le programme), et ne se transmet pas au shell (ou autre) qui a lancé le programme en cours.
1 #include <s t d i o . h>
2 #include <s t d l i b . h> /* pour u t i l i s e r getenv */
4 int main ( void)
5 {
6 char *path , *home , *nouveaupath ;
7 char a s s i g n a t i o n [ 1 5 0 ] ;
8 path = getenv ( ”PATH” ) ;
9 home = getenv ( ”HOME” ) ;
10 printf ( ” ancien ␣PATH␣ :␣%s \ net ␣HOME␣ :␣%s \n” ,
11 path , home) ;
12 s printf ( assignation , ”PATH=%s :%s / bin ” , path , home) ;
13 putenv ( a s s i g n a t i o n ) ;
14 nouveaupath = getenv ( ”PATH” ) ;
15 printf ( ”nouveau␣PATH␣ :␣\n%s \n” , nouveaupath ) ;
16 return 0 ;
17 }
Exemple de trace :
1 $ gcc putenv . c −o putenv
2 echo PATH
4 $ . / putenv
5 ancien PATH : / usr / local / bin :/ usr / bin :/bin :/ usr / bin /X11
6 et HOME : /home/remy
7 nouveau PATH :
8 / usr / local / bin :/ usr / bin :/bin :/ usr / bin /X11 :/home/remy/ bin
9 echo PATH
10 / usr / local / bin :/ usr / bin :/bin :/ usr / bin /X11
Chaque programme (fichier exécutable ou script shell,Perl) en cours d’exécution dans le système coorespond à un (ou parfois plusieurs) processus du système. Chaque processus possède un numéro de processus (PID).
Sous unix, on peut voir la liste des processus en cours d’exécution, ainsi que leur PID, par la commande ps, qui comporte différentes options.
Pour voir ses propres processus en cours d’exécution on peut utiliser le commande
1 $ ps x
Pour voir l’ensemble des processus du système, on peut utiliser la commande
1 $ ps −aux
Pour voir l’ensemble des attributs des processus, on peut utiliser l’option -f. Par exemple, pour l’ensemble des attributs de ses propres processus, on peut utiliser
1 $ ps −f x
Un programme C peut accéder au PID de son instance en cours d’exécusion par la fonction getpid, qui retourne le PID :
1 pid_t getpid (\ void) ;
Nous allons voir dans ce chapitre comment un programme C en cours d’exécution peut créer un nouveau processus (fonction fork), puis au chapitre suivant comment un programme C en cours d’exécution peut se faire remplacer par un autre programme,tout en gardant le même numéro de processus (fonction exec). L’ensemble de ces deux fonction permettra à un programme C de lancer un autre programme. Nous verrons ensuite la fonction system, qui permet directement de lancer un autre programme, ainsi que les problèmes de sécurité liés à l’utilisation de cette fonction.
1 uid_t getuid (\ void) ;
Il existe une permission spéciale, uniquement pour les exécutables binaires, appelée la permission Set-UID. Cette permission permet à un ustilisateur ayant les droits en exécution sur le fichier dexécuter le fichier avec les privilège du propriétaire du fichier. On met les droits
Set-UID avec chmod +s.
1 $ chmod +x f i c h i e r
2 $ l s −l
3 −rwxr−xr−x 1 remy remy 7145 Sep 6 14 :04 f i c h i e r
4 $ chmod +s f i c h i e r
5 −rwsr−sr−x 1 remy remy 7145 Sep 6 14 :05 f i c h i e r
La fonction fork permet à un programme en cours d’exécution de créer un nouveau processus.
Le processus d’origine est appelé processus père, et il garde son PID, et le nouveau processus créé s’appelle processus fils, et possède un nouveau PID. Le processus père et le processus fils ont le même code source, mais la valeur retournée par fork permet de savoir si on est dans le processus père ou fils. Ceci permet de faire deux choses différentes dans le processus père et dans le processus fils (en utilisant un if et un else ou un switch), même si les deux processus on le même code source.
La fonction fork retourne -1 en cas d’erreur, retourne 0 dans le processus fils, et retourne le PID du fils dans le processus père. Ceci permet au père de connaître le PID de son fils.
1 #include <s t d l i b . h>
2 #include <s t d i o . h>
3 #include <unistd . h>
5 int main ( void)
6 {
7 pid_t p i d _ f i l s ;
9 p i d _ f i l s = fork ( ) ;
10 i f ( p i d _ f i l s == −1)
11 {
12 puts ( ” Erreur ␣de␣ c r é a t i o n ␣du␣nouveau␣ processus ” ) ;
13 e x i t (1) ;
14 }
15 i f ( p i d _ f i l s == 0)
16 {
17 printf ( ”Nous␣sommes␣dans␣ l e ␣ f i l s \n” ) ;
18 /* l a fonction g e t p i d permet de connaître son propre PID */
19 printf ( ”Le␣PID␣du␣ f i l s ␣ e s t ␣\%d\n” , getpid ( ) ) ;
21 (PID de son père ) */
22 printf ( ”Le␣PID␣de␣mon␣ père ␣ (PPID) ␣ e s t ␣\%d” , getppid ( ) ) ;
23 }
24 else
25 {
26 printf ( ”Nous␣sommes␣dans␣ l e ␣ père \n” ) ;
27 printf ( ”Le␣PID␣du␣ f i l s ␣ e s t ␣\%d\n” , p i d _ f i l s ) ;
28 printf ( ”Le␣PID␣du␣ père ␣ e s t ␣\%d\n” , getpid ( ) ) ;
29 printf ( ”PID␣du␣grand−père ␣ :␣\%d” , getppid ( ) ) ;
30 }
31 return 0 ;
32 }