Document Details

AwesomeSerpentine9173

Uploaded by AwesomeSerpentine9173

Maths-Info

Tags

system programming processes operating systems computer science

Summary

This document provides an overview of system programming concepts, focusing on processes.  It details process definition, lifecycles including creation, states, and termination. Topics also include process trees and the related commands used within Unix-like systems.

Full Transcript

Programmation Système Département Maths-Info Programmation Système 1 1 1 Les processus http://manpagesfr.free.fr/consulter.html...

Programmation Système Département Maths-Info Programmation Système 1 1 1 Les processus http://manpagesfr.free.fr/consulter.html Programmation Système 2 2 2 Définition des processus Un processus est l’image en mémoire d’un programme en cours d’exécution, il a un cycle de vie : il naît, il meurt. Un même programme lancé deux fois génère deux processus différents. Un processus peut créer d’autres processus, on les appelle ses « fils ». Un processus est identifié en mémoire par un numéro unique défini par le système d’exploitation appelé le PID (Processus IDentifier). Programmation Système 3 3 3 Arbre des processus d'un système Unix init est le processus parent de tous les autres. Il permet d'attribuer le nom de la machine, d'initialiser la date et l'heure et de contrôler si le système a bien été arrêté. Il lance un processus de surveillance getty qui permet d'afficher le message "login :" sur un écran terminal et d'attendre une réponse. Il lance également un processus de surveillance du réseau : inetd. Les processus dont le nom se termine par "d" sont appelés processus démons. Ce sont des processus serveurs qui tournent en boucle afin d'offrir le même service à plusieurs clients. Programmation Système 4 4 4 Arbre des processus d'un système Unix (suite) Le schéma ci-dessous présente l'arborescence des processus d'un système Unix Arborescence des processus Unix Programmation Système 5 5 5 Les processus : la commande pstree Programmation Système 6 6 6 Les différents états d’un processus Comme nous l’avons énoncé précédemment, un processus a un cycle de vie, ses 4 états principaux sont : ─ Initial : le processus est nouvellement créé et se trouve dans un état de transition, ─ Actif : le processus s’exécute, ─ En attente : le processus est suspendu, il libère alors l’UC pour un autre processus, ─ Final (zombie) : le processus a terminé son exécution. Programmation Système 7 7 7 Concept de processus Le processeur traite une tâche à la fois, s’interrompe et passe à la suivante Le diagramme d’état du processus Prêt processeur alloué admit interruption en sortie terminé exécution Nouveau occurrence d’un événement en attente d’un événement Bloqué Programmation Système 8 8 8 Création d’un processus Les processus des utilisateurs sont lancés par un interprète de commande (shell). Ils peuvent eux même lancer ensuite d’autres processus Ces processus doivent ensuite pouvoir communiquer entre eux Le processus créateur = le père Les processus crées = les fils Les processus peuvent se structurer sous la forme d’une arborescence P1 P2 P3 P4 P5 P6 Programmation Système 9 9 9 Destruction d’un processus 3 possibilités pour l’arrêt d’un processus Normal : par lui même en ayant terminé ses opérations Autorisé : par son père qui exécute une commande appropriée Anormal : par le système temps d’exécution dépassé mémoire demandée non disponible instruction invalide etc. Le processus créateur est le seul à pouvoir exécuter l’arrêt de ses fils Dans plusieurs systèmes, la destruction d’un processus père entraîne la destruction de tous ses fils Programmation Système 10 10 10 Mise en oeuvre Pour mettre en oeuvre le modèle des processus, le système d’exploitation construit une table, appelé table des processus, dont chaque entrée correspond à un processus particulier Chaque entrée comporte des informations sur : l’état du processus son compteur ordinal : contient l’adresse de la prochaine instruction à extraire de la mémoire son pointeur de pile : contient l’adresse courante du sommet de pile en mémoire son allocation mémoire l’état de ses fichiers ouverts et tous ce qui peut être sauvegardé lorsqu’un processus passe de l’état élu à l’état prêt Programmation Système 11 11 11 Structure d’un processus L’environnement d’un processus comprend : un numéro d’identification unique appelé PID (Process IDentifier) le numéro d’identification de l’utilisateur qui a lancé ce processus, appelé UID (User IDentifier), et le numéro du groupe auquel appartient cet utilisateur, appelé GID (Group IDentifier) le répertoire courant les fichiers ouverts par ce processus le masque de création de fichier, appelé umask la taille maximale des fichiers que ce processus peut créer, appelé ulimit la priorité les temps d’exécution le terminal de contrôle, c’est à dire le terminal à partir duquel la commande a été lancée, appelé TTY Programmation Système 12 12 12 Priorités Chaque processus a une priorité d’exécution Sous linux les priorité vont de –20 à 19 Plus la valeur est grande plus la priorité est petite La commande nice affecte la priorité $ nice –5 find / -name *.c Programmation Système 13 13 13 Un exemple : schéma d’un processus Unix UID = 106 répertoire courant GID = 104 PID = 36 /usr/c1 fichiers ouverts 0 /dev/term/c4 2 -> /dev/term/c4 3 /tmp/toto umask = 027 ulimit = 2048 /dev/term/c4 priorité = 19 temps = 0.3 Ce processus a le numéro 36. Il a été lancé par l’utilisateur qui a 106 pour UID. Il est entrain d’exécuter le programme ‘cmd1’. Il a consommé 0.3 seconde, avec une priorité de 19. Son masque de création est 027. Son terminal de contrôle est /dev/term/c4. Son répertoire courant est /usr/c1. Il a 4 fichiers ouverts : 0, 1, 2, et 3. Programmation Système 14 14 14 Structure d’un processus Unix Le PPID est le PID du processus père Le processus fils hérite de tout l’environnement du processus père, sauf bien sûr du PID, du PPID et des temps d’exécution Le père du processus 36 est le processus 27, et celui de 27 est le processus 1 Seul le fils 36 a ouvert le fichier /tmp/toto Père Fils PID = 27 répertoire PID = 36 répertoire UID = 106 PPID = 1 UID = 106 PPID = 27 GID = 104 courant GID = 104 courant /usr/c1 /usr/c1 fichiers ouverts fichiers ouverts signaux 0 /dev/term/c4 2 -> /dev/term/c4 2 -> /dev/term/c4 umask = 027 umask = 027 3 /tmp/toto ulimit = 2048 ulimit = 2048 /dev/term/c4 priorité = 19 /dev/term/c4 priorité = 19 temps = 0.1 temps = 0.3 Programmation Système 15 15 15 Les processus : la commande ps Un processus est un programme qui est en cours d’exécution La commande ps donne un ensemble de renseignements sur les processus en court d’exécution Syntaxe : ps options Options : -a : affiche des renseignement sur tous les processus attachés à un terminal -l : donne, pour chaque processus, le nom de l’utilisateur (user), le pourcentage de cpu (%cpu), la taille totale du processus dans la mémoire (size), la mémoire réservée (rss) en Ko … -x : affiche également des informations sur les processus non liés au terminal -w : affiche sur 132 colonnes, utile pour voir le nom complet de la commande associée à chaque processus Programmation Système 16 16 16 Les processus : la commande ps % ps PID TTY STAT TIME CMD 746 pts/3 S 00:00:00 -bash 749 pts/3 S 00:00:02 gs 848 pts/3 S 00:03:28 mozilla-bin 965 pts/3 S 00:00:00 ps PID : le numéro d’identification du processus TTY : le terminal depuis lequel le processus a été lancé STAT : l’état du processus au moment du lancement de la commande R : le processus est en cours d’exécution T : le processus est stoppé S : le processus dort depuis moins de 20 secondes Z : le processus en attente d’un message du noyau (zombie) TIME : le temps d ’exécution de la commande CMD : le libellé de la commande lancée Programmation Système 17 17 17 Arrêt d’un processus : kill La commande kill permet d’envoyer un signal au processus Syntaxes : kill -signal pid kill -l Options : -9 : demande l’arrêt du processus désigné par son pid -l : affiche la liste des signaux disponibles $ kill -l 1) HUP 2) INT 3) QUIT … 7) EMT 8) FPE 9) KILL … $ kill -9 1635 Cette commande tue le processus dont le numéro PID est 1635 Programmation Système 18 18 18 Les signaux Chaque processus peut recevoir des signanux Chaque signal a une signification particulière Pour envoyer un signal on utilise la commande kill Exemple kill –9 2345 Nom du signal Numéro Description SIGINT 2 touche Ctrl-C, termine le processus SIGKILL 9 arrêter tout programme car il ne peut être géré différemment que le comportement par défaut. L'arrêt du programme est brutal. SIGTERM 15 Arrête le processus, mais permet d’effectuer des opérations avant l’arrêt. SIGCHLD 17 Informe le père de la mort de son fils Programmation Système 19 19 19 Gestion des Jobs Un job est un ensemble de 1 ou plusieurs commande Une commande peut etre aussi un programme à lancer $ ls | wc est un job $ emacs Lancement des jobs en arrière plan avec & $ emacs & jobs : affiche les jobs lancés sur le shell bg : reprendre l’exécution d’un job arrêté; $ bg %n fg : remet en avant plan un job; $ fg %n Avec n le numéro du job Programmation Système 20 20 20 Linux : Gestion des Services La commande service : manipule les services de la machine # service --status-all rpc.mountd (pid 1541) is running... nfsd (pid 1529) is running... 1528 (pid 1527) is running... 1526 (pid 1525) is running... 1524 (pid 1523) is running... 1522 (pid ) is running... lockd (pid 1126) is running... rpc.statd (pid 937) is running... ntpd (pid 1232) is running... numlock is enabled partmon has been startedportmap (pid 839) is running... The random data source exists Sound loaded syslogd (pid 864) is running... http://www.fevrierdorian.com/wiki/Commandes_utiles_(Linux) Programmation Système 21 21 21 Concepts et outils Programmation Système 22 22 22 Généralités sur le développement sous linux La base du système est le noyau=Linux, fonctionnant en arrière-plan pour surveiller les applications des utilisateurs : C’est un ensemble cohérent de routines fournissant des services aux applications, en s’assurant de conserver l’intégrité du système. Pour le développeur, le noyau est surtout une interface entre son application et la machine physique. Le noyau fournit donc des points d’entrée, qu’on nomme « appels- système », et que le programmeur invoque comme des sous-routines offrant des services variés. Il existe une centaine d’appels-systèmes sous Linux. Ils effectuent des tâches très variées, allant de l’allocation mémoire aux entrées-sorties directes sur un périphérique, en passant par la gestion du système de fichiers, le lancement d’applications ou la communication réseau. Il existe une couche supérieure avec des fonctions qui viennent compléter les appels-système. Cette interface est constituée par la bibliothèque C, qui regroupe des fonctionnalités complémentaires de celles qui sont assurées par le noyau Programmation Système 23 23 23 Généralités sur le développement sous linux (suite) – Cette bibliothèque regroupe par exemple toutes les fonctions mathématiques. – La bibliothèque C permet aussi d’encapsuler les appels-systèmes dans des routines de plus haut niveau, qui sont donc plus aisément portables d’une machine à l’autre. Il y a plusieurs bibliothèques C successivement utilisées sous Linux : Les versions 1 à 5 de la libc Linux, A partir de la version 2.0 du noyau Linux, toutes les distributions ont basculé vers une autre version de la bibliothèque, la GlibC, issue du projet GNU. Elle parfois nommée –abusivement- libc 6. La commande ldd permet de connaitre la liste des bibliothèques liées à un fichier exécutable: #ldd libc.so.6 linux-vdso.so.1 (0x00007ffe841f2000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fed3e29b000) /lib64/ld-linux-x86-64.so.2 (0x00005602f6f7c000) la commande #uname –a donne le numéro de noyau Les fonctions de la bibliothèque GlibC et les appels-systèmes représentent un ensemble minimal de fonctionnalités indispensables pour le développement d’application. Ils sont pourtant très limités en termes d’interface utilisateur. Programmation Système 24 24 24 conception du système la conception du système repose sur différents niveaux bien distincts: le noyau, un interpréteur de commandes (le shell), des bibliothèques et un nombre important d’utilitaires. Programmation Système 25 25 25 Architecture logique d’un système UNIX Programmation Système 26 26 26 La démarche de développement Le développement en C sous Linux comme sur la plupart des autres systèmes d’exploitation met en œuvre principalement trois étapes : L’éditeur de texte, Le compilateur, qui permet de passer d’un fichier source à un fichier objet, L’éditeur de liens, qui assure le regroupement des fichiers objet provenant des différents modules et les associe avec les bibliothèques utilisées pour l’application. Nous obtenons ici un fichier exécutable. Programmation Système 27 27 27 Compilateur, éditeur de liens Le compilateur C utilisé sous Linux est le gcc (Gnu Compiler Collection).: L’éditeur de texte, Le compilateur, qui permet de passer d’un fichier source à un fichier objet, L’éditeur de liens, qui assure le regroupement des fichiers objet provenant des différents modules et les associe avec les bibliothèques utilisées pour l’application. Nous obtenons ici un fichier exécutable. Programmation Système 28 28 28 La programmation C Sous le shell on écrit le programme avec un éditeur de texte (vi exemple) et on l’enregistre avec l’extension.c. Exemple teste.c: Compilation du programme cc -o teste teste.c ou cc teste.c -o teste ou gcc -o teste teste.c ou gcc teste.c -o teste Exécution Si la compilation s’est fait sans erreur on exécute :./teste Programmation Système 29 29 29 Exemple Programmation Système 30 30 30 Compilation du programme La compilation du fichier prog.c se déroule en général en deux étapes : 1.traduction du langage C dans le langage de la machine (binaire) sous forme d’un fichier objet prog.o : gcc -O3 -Wall -c prog.c 2.regroupement des variables et des fonctions du programme avec les fonctions fournies par des bibliothèques du système ou faites par l’utilisateur (edition de liens) : gcc -o prog prog.o Ces deux commandes ont donc permis de générer l’exécutable prog qui peut alors est lancé (./prog dans une fenêtre de shell). Si un seul fichier source est utilisé pour générer l’exécutable, les deux commandes précédentes peuvent être regroupées en une seule : gcc -O3 -Wall prog.c -o prog Quelques explications sur les options de gcc s’imposent : -O : effectue des optimisations sur le code généré. La compilation peut alors prendre plus de temps et utiliser plus de mémoire. Un niveau, variant de 0 à 3 spécifie le degré d’optimisation demandé. Ainsi, l’option -O3 permet d’obtenir le code le plus optimisé. -Wall : active des messages d’avertissements (warnings) supplémentaires ; -c : demande au compilateur de ne faire que la première étape de compilation ; -o : permet de préciser le nom du fichier produit. Programmation Système 31 31 31 Compilation du programme il est important de noter que l’option -Wall est indispensable puisqu’elle permet de détecter la plupart des erreurs d’inattention, de mauvaise pratique du langage ou d’usage involontaire de conversion implicite. Il existe bien sûr de nombreuses autres options disponibles. En voici certaines qui pourront être utiles : -Idir : ajoute le répertoire dir dans la liste des répertoires ou se trouvent des fichiers de header.h. Exemple : -Iinclude/ -Werror : considère les messages d’avertissements (warnings) comme des erreurs -g : est nécessaire pour l’utilisation d’un debugger. Il est possible de fournir plus ou moins d’informations au debugger. Ce critère est caractérisé par un niveau variant entre 1 et 3 (2 par défaut). Ainsi, l’utilisation du niveau maximal (option -g3) assure que le maximum d’informations sera fourni au debugger ; -pg : nécessaire pour l’utilisation du profiler GNU : gprof5 (qui permet de déterminer par exemple quelles sont les parties du code qui prennent le plus de temps d’exécution et qui doivent donc être optimisées). Enfin, il est possible d’automatiser la compilation de l’ensemble des fichiers d’un projet à l’aide de l’utilitaire make Programmation Système 32 32 32 La compilation séparée Ce n’est pas le tout de bien fragmenter son code en plusieurs fichiers, encore faut-il les compiler pour obtenir un exécutable. La méthode consiste à générer un fichier objet par module (option -c de gcc) : gcc -O3 -Wall -I. -c module_1.c gcc -O3 -Wall -I. -c module_2.c... gcc -O3 -Wall -I. -c module_n.c Ces commandes générent n fichiers objets module_i.o. L’option -I de gcc permet d’ajouter un répertoire en première position de la liste des répertoires où sont cherchés les fichiers en-tête. (ici, le répertoire courant,./ : l’option -I est donc en fait facultative dans ce cas). Cette option est utile lorsque, pour des raisons de lisibilité dans l’architecture des fichiers sources, un répertoire Include est créé pour contenir tous les fichiers en-tête du programme. La compilation avec gcc comportera alors l’option -IInclude. Une passe d’´edition de lien entre ces fichiers objets en ensuite nécessaire pour générer l’exécutable final toto.exe : gcc -o toto.exe module_1.o module_2.o... module_n.o Programmation Système 33 33 33 Création d’un fichier makefile Programmation Système 34 34 34 Programmation Système 35 35 35 Programmation Système 36 36 36 Programmation Système 37 37 37 Programmation Système 38 38 38 Programmation Système 39 39 39 Les paramètres de "main()" main() est une fonction comme une autre. Elle reçoit donc des paramètres et renvoie une valeur. int main (int argc, char *argv[], char *envp[]); int argc (vient de "argument count") : Nombre d'arguments passés au programme ; y compris le nom du programme lui-même. char *argv[](vient de "argument value") : Pointeur vers un tableau de pointeurs, chacun de ceux-ci pointant vers un tableau de caractères au format chaîne. De plus, argv contiendra le nom du programme lui-même. Enfin, ce tableau contiendra un pointeur supplémentaire de valeur nulle. On remarquera que cette valeur nulle se trouve dans la variable argv[argc]. Programmation Système 40 40 40 Les paramètres de "main()" (suite) main() est une fonction comme une autre. Elle reçoit donc des paramètres et renvoie une valeur. int main (int argc, char *argv[ ], char *envp[ ]); char *envp[ ] : (vient de "environment program"). De même format que "argv", cette variable pointe vers un tableau de chaînes de caractères. Chaque chaîne contiendra la valeur de chaque variable d'environnement qui a été exportée par un des processus ascendants et donc accessible au programme. Cette chaîne est de la forme variable=valeur. De même que pour "argv", un dernier pointeur de ce tableau a pour valeur "zéro". Valeur renvoyée (int) : un entier utilisable par le processus appelant le programme (récupéré par $ ? en shell). Programmation Système 41 41 41 Les paramètres de "main()" (Exemple) int main (int argc, char*argv[], char *envp[]) { printf("Je me nomme %s et on m'a envoyé %d paramètres\n", argv, argc – 1); return(0); } Programmation Système 42 42 42 Programmation Système 43 43 43 La variable PATH La variable PATH contient une série de chemin vers des répertoires qui contiennent des exécutables ou des scripts de commande. Comme toute variable qui se respecte, on peut afficher sa valeur avec la commande : Lorsqu'on lance une commande dans la console, le système va chercher l'exécutable dans les chemins donnés dans le PATH. Pour le PATH donné en exemple, le noyau ira chercher l'exécutable dans /usr/local/sbin, puis dans /usr/local/bin, etc... En conséquence, si deux commandes portent le même nom, c'est la première trouvée qui sera exécutée. Programmation Système 44 44 44 ACCES A L'ENVIRONNEMENT Programmation Système 45 45 45 L'environnement Une application peut être exécutée dans des contextes différents : terminaux, répertoire de travail... C'est pourquoi le programmeur système a souvent besoin d'accéder à l'environnement. Celui-ci est définit sous la forme de variables d'environnement. Les Variables d'environnement sont définies sous la forme suivante : NOM = VALEUR. Lorsqu'un programme en C démarre, ces variables sont automatiquement copiées dans un tableau de char. Vous pouvez y accéder en déclarant la variable externe globale environ au début de votre fichier, comme ceci : La commande suivante permet d’afficher les variables d'environnement : # env 46 Programmation Système 46 46 L'environnement Ce tableau contient des chaînes de caractères se terminant par NULL, et lui-même se termine par un pointeur nul. Un petit schéma peut-être ? Programmation Système 47 47 47 Exemple Programmation Système 48 48 48 Voici un exemple de résultat : Programmation Système 49 49 49 Variables d'environnement classiques Voici une explication des variables d'environnement couramment utilisées : HOME : contient le répertoire personnel de l'utilisateur ; PATH : contient une série de chemin vers des répertoires qui contiennent des exécutables ; PWD : contient le répertoire de travail ; USER (ou LOGNAME) : nom de l'utilisateur ; TERM : type de terminal utilisé ; SHELL : shell de connexion utilisé. Programmation Système 50 50 50 Créer, modifier, rechercher et supprimer une variable ‒ La fonction : int putenv(const char *string); sert à créer une variable d'environnement. Elle prend en argument une chaîne de caractère du type « NOM=VALEUR ». ‒ La fonction : int setenv(const char *name, const char *value, int overwrite); sert à modifier une variable d'environnement. Elle prend en argument le nom de la variable, la valeur à affecter et si on écrase la précédente valeur de la variable (si il y en a une) ou pas (1 pour l'écraser, 0 sinon). ‒ La fonction getenv, ainsi déclarée dans stdlib.h : char *getenv(const char *name); permet de rechercher une variable d'environnement. ‒ Enfin, la fonction : void unsetenv(const char *name); permet de supprimer une variable d'environnement. Programmation Système 51 51 51 Valeur des variables d’environnement La primitive getenv() permet d'obtenir la valeur courante d'une variable d'environnement particulière. #include char* getenv (char *string); Explication des paramètres : char *string : Chaîne contenant la variable dont on veut obtenir la valeur Valeur renvoyée (char*) : un pointeur sur une zone mémoire statique contenant la chaîne correspondant à la variable demandée si celle-ci existe Programmation Système 52 52 52 Valeur des variables d’environnement Exemple : #include main() { char *pt; pt=getenv("PATH"); printf("PATH = %s\n ", pt); } Programmation Système 53 53 53 Exercice Créez un programme permettant de rechercher les variables d'environnement passées en paramètre de la fonction main. Programmation Système 54 54 54 Exemple Programmation Système 55 55 55 Les appels système en C Programmation Système 56 56 56 L’appel système fork de création de processus Programmation Système 57 57 57 Lancement d’un nouveau programme Chaque programme (fichier exécutable ou script shell) en cours d’exécution dans le système correspond à un (parfois plusieurs) processus du système. Chaque processus possède un numéro de processus (PID). Les deux processus pouvant être distingués par leur numéro d’identification PID (Process IDentifier), il est possible d’exécuter deux codes différents au retour de l’appel-système fork(). Par exemple, le processus fils peut demander à être remplacé par le code d’un autre programme exécutable se trouvant sur le disque. C’est exactement ce que fait un shell habituellement. Programmation Système 58 58 58 Présentation des processus Le premier processus du système, init, est créé directement par le noyau au démarrage. La seule manière, ensuite de créer un nouveau processus est d’appeler l’appel-système fork(), qui va dupliquer le processus appelant. Au retour de cet appel-système, deux processus identiques continueront d’exécuter le code à la suite de fork(). La différence essentielle entre ces deux processus est un numéro d’identification PID (Process IDentifier). On distingue ainsi le processus original, qu’on nomme traditionnellement le processus père, et la nouvelle copie, le processus fils. L’appel-système fork() est déclaré dans , ainsi : pid_t fork(void); Il est possible d’exécuter deux codes différents au retour de l’appel- système fork(). Par exemple, le processus fils peut demander à être remplacé par le code d’un autre programme exécutable se trouvant sur le disque. C’est exactement ce que fait un shell habituellement. Programmation Système 59 59 59 Présentation des processus (suite) Pour connaître son propre identifiant PID, on utilise l’appel-système getpid(), qui ne prend pas d’argument et renvoie une valeur de type pid_t. Il s’agit, bien entendu, du PID du processus appelant. Cet appel- système, déclaré dans : pid_t getpid(void); Ce numéro de PID est celui que nous avons vu affiché en première colonne de la commande ps. Sous Linux, le type pid_t est un entier sur 64 bits, mais ce n’est pas le cas pour tous les Unix. Pour assurer une bonne portabilité lors de l’affichage d’un PID, nous utiliserons la conversion %ld de printf(), et nous ferons explicitement une conversion de type en long int ainsi : fprintf(stdout, "Mon PID est : %ld\n", (long) getpid()); Programmation Système 60 60 60 Présentation des processus (suite) La distinction entre processus père et fils peut se faire directement au retour de l’appel fork(). Celui-ci, en effet, renvoie une valeur de type pid_t, qui vaut zéro si on se trouve dans le processus fils, est négative en cas d’erreur, et correspond au PID du fils si on se trouve dans le processus père. Voici en effet un point important : dans la plupart des applications courantes, la création d’un processus fils a pour but de faire dialoguer deux parties indépendante du programme (à l’aide de signaux, de tubes, de mémoire partagée…). Le processus fils peut aisément accéder au PID de son père (noté PPID pour Parent PID) grâce à l’appel-système getppid(), déclaré dans : pid_t getppid(void); Cette routine se comporte comme getpid(), mais renvoie le PID du père du processus appelant. En revanche, le processus père ne peut connaître le numéro du nouveau processus créé qu’au moment du retour du fork(). Programmation Système 61 61 61 Présentation des processus (suite) Lorsqu’un processus est créé par fork(), il dispose d’une copie des données de son père, mais également de l’environnement de celui-ci et d’un certain nombre d’autres éléments (table des descripteurs de fichiers, etc.). On parle alors d’héritage du père. En cas d’erreur, fork() renvoie la valeur –1, et la variable globale errno contient le code d’erreur, défini dans. Ce code d’erreur peut être soit ENOMEM, qui indique que le noyau n’a plus assez de mémoire disponible pour créer un nouveau processus, soit EAGAIN, qui signale que le système n’a plus de place libre dans sa table des processus, mais qu’il y en aura probablement sous peu. Un processus est donc autorisé à réitérer sa demande de duplication lorsqu’il a obtenu un code d’erreur EAGAIN. Voici à présent un exemple de création d’un processus fils par l’appel- système fork(). Programmation Système 62 62 62 Un exemple simple #include #include #include #include int main (void) { pid_t pid_fils; do { pid_fils = fork(); } while ((pid_fils == -1) && (errno == EAGAIN)); if (pid_fils == -1){ fprintf(stderr, "fork() impossible, errno=%d\n", errno); return 1; } if (pid_fils == 0) { fprintf(stdout, "Fils : PID=%ld, PPID=%ld\n", (long)getpid(), (long)getppid()); return 0; } else { fprintf(stdout, "Pere : PID=%ld, PPID=%ld, PID fils=%ld\n", (long)getpid(), (long)getppid(), (long)pid_fils); return 0; } } Programmation Système 63 63 63 fork() Exemple (prog.c) #include #include #include int main() { pid_t pid; int i; pid = fork(); if( pid > 0 ) { for( i=0; i < 1000; i++ ) printf(“\t\t\tPARENT %d\n”, i); } Programmation Système 64 64 64 else { for( i=0; I < 1000; i++ ) printf( “CHILD %d\n”, i ); } return 0; } Programmation Système 65 65 65 Sortie possible CHILD 0 CHILD 1 CHILD 2 PARENT 0 PARENT 1 PARENT 2 PARENT 3 CHILD 3 CHILD 4 PARENT 4 : Programmation Système 66 66 66 Attendre un processus Le shell effectue un fork pour exécuter une commande, puis attend la fin de ce processus. En C, la fonction wait() attend et retourne la valeur passée à exit() par le fils. pid_t wait(int *); // attend la fin de n'importe quel fils // int * : valeur de retour passée à exit par le fils pid_t waitpid(pid_t, int *, int); // attend la fin d'un processus donné // le 3e paramètre permet de passer des flags wait est bloquante mais retourne immédiatement si le fils a terminé son exécution ou est devenu zombie. waitpid ne bloque pas avec l'option WNOHANG Remarque : Le processus père tourne sans arrêt. Quand il lance un fils, il récupère un int status. Programmation Système 67 67 67 On crée deux processus séquentiels chargés pour le premier d'exécuter la commande pwd et pour le second la commande ls -li Programmation Système 68 68 68 Le répertoire /proc Parmi les différents répertoires qui constitues l'arborescence d'un système Linux typique, l'un d'entre eux permet d'obtenir des informations sur le système. Ce répertoire /proc contient des fichiers comportant des informations classées par type. C'est un pseudo système de fichiers généré de manière dynamique au démarrage. Les tailles de ses fichiers sont à 0 généralement, car ils ne consomme pas d'espace disque mais seulement de la mémoire. ce sont des liens symboliques spéciaux. Un sous-répertoire nommé d'après le numéro ID du processus est créé chaque fois qu'un programme est lancé. Ce répertoire contient toutes les informations utiles sur le processus en question. $ man 5 proc Programmation Système 69 69 69 Le répertoire /proc Un exemple de fichiers d’un sous répertoire de /proc : stat contient des statistiques et des informations sur le statut du processus. Ce sont les mêmes données que celles présentées dans le fichier status, mais au format numérique brut, sur une seule ligne. Ce format est difficile à lire mais plus adapté au traitement automatique par des programmes. Si vous voulez utiliser le fichier stat dans vos programmes, consultez la page de manuel de proc qui décrit son contenu en invoquant man 5 proc. status contient des informations statistiques et statut sur le processus formatée de façon à être lisibles par un humain. Programmation Système 70 70 70 Les zombies Un processus qui meurt devient un zombie jusqu'au wait. – Si le père meurt avant son fils, il est adopté par un autre processus (celui de PID 1 en général). – Si le père n'attend pas la mort de son fils, alors son fils reste un zombie. Sur certains systèmes d'exploitation, les zombies ne peuvent pas être supprimés, il faut donc y faire attention. Si un père a de nombreux fils, il vaut mieux utiliser waitpid() qui permet d'attendre un fils en particulier. Programmation Système 71 71 71 Rendre les fils autonomes Une méthode pour dissocier père et fils : – Le processus original fait un fork() – Le fils fait un fork() à son tour – Le fils meurt avec exit() Le processus original et le petit fils sont alors indépendants : – Pas besoin d'attendre la mort du petit fils – Pas besoin de s'inquiéter quand cela arrive. Programmation Système 72 72 72 Exécution des programmes Programmation Système 73 73 73 La fonction system() Programmation Système 74 74 74 Les primitives system() La fonction system de la bibliothèque standard propose une manière simple d'exécuter une commande depuis un programme, comme si la commande avait été tapée dans un shell. En fait, system crée un sous-processus dans lequel s'exécute le shell Bourne standard (/bin/sh) et passe la commande à ce shell pour qu'il l'exécute. int main () { int return_value ; return_value = system ("ls -l /"); return return_value ; } Comme la fonction system utilise un shell pour invoquer votre commande elle est soumises aux fonctionnalités, limitations et failles de sécurité du shell système. Vous ne pouvez pas vous reposer sur la disponibilité d'une version spécifique du shell Bourne. Sur beaucoup de systèmes UNIX, /bin/sh est en fait un lien symbolique vers un autre shell. Par exemple, sur la plupart des systèmes GNU/Linux, /bin/sh pointe vers bash (le Bourne-Again SHell) et des distributions différentes de GNU/Linux utilisent différentes version de bash. Invoquer un programme avec les privilèges root via la fonction system peut donner des résultats différents selon le système GNU/Linux. Il est donc préférable d'utiliser la méthode de fork et exec pour créer des processus. Programmation Système 75 75 75 L’appel système exec Appel avec listes (execl, execlp, execle) Appel avec tableaux (execv, execvp, execve) Programmation Système 76 76 76 Créer un nouveau processus fork, wait et exit ne permettent pas d'exécuter de nouveaux programmes. Les appels système exec* permettent de remplacer l'image courante du processus par un autre programme. exec* ne retourne jamais. Il en existe plusieurs variantes (execlp, execve...) pour lesquelles il faut combiner les lettres suivantes : v : si des tableaux sont passés en argument l : si des listes sont passées p : si seul le nom de fichier est donné e : si l'env est passé en argument Programmation Système 77 77 77 Les primitives exec() Il s’agit d’une famille de primitives permettant le lancement de l’exécution d’un programme externe. Il n’y a pas création d’un nouveau processus, mais simplement changement de programme. Il y a six primitives exec() que l’on peut répartir dans deux groupes : les execl(), pour lesquels le nombre des arguments du programme lancé est connu, puis les execv() où il ne l’est pas. En outre toutes ces primitives se distinguent par le type et le nombre de paramètres passés. Premier groupe d’exec(). Les arguments sont passés sous forme de liste : int execl(char *path, char *arg0, char *arg1,..., char *argn,NULL) int execle(char *path,char *arg0,char *arg1,...,char *argn,NULL,char *envp[]) int execlp(char *file, char *arg0, char *arg1,...,char *argn,NULL) Les tableaux argv[ ] et envp[ ] doivent se terminer par des pointeurs NULL. Programmation Système 78 78 78 Les primitives execl(),execle() et execlp() int execl(char *path, char *arg0, char *arg1,..., char *argn, NULL) int execle(char *path,char *arg0,char *arg1,...,char *argn,NULL,char *envp[]) int execlp(char *file, char *arg0, char *arg1,...,char *argn, NULL) Dans execl et execle, path est une chaîne indiquant le chemin exact d’un programme. Un exemple est "/bin/ls". Dans execlp, le « p » correspond à path et signifie que les chemins de recherche de l’environnement sont utilisés. Par conséquent, il n’est plus nécessaire d’indiquer le chemin complet. Le premier paramètre de execlp pourra par exemple être "ls". Le second paramètre des trois fonctions exec est le nom de la commande lancée et reprend donc une partie du premier paramètre. Si le premier paramètre est "/bin/ls", le second doit être "ls". Pour la troisième commande, le second paramètre est en général identique au premier si aucun chemin explicite n’a été donné. Exemple : execlp("ls", "ls", "*.c","/ ", NULL) ; Programmation Système 79 79 79 Afficher la date avec le fils L'exécution d'une commande telle que "date" sous Unix agit de cette manière : ✓ le shell est dupliqué, ✓ la copie du shell père est transformé en la commande date, ✓ date termine et le shell principal reprend la main. Programmation Système 80 80 80 Afficher la date avec le fils #include #include #include #include int main (void) { pid_t pid_fils; do { pid_fils = fork(); } while ((pid_fils == -1) && (errno == EAGAIN)); if (pid_fils == -1){ fprintf(stderr, "fork() impossible, errno=%d\n", errno); return 1; } if (pid_fils == 0) { fprintf(stdout, "Fils : PID=%ld, PPID=%ld\n", (long)getpid(), (long)getppid()); execl("/bin/date","date",(char *)0); return 0; } else { fprintf(stdout, "Pere : PID=%ld, PPID=%ld, PID fils=%ld\n", (long)getpid(), (long)getppid(), (long)pid_fils); return 0; } } Programmation Système 81 81 81 Les primitives execv(),execve() et execvp() Second groupe d’exec(). Les arguments sont passés sous forme de tableau : int execv(char *path,char *argv[]) int execve(char *path,char *argv[],char *envp[]) int execvp(char *file,char *argv[]) path et file ont la même signification que dans le premier groupe de commandes. Les autres paramètres du premier groupe de commandes sont regroupés dans des tableaux dans le second groupe. La dernière case du tableau doit être un pointeur nul, car cela permet d’identifier le nombre d’éléments utiles dans le tableau. Pour execle et execve, le dernier paramètre correspond à un tableau de chaînes de caractères, chacune correspondant à une variable d’environnement. On trouvera plus de détails dans le manuel en ligne. Programmation Système 82 82 82 Recap de la syntaxe de execve() int execve(char *path, char *argv[], char *envp[]); // path : chemin vers exécutable // argv : arguments, se terminant par NULL // envp : variables système (forme var=val) // exemple : char *argv[] = {"mv","prog.c","prog.d",NULL}; char *envp[] = {"PATH=/bin",NULL}; execve("/bin/mv",argv,envp); Programmation Système 83 83 83 Exemple execvp() Programmation Système 84 84 84 menu.c #include #include void main() { char *cmd[] = {“who”, “ls”, “date”}; int i; printf(“0=who 1=ls 2=date : “); scanf(“%d”, &i); execlp( cmd[i], cmd[i], (char *)0 ); printf( “execlp failed\n” ); } Programmation Système 85 85 85 Execution menu cmd[i] execlp() printf() n’est pas exécutée que s’il y a un problème avec execlp() Programmation Système 86 86 86 fork() et execv() execv(new_program, argv[ ]) Processus initiale fork() fork() returns pid=0 et produit un clone du Retourne un programme parent jusqu’à nouveau PID ce que execv est exécuté Processus originale Nouvelle Nouveau programme Continue copie du (replacement) père execv(nouveau programme) Programmation Système 87 87 87 wait() #include #include pid_t wait(int *statloc); Suspend le processus appelant jusqu'à ce que l'enfant ait fini. Retourne l'identifiant du processus fils terminé si ok, -1 on error. statloc peut être (int *) 0 ou une variable qui sera lié aux informations de statut. à propos de l'enfant. Programmation Système 88 88 88 wait() Actions Un processus qui appelle wait () peut : – suspendre (bloc) si tous ses enfants sont encore en cours d'exécution, ou – Retourner immédiatement avec le statut final d'un enfant, ou – Retourner immédiatement avec une erreur si il n'y a pas de processus enfants. Programmation Système 89 89 89 menushell.c #include #include #include #include void main() { char *cmd[] = {“who”, “ls”, “date”}; int i; while( 1 ) { printf( “0=who 1=ls 2=date : “ ); scanf( “%d”, &i ); Programmation Système continued90 90 90 if(fork() == 0) { execlp( cmd[i], cmd[i], (char *)0 ); printf( “execlp failed\n” ); exit(1); } else { wait( (int *)0 ); printf( “child finished\n” ); } } } Programmation Système 91 91 91 Execution menushell fork() child cmd[i] wait() execlp() Programmation Système 92 92 92 MANIPULATION DE FICHIERS Programmation Système 93 93 93 Fichiers bruts C'est la méthode la plus efficace et rapide pour stocker et récupérer des données sur fichier (mais aussi la moins pratique). On accède au fichier par lecture ou écriture de blocs (groupe d'octets de taille définie par le programmeur). C'est au programmeur de préparer et gérer ses blocs. On choisira en général une taille de bloc constante pour tout le fichier, et correspondant à la taille d'un enregistrement physique (secteur, cluster...). On traite les fichiers par l'intermédiaire de fonctions, prototypées dans stdio.h (ouverture et fermeture) et dans unistd.h. La première opération à effectuer est d'ouvrir le fichier. Ceci consiste à définir le nom du fichier (comment il s'appelle sous le système) et comment on veut l'utiliser. On appelle pour cela la fonction : int open(char *nomfic, int mode); Programmation Système 94 94 94 Descripteurs de fichiers Un descripteur est un entier compris entre 0 et la valeur de la constante OPEN_MAX qui est définie dans (1024 octet sous Linux). Les descripteurs 0, 1 et 2 sont réservés respectivement pour l’entrée et la sortie standard, ainsi que pour la sortie d’erreur. On peut toutefois remplacer ces valeurs à profit par les constantes symboliques : STDIN_FILENO, STDOUT_FILENO et STDERR_FILENO qui sont définies dans. Il existe pour cela deux appels-système, open() et creat(), le premier est un prototype avec un argument optionnel : int open (const char * nom_fichier, int attributs,... ); int creat (const char * nom_fichier, mode_t mode); Programmation Système 95 95 95 Descripteurs de fichiers SYNOPSIS #include #include #include int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); int creat(const char *pathname, mode_t mode); Programmation Système 96 96 96 Descripteurs de fichiers int open (const char * nom_fichier, int attributs,... ); La fonction open() prend en premier argument le nom d’un fichier à ouvrir. Le second argument est une combinaison de plusieurs éléments assemblés par un OU binaire c.à.d. :( | ), permet de définir comment on utilisera le fichier. Tout d’abord, il faut impérativement utiliser l’une des trois constantes suivantes : O_RDONLY : fichier ouvert en lecture seule ; O_WRONLY : fichier ouvert en écriture seule ; O_RDWR : fichier ouvert à la fois en lecture et en écriture. Exemple : open (nom_fichier, O_CREAT | O_WRONLY | O_TRUNC, mode); Programmation Système 97 97 97 Descripteurs de fichiers O_APPEND : positionnement en fin de fichier ; O_CREAT : créer le fichier s’il n’existe pas ; O_TRUNC : vide le fichier s’il existait ; O_EXCL : renvoie une erreur si le fichier existe. Ces constantes sont définies dans fcntl.h (fcntl c.à.d. file control) En cas de création de fichier, un troisième paramètre mode_t mode indique les permissions si un fichier est créé, facultatif si le fichier n’est pas créer. La liste des modes disponibles étant longue, je vous invite à consulter la page de manuel de open. Exemple : open (nom_fichier, O_CREAT | O_WRONLY | O_TRUNC, mode); Le 3° argument (mode) : prend les valeurs S_IREAD | S_IWRITE | S_EXEC declarés dans stat.h. Programmation Système 98 98 98 Quelques valeurs pour le mode Les constantes symboliques ci-dessous donne un exemple de valeurs pour le mode des fichiers : S_IRWXU 00700 user (file owner) has read, write and execute permission S_IRUSR 00400 user has read permission S_IWUSR 00200 user has write permission S_IXUSR 00100 user has execute permission S_IRWXG 00070 group has read, write and execute permission S_IRGRP 00040 group has read permission S_IWGRP 00020 group has write permission S_IXGRP 00010 group has execute permission S_IRWXO 00007 others have read, write and execute permission S_IROTH 00004 others have read permission S_IWOTH 00002 others have write permission S_IXOTH 00001 others have execute permission Programmation Système 99 99 99 Pour les fonctions que nous avons étudiées, il faut inclure les fichiers suivants : Il est donc conseillé d’inclure systématiquement ces quatre fichiers en début de programme pour pouvoir utiliser les descripteurs avec le maximum de portabilité. Programmation Système 100 100 100 Exemple #include #include #include #include #include int main(void) { int fd; // Notre file descriptor if (-1 == (fd = open("alphabet.txt", O_WRONLY | O_CREAT, S_IRWXU))) // Création du fichier et test { perror("open() "); exit(EXIT_FAILURE); } return EXIT_SUCCESS; } Programmation Système 101 101 101 La structure "stat" La manipulation "externe" des fichiers (sans préoccupation du contenu) se fait en utilisant son inode. La taille de cet inode est de 64 octets. Il contient divers renseignements sur le fichier en lui-même tels que ses droits d’accès, ses dates de modification, sa taille et ses adresses de stockage sur le disque. La structure stat définie dans sys/stat.h permet d'accéder à ces informations. Programmation Système 102 102 102 La structure stat struct stat { dev_t st_dev; ino_t st_ino; ushort st_mode; short st_nlink; ushort st_uid; ushort st_gid; dev_t st_rdev; off_t st_size; time_t st_atime; time_t st_mtime; time_t st_ctime; }; Programmation Système 103 103 103 La structure stat : ❑ umode_t st_mode : droits associés au fichier : - S_ISLNK (st_mode) : lien symbolique. - S_ISREG (st_mode) : un fichier régulier. - S_ISDIR (st_mode) : un répertoire. - S_ISCHR (st_mode) : un péripherique en mode caractère. - S_ISBLK (st_mode) : un périphérique en mode blocs. - S_ISFIFO (st_mode) : une FIFO. - S_ISSOCK (st_mode) : une socket. ❑ nlink_t st_nlink : nombre de liens matériels sur le fichier. ❑ uid_t st_uid : numéro du propriétaire (UID). ❑ gid_t st_gid : numéro de groupe du propriétaire (GID). ❑ off_t st_size : taille totale en octets. ❑ time_t st_atime : date du dernier accès. ❑ time_t st_mtime : date de la dernière modification. ❑ time_t st_ctime : date du dernier changement. Programmation Système 104 104 104 Exemple 2 #include #include #include #include #include #include #include int main (int argc, char * argv[]) { int st; struct stat infos; char * date; st= stat(argv, &infos); if (st< 0) return (-1); if (S_ISLNK (infos.st_mode))printf(" lien symbolique\n"); else if (S_ISREG (infos.st_mode))printf(" fichier régulier."); else if (S_ISDIR (infos.st_mode))printf(" répertoire."); else if (S_ISCHR (infos.st_mode))printf(" péripherique caractère."); else if (S_ISBLK (infos.st_mode))printf(" périphérique blocs."); else if (S_ISFIFO(infos.st_mode))printf("e FIFO."); else if (S_ISSOCK(infos.st_mode))printf("e Socket\n"); printf ("liens = %d\n", (int)infos.st_nlink); printf ("UID = %d\n", (int)infos.st_uid); printf ("GID = %d\n", (int)infos.st_gid); printf ("Taille= %d\n", (int)infos.st_size); printf ("Accès = %s\n", (char *)ctime (& infos.st_atime)); printf ("Modif = %s\n", (char *)ctime (& infos.st_mtime)); printf ("Changé= %s\n", (char *)ctime (& infos.st_ctime)); return (0); } Programmation Système 105 105 105 Exemple 1 #include #include #include #include int main (int nb_args, char * args []) { struct stat sts; if (nb_args != 2) { fprintf (stderr, "syntaxe : %s \n", args ); exit (1); } if ( stat (args , & sts) != 0) { printf (" erreur \n"); exit (1); } fprintf (stdout,"Périphérique : %d\n",sts.st_dev); fprintf (stdout,"Noeud : %ld\n",sts.st_ino); fprintf (stdout,"Protection : %o\n",sts.st_mode); fprintf (stdout,"nb liens matériels: %d\n",sts.st_nlink); fprintf (stdout,"ID propriétaire : %d\n",sts.st_uid); fprintf (stdout,"ID groupe: %d\n",sts. st_gid); fprintf (stdout,"Taille : %lu octets\n",sts.st_size); fprintf (stdout,"Taille de bloc : %lu\n",sts.st_blksize); fprintf (stdout,"Nombre de blocs : %lu\n",sts.st_blocks); } Programmation Système 106 106 106 La différence entre st_ctime et st_mtime st_mtime : est mis à jour lorsque le contenu du fichier change. st_ctime : est mis à jour quand les métadonnées du fichier (propriétaire, groupe, permissions, nombre de liens, etc.) changements. Programmation Système 107 107 107 Ecriture dans un fichier brute via l’appel système write() Programmation Système 108 108 108 write() La fonction open( ), rend un entier positif dont on se servira par la suite pour accéder au fichier, ou -1 en cas d'erreur. Dans ce cas, le type d'erreur est donné dans la variable errno, détaillée dans errno.h. On peut ensuite, suivant le mode d'ouverture, soit lire soit écrire un bloc (l'opération est alors directement effectuée sur disque) : ssize_t write (int fd, const void * buf, size_t count); Rapide petite description : - int fd : Notre File Descriptor, la valeur renvoyée par open - const void * buf : Les données à écrire. - size_t count : La taille des données à écrire (en octets). write() renvoie le nombre d'octets écrits ou -1 si il échoue. Programmation Système 109 109 109 Exercice Ecrire un programme en c pour créer un fichier texte qui contient les 26 lettres de l'alphabet ! Programmation Système 110 110 110 #include #include #include Correction #include #include int main (int argc, char * argv []) { int fd; // Notre file descriptor if (argc != 2) { fprintf (stderr, "syntaxe : %s \n", argv ); exit (1); } if (-1 == (fd = open(argv, O_WRONLY | O_CREAT, S_IRWXU))) // Création du fichier et test { perror("open() "); exit(EXIT_FAILURE); } char c = 'a'; // Octet à écrire int i; // compteur for (i = 0 ; i < 26 ; i++) { write(fd, &c, sizeof(char)); // Ecriture, Il faut bien passer l'adresse de c, car write() attend un pointeur ! c++; // Incrément } return EXIT_SUCCESS; } Programmation Système 111 111 111 read() ssize_t read (int fd, void * buf, size_t count); lit dans le fichier désigné par son descripteur fd, et le met dans le buf dont on donne l'adresse et la taille. La fonction retourne le nombre d'octets lus, 0 si on était déjà sur la fin du fichier, -1 si erreur. int eof(int fd) dit si on se trouve (1) ou non (0) sur la fin du fichier. Lorsque l'on ne se sert plus du fichier, il faut le fermer (obligatoire pour que le fichier soit utilisable par le système d'exploitation, entre autre mise à jour de sa taille : int close(int fd) fermeture, rend 0 si ok, -1 si erreur. Programmation Système 112 112 112 Exercice Ecrire un programme en c pour lire un fichier texte ! Programmation Système 113 113 113 Correction #include #include #include #include #define taillebloc 1024 int main(int argc,char *argv[]) { int df; char buffer[taillebloc]; int nb_lus, nb_ecrits; if (argc!=2) {puts("erreur arguments");return(1);} if((df=open(argv,O_RDONLY))0) nb_ecrits= write(1,(char*)buffer, nb_lus); } while ((nb_lus==taillebloc)&&(nb_ecrits>0)); close(df); return(0); } Programmation Système 115 115 115 Exercice Ecrire un programme en c pour copier un fichier source dans un fichier destination ! Programmation Système 116 116 116 Exemple #include #include #include #include #define taillebloc 1024 int main(int argc,char *argv[]) { int source, destination; char buffer[taillebloc]; int nb_lus,nb_ecrits; if (argc!=3) {puts("erreur arguments");return(1);} if((source=open(argv,O_RDONLY))0)); close(source); close(destination); return(0); } Programmation Système 118 118 118 Déplacement dans le fichier Le fichier peut être utilisé séquentiellement (le "pointeur de fichier" est toujours placé derrière le bloc que l'on vient de traiter, pour pouvoir traiter le suivant). Pour déplacer le pointeur de fichier en n'importe que autre endroit, on appelle la fonction : long lseek(int df, long combien, int code); déplace le pointeur de fichier de combien octets, à partir de : début du fichier si code=0, position actuelle si 1, fin du fichier si 2. La fonction retourne la position atteinte (en nb d'octets), -1 si erreur. long filelength(int df); rend la taille d'un fichier (sans déplacer le pointeur de fichier). Programmation Système 119 119 119 Fichiers bufférisés Les opérations d'entrée / sortie sur ces fichiers se font par l'intermédiaire d'un "buffer" (bloc en mémoire) géré automatiquement. Ceci signifie qu'une instruction d'écriture n'impliquera pas une écriture physique sur le disque mais dans le buffer, avec écriture sur disque uniquement quand le buffer est plein. Les fichiers sont identifiés non par un entier mais par un pointeur sur une structure FILE (définie par un typedef dans stdio.h). Les fonctions disponibles (prototypées dans stdio.h) sont : FILE *fopen(char *nomfic, char *mode) Cette fonction attend 2 paramètres : * Le nom du fichier à ouvrir. * Le mode d'ouverture du fichier. Cette fonction renvoie un pointeur sur FILE Programmation Système 120 120 120 Les modes d'ouvertures possibles - "r" : lecture seule. Vous pourrez lire le contenu du fichier, mais pas écrire dedans. Le fichier doit avoir été créé au préalable. - "w" : écriture seule. Vous pourrez écrire dans le fichier, mais pas lire son contenu. Si le fichier n'existe pas, il sera créé. - "a" : mode d'ajout. Vous écrirez dans le fichier, en partant de la fin du fichier. Vous rajouterez donc du texte à la fin du fichier. Si le fichier n'existe pas, il sera créé. - "r+" : lecture et écriture. Vous pourrez lire et écrire dans le fichier. Le fichier doit avoir été créé au préalable. - "w+" : lecture et écriture, avec suppression du contenu au préalable. Le fichier est donc d'abord vidé de son contenu, et vous écrivez et lisez ensuite dedans. Si le fichier n'existe pas, il sera créé. - "a+" : ajout en lecture / écriture à la fin. Vous écrivez et lisez du texte à partir de la fin du fichier. Si le fichier n'existe pas, il sera créé. Programmation Système 121 121 121 fopen() #include #include int main(int argc, char *argv[]) { FILE* fichier = NULL; fichier = fopen("test.txt", "r+"); return 0; } Le pointeur est initialisé à NULL dès le début. Je vous rappelle que c'est une règle fondamentale que d'initialiser ses pointeurs à NULL dès le début. Programmation Système 122 122 122 lecture / écriture Programmation Système 123 123 123 Ecrire dans le fichier Il existe plusieurs fonctions capables d'écrire dans un fichier. Ce sera à vous de choisir celle qui est la plus adaptée à votre cas. Voici les 3 fonctions que nous allons étudier : - fputc : écrit un caractère dans le fichier (UN SEUL caractère à la fois). - fputs : écrit une chaîne dans le fichier - fprintf : écrit une chaîne "formatée" dans le fichier, fonctionnement quasi-identique à printf Programmation Système 124 124 124 fputc() int main(int argc, char *argv[]) { FILE* fichier = NULL; fichier = fopen("test.txt", "w"); if (fichier != NULL) { fputc('A', fichier); // Ecriture du caractère A fclose(fichier); } return 0; } Programmation Système 125 125 125 fputs() int main(int argc, char *argv[]) { FILE* fichier = NULL; fichier = fopen("test.txt", "w"); if (fichier != NULL) { fputs("Salut\nComment allez-vous ?", fichier); fclose(fichier); } return 0; } Programmation Système 126 126 126 fprintf() int main(int argc, char *argv[]) { FILE* fichier = NULL; int age = 0; fichier = fopen("test.txt", "w"); if (fichier != NULL) { // On demande l'âge printf("Quel age avez-vous ? "); scanf("%d", &age); // On l'écrit dans le fichier fprintf(fichier, "Le Monsieur qui utilise le programme, il a %d ans\n", age); fclose(fichier); } return 0; } Programmation Système 127 127 127 Lire dans un fichier Nous pouvons utiliser quasiment les mêmes fonctions que pour l'écriture, le nom change juste un petit peu : - fgetc : lit un caractère - fgets : lit une chaîne - fscanf : lit une chaîne formatée Programmation Système 128 128 128 fgetc() int main(int argc, char *argv[]) { FILE* fichier = NULL; int caractereActuel = 0; fichier = fopen("test.txt", "r"); if (fichier != NULL) { caractereActuel = fgetc(fichier); // On initialise caractereActuel // Boucle de lecture des caractères un à un while (caractereActuel != EOF) // On continue tant que fgetc n'a pas retourné EOF { { printf("%c", caractereActuel); // On affiche le caractère stocké dans caractereActuel caractereActuel = fgetc(fichier); // On lit le caractère suivant } fclose(fichier); } return 0; } Programmation Système 129 129 129 fgets() #define TAILLE_MAX 1000 // Tableau de taille 1000 int main(int argc, char *argv[]) { FILE* fichier = NULL; char chaine[TAILLE_MAX] = ""; // Chaîne vide de taille TAILLE_MAX fichier = fopen("test.txt", "r"); if (fichier != NULL) { fgets(chaine, TAILLE_MAX, fichier); // On lit maximum TAILLE_MAX caractères du fichier, on stocke le tout dans "chaine" printf("%s", chaine); // On affiche la chaîne fclose(fichier); } return 0; } Programmation Système 130 130 130 fgets() #define TAILLE_MAX 1000 int main(int argc, char *argv[]) { FILE* fichier = NULL; char chaine[TAILLE_MAX] = ""; fichier = fopen("test.txt", "r"); if (fichier != NULL) { while (fgets(chaine, TAILLE_MAX, fichier) != NULL) // On lit le fichier tant qu'on ne reçoit pas d'erreur (NULL) { printf("%s", chaine); // On affiche la chaîne qu'on vient de lire } fclose(fichier); } return 0; } Programmation Système 131 131 131 fscanf() int main(int argc, char *argv[]) { FILE* fichier = NULL; int score = {0}; // Tableau des 3 meilleurs scores fichier = fopen("test.txt", "r"); if (fichier != NULL) { fscanf(fichier, "%d %d %d", &score, &score, &score); printf("Les meilleurs scores sont : %d, %d et %d", score, score, score); fclose(fichier); } return 0; }Programmation Système 132 132 132 Se déplacer dans un fichier Il existe 3 fonctions à connaître : - ftell : indique à quelle position vous êtes actuellement dans le fichier - fseek : positionne le curseur à un endroit précis - rewind : remet le curseur au début du fichier (c'est équivalent à demander à la fonction fseek de positionner le curseur au début). Programmation Système 133 133 133 ftell Cette fonction est très simple à utiliser. Elle renvoie la position actuelle du curseur sous la forme d'un long : long ftell(FILE* pointeurSurFichier); Le nombre renvoyé indique donc la position du curseur dans le fichier. Programmation Système 134 134 134 Le prototype de fseek est le suivant : fseek int fseek(FILE* pointeurSurFichier, long deplacement, int origine); La fonction fseek permet de déplacer le "curseur" d'un certain nombre de caractères (indiqué par déplacement) à partir de la position indiquée par origine. Le nombre déplacement peut être un nombre positif (pour se déplacer en avant), nul (= 0) ou négatif (pour se déplacer en arrière). Quant au nombre origine, vous pouvez mettre comme valeur l'une des 3 constantes (généralement des defines) listées ci- dessous : o SEEK_SET : indique le début du fichier. o SEEK_CUR : indique la position actuelle du curseur. o SEEK_END : indique la fin du fichier. Programmation Système 135 135 135 fseek # Le code suivant place le curseur 2 caractères après le début : fseek(fichier, 2, SEEK_SET); # Le code suivant place le curseur 4 caractères avant la position courante : fseek(fichier, -4, SEEK_CUR); (remarquez que déplacement est négatif car on se déplace en arrière) # Le code suivant place le curseur à la fin du fichier : fseek(fichier, 0, SEEK_END); Programmation Système 136 136 136 rewind Cette fonction est équivalente à utiliser fseek pour nous renvoyer à la position 0 dans le fichier. Si vous avez eu un magnétoscope un jour dans votre vie, eh bien c'est le même nom que la touche qui permet de revenir en arrière. Le prototype est : void rewind(FILE* pointeurSurFichier); Programmation Système 137 137 137 Renommer et supprimer un fichier Nous terminerons ce chapitre en douceur par l'étude de 2 fonctions très simples : - rename : renomme un fichier - remove : supprime un fichier La particularité de ces fonctions est qu'elles ne nécessitent pas de pointeur de fichier pour fonctionner. Il suffira juste d'indiquer le nom du fichier à renommer / supprimer. Programmation Système 138 138 138 rename int rename(const char* ancienNom, const char* nouveauNom); Exemple int main(int argc, char *argv[]) { rename("test.txt", "test_renomme.txt"); return 0; } Programmation Système 139 139 139 remove int remove(const char* fichierASupprimer); Exemple int main(int argc, char *argv[]) { remove("test.txt"); return 0; } Programmation Système 140 140 140 Le standard POSIX Au début des années 1990, Unix s’était morcelé en un certain nombre de systèmes semblables mais incompatibles. L’écriture d’un programme portable entre les différents dialectes d’Unix était devenue un exercice inutilement excitant. Le standard IEEE 1003, communément appelé POSIX (Portable Operating System Interface) vise à créer un ensemble commun de fonctions disponibles sur tous les systèmes Unix. Programmation Système 141 141 141 Fonctions utiles Fonctions norme POSIX open, read, write, stat, pipe, mkfifo, dup, dup2, fcntl,... descripteur = int Fonctions de la librairie C (surcouche) fopen, fread, fwrite, opendir, readdir,... descripteur = FILE * Faire un : man syscalls pour lister les 150 appels disponibles Programmation Système 142 142 142 LES TUBES DE COMMUNICATION Les tubes sont un mécanisme de communication qui permet de réaliser des communications entre processus sous forme d’un flot continu d’octets. Programmation Système 143 143 143 Préambule Les pipes (tubes) sont bien connus dans le monde d'Unix. En effet, ils permettent de faire communiquer deux processus entre eux. Ils sont représentés par le caractère "|". On les utilisent couramment dans les terminaux pour rediriger la sortie d'une commande vers l'entrée d'une autre commande, par exemple : ls | wc Ce qui est moins courant c'est de les utiliser dans un programme en langage C. C'est cela que nous allons traiter dans cette partie. Programmation Système 144 144 144 Généralités Un tube est une zone mémoire limitée (en général de 4Ko) permettant à deux processus de faire transiter des données de l'un vers l'autre. Son mode de fonctionnement est FIFO (First-In, First- Out). C'est à dire que le premier élément à entrer dans le tube sera le premier à en sortir. Le principe est qu'un processus ira mettre des données dans le tube tandis qu'un second récupèrera les données émises par le premier. La lecture est bloquante tant que le tube est vide, l'écriture est bloquante tant que le tube est plein. Les tubes sont unidirectionnels. Il faut utiliser deux tubes si, dans l

Use Quizgecko on...
Browser
Browser