Programmation système en C/C++ - Bienvenue sur le site de Jean
Transcription
Programmation système en C/C++ - Bienvenue sur le site de Jean
gcc Entrées et sorties Accès aux fichiers Processus Programmation système en C/C++ Jean-Baptiste Vioix ([email protected]) LE2I - ENESAD http://jb.vioix.free.fr 1-41 gcc Entrées et sorties Accès aux fichiers Processus Programmation en C/C++ sous Unix Il existe des très nombreux outils de développement sous Linux (KDevelop, Anjuta, ...). Ils utilisent tous le compilateur gcc, http://jb.vioix.free.fr 2-41 gcc Entrées et sorties Accès aux fichiers Processus Programmation en C/C++ sous Unix gcc s’appelle simplement sur la ligne de commande avec le nom du(des) fichier(s) source(s) (extension .c) comme paramètre(s). Après compilation, on obtient un fichier exécutable nommé, par défaut, a.out L’option -o permet de changer le nom du fichier crée : gcc -o programme programme.c http://jb.vioix.free.fr 3-41 gcc Entrées et sorties Accès aux fichiers Processus Programmation en C/C++ sous Unix Pour programmer en C++, le fichier doit avoir une extension compatible (comme .CC). De plus il faut indiquer à gcc les bibliothèques C++ avec l’option -l En pratique, on utilise g++ qui permet de lier automatiquement les bonnes librairies. La syntaxe est la même que gcc http://jb.vioix.free.fr 4-41 gcc Entrées et sorties Accès aux fichiers Processus Entrée standard Sortie standard et sortie des erreurs Valeur de retour Passage de paramètres Accès aux variables d’environnement Entrée standard En C, la sortie standard est lue avec la fonction scanf. En C++, la sortie standard est lue avec le flux cin #include<iostream.h> void main(){ char chaine[100]; cin>>chaine; } http://jb.vioix.free.fr 5-41 gcc Entrées et sorties Accès aux fichiers Processus Entrée standard Sortie standard et sortie des erreurs Valeur de retour Passage de paramètres Accès aux variables d’environnement Sortie standard et sortie des erreurs en C En C on accède à la sortie standard via la fonction printf. Pour accéder à la sortie des erreurs, on doit passer par le fichier stderr via fprintf Ces fonctions sont dans la bibliothèque stdio. #include<stdio.h> void main(){ printf("Sortie standard \n"); fprintf(stderr,"Sortie des erreurs \n"); } http://jb.vioix.free.fr 6-41 gcc Entrées et sorties Accès aux fichiers Processus Entrée standard Sortie standard et sortie des erreurs Valeur de retour Passage de paramètres Accès aux variables d’environnement Sortie standard et sortie des erreurs en C++ En C++ les sorties sont gérées par des flux. Le flux cout gère la sortie standard. Le flux cerr gère la sortie des erreurs. Ces fonctions sont dans la bibliothèque iostream. #include<iostream.h> void main(){ cout<<"Sortie standard \n"; cerr<<"Sortie des erreurs \n"; } http://jb.vioix.free.fr 7-41 gcc Entrées et sorties Accès aux fichiers Processus Entrée standard Sortie standard et sortie des erreurs Valeur de retour Passage de paramètres Accès aux variables d’environnement Valeur de retour Selon la norme UNIX un programme doit renvoyer un entier compris entre 0 et 255 après son exécution. Cette valeur vaut 0 si le déroulement du programme est normal, une autre valeur sinon. http://jb.vioix.free.fr 8-41 gcc Entrées et sorties Accès aux fichiers Processus Entrée standard Sortie standard et sortie des erreurs Valeur de retour Passage de paramètres Accès aux variables d’environnement Valeur de retour Pour renvoyer une valeur de retour il faut définir la fonction main comme renvoyant un int La valeur de retour du programme est alors définie en utilisant l’instruction return #include<iostream.h> int main(){ cout<<"Sortie standard \n"; cerr<<"Sortie des erreurs \n"; return 0; } http://jb.vioix.free.fr 9-41 gcc Entrées et sorties Accès aux fichiers Processus Entrée standard Sortie standard et sortie des erreurs Valeur de retour Passage de paramètres Accès aux variables d’environnement Valeur de retour On peut aussi quitter le programme de manière plus ”brutale” en utilisant l’instruction exit(int ) Le paramètre correspond à la valeur de retour. Dans ce cas, la valeur de retour doit être différente de 0 (sortie anormale du programme). #include<iostream.h> int main(){ cout<<"Un texte avant exit \n"; exit(1); cout<<"Un texte après exit\n"; return 0; } http://jb.vioix.free.fr 10-41 gcc Entrées et sorties Accès aux fichiers Processus Entrée standard Sortie standard et sortie des erreurs Valeur de retour Passage de paramètres Accès aux variables d’environnement Passage de paramètres Le passage de paramètres est possible avec des programmes écrits en C/C++. La fonction main doit alors avoir le prototype suivant : int main (int , char* ) La première variable correspond au nombre de paramètre sur la ligne de commande. La seconde est un tableau de chaı̂nes de caractères contenant les paramètres. http://jb.vioix.free.fr 11-41 gcc Entrées et sorties Accès aux fichiers Processus Entrée standard Sortie standard et sortie des erreurs Valeur de retour Passage de paramètres Accès aux variables d’environnement Passage de paramètres Lecture des paramètres : #include<iostream.h> int main(int argc, char* argv[]){ cout<<"Nombre de paramètres : "<<argc<<endl; for(int i=0;i<argc;i++) { cout<<i<<" "<<argv[i]<<endl; } return 0; } http://jb.vioix.free.fr 12-41 gcc Entrées et sorties Accès aux fichiers Processus Entrée standard Sortie standard et sortie des erreurs Valeur de retour Passage de paramètres Accès aux variables d’environnement Accès aux variables d’environnement Les variables d’environnement permettent d’accéder à certains paramètres du système (comme la langue, le shell, ...). La fonction char* getenv(char* ) permet de lire une variable, passée en paramètre. #include<iostream.h> int main(int argc, char* argv[]){ char* var; var = getenv("SHELL"); cout<<"Le shell est : "<<var<<endl; return 0; } http://jb.vioix.free.fr 13-41 gcc Entrées et sorties Accès aux fichiers Processus Entrée standard Sortie standard et sortie des erreurs Valeur de retour Passage de paramètres Accès aux variables d’environnement Accès aux variables d’environnement On peut créer ou modifier une variable d’environement à l’aide de int setenv(char* , char*, int) Le premier paramètre est le nom de la variable, le second sa valeur, le troisième permet de forcer la mise à jour (s’il vaut 1) ; Cette fonction renvoie 0 en cas de reussite, -1 sinon. #include<iostream.h> int main(int argc, char* argv[]){ int res; res = setenv ("UTILISATEUR","IQS",1); return 0; } http://jb.vioix.free.fr 14-41 gcc Entrées et sorties Accès aux fichiers Processus Accès bas niveaux Accès à des données formatées Accès bas niveaux La norme Unix propose différents niveaux d’accès aux fichiers. Les fonctions de bas niveaux permettent : d’ouvrir (ou créer) un fichier, de lire et d’écrire des octects dans un fichier, de se déplacer dans un fichier, de fermer un fichier. Les fichiers sont associés à un descripteur (de type int). Les descripteurs 0,1,2 sont respectivement l’entrée standard, la sortie standard, la sortie des erreurs. http://jb.vioix.free.fr 15-41 gcc Entrées et sorties Accès aux fichiers Processus Accès bas niveaux Accès à des données formatées Ouverture d’un fichier Pour associer un fichier à un descripteur on utilise la fonction open. La fonction renvoie un descripteur en cas de succès, -1 en cas d’échec. La fonction utilise deux paramètres : le nom du fichier, les modes d’ouvertures. Les modes d’ouvertures sont des entiers mais les bibliothèques permettent d’utiliser des équivalents int open(char* , int flags) http://jb.vioix.free.fr 16-41 gcc Entrées et sorties Accès aux fichiers Processus Accès bas niveaux Accès à des données formatées Ouverture d’un fichier : options d’ouverture Les modes basiques sont : O RDONLY : ouverture en lecture seule, O WRONLY : ouverture en écriture seule, O RDWR : ouveture en lecture/écriture. D’autres options sont possibles comme, par exemple : O APPEND :écriture en fin de fichier ; O TRUNC : vide le fichier avant d’écrire ; O CREAT : création du fichier. On combine les modes avec l’opérateur OU logique. http://jb.vioix.free.fr 17-41 gcc Entrées et sorties Accès aux fichiers Processus Accès bas niveaux Accès à des données formatées Ouverture d’un fichier : options de création Lors de la création d’un fichier, il convient d’ajouter les droits d’accès au fichier. Dans ce cas, la fonction open admet trois arguments : int open(char*, int flags, mode t mode) Les pricipaux droits sont : S IRUSR, S IRGRP, S IROTH : lecture pour l’utilisateur, le groupe, les autres ; S IWUSR, S IWGRP, S IWOTH : écriture pour l’utilisateur, le groupe, les autres ; S IXUSR, S IXGRP, S IXOTH : exécution pour l’utilisateur, le groupe, les autres ; S IRWXU, S IRWXG, S IRWO : tous les droits pour l’utilisateur, le groupes, les autres. Les droits d’accès peuvent aussi être écrits en octal (0600). http://jb.vioix.free.fr 18-41 gcc Entrées et sorties Accès aux fichiers Processus Accès bas niveaux Accès à des données formatées Fermeture d’un fichier Un fichier ouvert doit être fermé avant de quitter le programme. La fonction de fermeture ne nécessite que le descripteur du fichier. int close(int fd) La fonction renvoie 0 en cas de succès, -1 en cas d’échec. http://jb.vioix.free.fr 19-41 gcc Entrées et sorties Accès aux fichiers Processus Accès bas niveaux Accès à des données formatées Exemple simple #include<iostream.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> int main(int argc, char* argv[]){ int fd; fd = open("Fichier.txt", O_RDONLY); ... close(fd); return 0; } http://jb.vioix.free.fr 20-41 gcc Entrées et sorties Accès aux fichiers Processus Accès bas niveaux Accès à des données formatées Lecture dans un fichier La fonction int read(int fd, void* buffer, int n) permet de lire n octects depuis la position actuelle dans le fichier fd et les place dans buffer. La valeur de retour correspond au nombre d’octects lus, 0 quand la fin du fichier est atteinte, -1 en cas d’erreur. http://jb.vioix.free.fr 21-41 gcc Entrées et sorties Accès aux fichiers Processus Accès bas niveaux Accès à des données formatées Ecriture dans un fichier La fonction int write(int fd, void* buffer, int n) permet d’écrire n octects provenant de buffer dans le fichier fd depuis la position actuelle . La valeur de retour correspond au nombre d’octects écrits, -1 en cas d’erreur. http://jb.vioix.free.fr 22-41 gcc Entrées et sorties Accès aux fichiers Processus Accès bas niveaux Accès à des données formatées Exemple simple #include<iostream.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<unistd.h> int main(int argc, char* argv[]){ int fd; int nb_lus, nb_ecrits; char buffer[10]; ... nb_lus = read(fd, buffer, 10); ... nb_ecrits = write(fd, buffer, 10); ... return 0; } http://jb.vioix.free.fr 23-41 gcc Entrées et sorties Accès aux fichiers Processus Accès bas niveaux Accès à des données formatées Redirection des descripteurs Il est possible de rediriger un descripteur existant vers un autre descripteur. La fonction int dup2 (int oldfd, int newfd) permet d’associer le descripteur newfd au fichier pointé par oldfd. Après l’éxecution de la fonction, les deux descripteurs pointeront vers le fichier pointé par olfdfd. http://jb.vioix.free.fr 24-41 gcc Entrées et sorties Accès aux fichiers Processus Accès bas niveaux Accès à des données formatées Accès à des données formatées En C, la bibliothèque stdio.h fournie de nombreuses fonctions facilitant l’accès aux données. En C++ les objets fstream (dans la bibliothèque fstream.h) permettent de manipuler les données contenues dans des fichiers. Les interfaces graphiques (Gnome, KDE, ...) proposent des fonctions particulières pour accéder aux fichiers (via leurs librairies associées gtk et Qt entre autres). Les autres languages font appel aux fonctions bas niveaux vu précédement pour traiter les fichiers. http://jb.vioix.free.fr 25-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Définitions (1) Un programme est fichier exécutable stocké sur une mémoire de masse. Un processus est un programme en cours d’exécution. Chaque processus dispose d’un espace mémoire réservé. Le système d’exploitation exécute les processus à tour de rôle jusqu’à ce qu’ils soient terminés. Un processus peut créer d’autres processus, nommés processus fils. http://jb.vioix.free.fr 26-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Définitions (2) Un processus est reconnu par le système à l’aide de son PID (Processus Id.). Il appartient à un propriétaire (UID) et à un groupe (GID). Les processus peuvent communiquer entre eux à l’aide de signaux et échanger des données via des tubes (pipe). Au démarage du système, le programme init est lancé, tous les processus descendent (directement ou non) de lui. http://jb.vioix.free.fr 27-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Commandes du shell associées Lorsque l’on exécute une commande dans le shell, le processus shell est mis en veille jusqu’à ce que la commande s’achève. Si on fait suivre le nom de & on crée un nouveau processus qui s’exécute en arrière plan. Lorque l’on utilise un pipe | les deux processus sont exécutés en arrière plan et communiquent via le pipe. http://jb.vioix.free.fr 28-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Commandes du shell associées La commande ps permet d’obtenir la liste des processus. Par défaut, la commande affiche la liste des processus appartenant à l’utilisateur exécutés dans la fenêtre courante. L’option -u<nom> permet d’afficher tous les processus appartenant à l’utilisateur <nom>. On obtient le numéro de PID, le terminal d’exécution, le temps d’exécution et la commande d’exécution. L’option -H permet d’obtenir la hierarchie des processus. Rappel : la commande id permet d’obtenir le nom de l’utilisateur et son groupe sous formes litérales et numériques. http://jb.vioix.free.fr 29-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Indentifiants des processus La fonction int getuid() renvoie le numéro de UID du processus en cours (c.-à-d. du programme en cours). La fonction int getgid renvoie le numéro de GID du processus en cours. Les fonctions int getpid et int getppid renvoient le numéro du processus en cours ainsi que le numéro de processus du processus père. http://jb.vioix.free.fr 30-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Création de processus La fonction int fork() permet de créer un nouveau processus. Ce processus hérite de toutes les variables de son père mais les variables ne sont pas partagées. La fonction fork renvoie le numéro du processus fils dans le processus père et renvoie 0 dans le processus fils. En cas d’échec, le processus fils n’est pas crée et la fonction renvoie -1. http://jb.vioix.free.fr 31-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Création de processus : exemple int main(int argc, char* argv[]){ int n; ... // Processus père ... n=fork(); if ( n ==0 ){ //Processus fils ... }else{ // Suite du processus père ... } .... return 0; } http://jb.vioix.free.fr 32-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Exécution de processus La famille des fonctions exec permettent d’exécuter un programme passé en paramètre dans le processus courant. Lorsque le programme se termine, le processus est détruit. La fonction int execlp(char* nom, char* arg0, ..., char* arg n, ..., NULL) permet d’exécuter le programme nom avec les paramètres arg0 à arg n (par convention, l’argument arg0 doit correspondre au nom du fichier, donc à l’argument nom). Si la fonction reussit, toutes les instructions qui la suivent ne seront pas exécutées. Usuellement, on utilise fork pour créer un processus fils dans lequel un appel à execlp va exécuter une commande. http://jb.vioix.free.fr 33-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Mise en pause de processus Plusieurs fonctions permettent de mettre en pause un processus. La fonction sleep (int n) met en pause le processus pendant n secondes. Un processus père peut attendre la fin de l’execution de l’un de ces fils à l’aide de la fonction int wait(int* status). La fonction renvoie alors le PID du processus qui vient de se terminer. La variable status contient alors un code de retour qui décrit les conditions dans lesquelles le processus s’est terminé. La variable peut être mise à NULL pour simplifier l’usage. La fonction int waitpid(int pid, int* status, int options) permet d’attendre la fin d’un processus précis. http://jb.vioix.free.fr 34-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Communication entre processus (1) Les processus peuvent se communiquer deux types d’informations : des données ou des signaux. Les données peuvent transiter par un fichier temporaire (eventuellement en utilisant les redirections à l’aide de dup2). Une approche plus ”unixienne” consiste à utiliser un pipe. La fonction pipe(int fd[2]) permet de créer un tube de communication. Le tableau passé en paramètre contient alors deux descripteurs de fichier. On lit dans le descripteur fd[0] et on écrit dans le descripteur fd[1]. http://jb.vioix.free.fr 35-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Communication entre processus (2) Les processus peuvent se transmettre des signaux. Les signaux correspondent à des messages simples sans transmission de données. Il existe un nombre fini de signaux, la liste est donnée par la commande kill -l en shell. La plupart des signaux sont définis par le système, toutefois quelques signaux sont disponibles pour les utilisateurs (signaux SIGUSRn). http://jb.vioix.free.fr 36-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Communication entre processus (2) Nom du signal SIGINT SIGFPE SIGKILL SIGSEGV SIGALRM SIGTERM SIGSTOP http://jb.vioix.free.fr Numéro 2 8 9 11 14 15 19 Description Signal d’interruption (comme CTRL+C). Erreur de calcul (par ex. : division par zéro). Demande d’arrêt du processus. Ecriture ou lecture à une adresse illégale. Lié à la fonction alarm() Signal de fin (émis par défaut par la commande kill). CTRL+Z (ne peut pas être intercepté). 37-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Communication entre processus (3) En bash la commande kill permet d’envoyer un signal à un ou des processus précis. Cette commande est souvent utilisée pour terminer (signal SIGKILL) un processus. En C, la fonction int kill(int pid, int sig) permet d’envoyer le signal sig, au processus référencé pid. Un programme peut avoir trois comportements possibles lorsqu’il reçoit un signal : soit il l’ignore, soit il utilise le comportement par défaut, soit il effectue une tâche particulière. http://jb.vioix.free.fr 38-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Communication entre processus (4) Si on souhaite modifier le comportement du programme lors de la réception d’un signal, on utilise la fonction signal(int sig, sig hdl handler) La variable sig correspond au numéro du signal (les équivalences sont reconnues). Les signaux SIGKILL et SIGSTOP ne peuvent pas être redéfinis. La variable handler permet de définir le comportement à adopter : SIG IGN, le signal est ignoré. SIG DFL, le signal retrouve son comportement par défaut. Le nom d’une fonction de prototype void fonction(int ). Cette fonction sera appelée à chaque réception du signal. Elle recevra le numéro du signal en paramètre (une même fonction peut donc traiter des signaux différents). http://jb.vioix.free.fr 39-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Communication entre processus (4) ... void signal_recu(int s){ cout<<"Signal recu : "<<s<<endl; exit(0); } ... int main(){ ... signal(SIGINT, signal_recu); while(1){ } ... } http://jb.vioix.free.fr 40-41 gcc Entrées et sorties Accès aux fichiers Processus Définitions Commandes du shell associées Fonctions C associées Partage de ressources Les sémaphores sont des mécanismes de synchronisation entre les processus. Un sémaphore est une variable entière positive ou nulle. Les deux opérations principales sont nommées P (proberen) et V(verhogen). L’opération P consiste à prendre un jeton. Si la valeur du semaphore est positive, on la décrémente et le processus s’exécute ; sinon, le processus est placé en attente jusqu’à ce que la valeur du semaphore devienne positive. L’opération V rend un jeton. On peut déposer des jetons à l’avance sur un sémaphore. Le système de gestion des sémaphores est très complet (et complexe) sous Unix. Le TP suivant va permettre de mettre en place un système simple de sémaphore. http://jb.vioix.free.fr 41-41