Support de cours Unix compilation et programmation

Support de cours Unix compilation et programmation, tutoriel & guide de travaux pratiques Unix en pdf.

 Les processus
 Accè s aux donné es du BCP

Chaque processus possède un bloc de contrôle (BCP) qui contient toutes ses caractéristiques.
 Identité du processus
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
 Proprié taires du processus
#include <unistd.h>
uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
gid_t getegid(void);
 La primitive fork
#include <unistd.h>
pid_t fork(void) ;
En cas d’échec, comme tout appel système, la primitive fork renvoie la valeur ­1 et la variable errno contient le numéro de l’erreur. Il s’agit certainement alors d’un problème de dépassement du nombre de processus permis pour l’utilisateur ou d’une saturation de la table des processus.
En cas de succès, LE PROCESSUS APPELANT A ÉTÉ CLONÉ : le processus initial est appelé père tandis que le processus cloné est appelé fils. Le père garde son PID et continue sa vie tandis que le fils a un nouveau PID et commence la sienne…
Immédiatement après le fork, les deux processus exécutent le même code avec les mêmes données, les mêmes fichiers ouverts, le même environnement mais on peut les différentier par la valeur renvoyée par la primitive :
FORK RENVOIE 0 CHEZ LE FILS ET LE PID DU FILS CHEZ LE PÈRE.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
extern int errno;
main(void) {
pid_t pid;
if((pid = fork()) == -1)
{
fprintf(stderr, » Echec de fork erreur numéro %d.\n « ,errno) ; exit(1) ;
}
printf( » Cet affichage est réalisé par le fiston et par son papa.\n « ); if(pid == 0)
printf( » Cet affichage est réalisé par le fiston.\n « );
else
printf( » Cet affichage est réalisé par le papa.\n « );
}
L’ordre dans lequel les affichages vont avoir lieu est totalement imprévisible car les deux processus père et fils sont concurrents.

Les primitives exec

#include <unistd.h>
int execl(const char *ref, const char *arg0,…,(char *)0);
Les primitives de la famille exec* permettent à un processus de charger en mémoire, en vue de son exécution, un nouveau programme binaire. En cas de succès de l’opération, il y a écrasement du code, des données et par conséquent aucune marche arrière n’est possible. Le processus garde son PID et donc la plupart de ses caractéristiques : il n’y a aucune création de processus.
Exemple
#include <stdio.h>
#include <unistd.h>
int main(void){
execl(« /usr/bin/ls », »ls », »-l », »/tmp »,NULL) ;
perror(« echec de execl.\n »);
}

La primitive wait

Un processus fils qui se termine envoie à son père le signal SIGCHLD et reste dans un état dit « Zombie » tant que le processus père n’aura pas consulté son code de retour. La prolifération de processus à l’état zombie doit être formellement évitée, aussi il faut synchroniser le père et le fils à l’aide de la primitive wait : le père attend la mort du processus fils.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *ptretat) ;
Si le processus appelant n’a aucun fils, la primitive wait renvoie ­1 et errno…
Si le processus appelant possède des fils qui ne sont pas à l’état « zombie », il est mis en attente jusqu’à ce que l’un des fils passe dans cet état.
Si le processus appelant possède des fils à l’état « zombie », la primitive renvoie le PID d’un des fils et écrit à l’adresse ptretat un entier qui renseigne sur la façon dont s’est terminé ce fils (code de retour ou numéro de signal ayant tué le processus).
• Si le processus fils est mort « naturellement » par un appel à exit(code), alors l’octet de poids faible de ce code est recopié dans l’octet de poids fort de l’entier pointé par ptretat (l’autre octet est nul).
• Si le processus fils a été tué par un signal, alors le numéro de signal (entier inférieur strictement à 128) est recopié dans l’octet de poids faible de l’entier pointé par ptretat (l’autre octet est nul). Si un fichier core a été créé la valeur décimale 128 est ajoutée à l’entier pointé par ptretat.
Le fichier <sys/wait.h> contient des fonctions de traitement de cet entier *ptretat qui sont macro­définies et qui permettent la réalisation d’une « autopsie » des processus fils.
Exemple d’autopsie
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/types.h>
main(void)
{
pid_t pid,pidfils ;
int status ;
printf(« Processus père de PID %d\n « ,getpid()) ; switch(pid = fork())
{ case (pid_t)-1 : perror(« naufrage… « ) ;
exit(2) ;
case (pid_t)0 :
printf( » Début du processus fils PID=%d \n « ,getpid()) ; /*tempo pour avoir le temps de tuer éventuellement
le processus fils avant qu’il se termine normalement */
sleep(30) ;
exit(33) ;
default :
pidfils = wait(&status) ; /* attention adresse ! ! ! */
printf( » Mort du processus fils PID=%d\n « ,pidfils) ;
if(WIFEXITED(status))
{
printf( » code de retour :%d\n « ,WEXITSTATUS(status)) ; exit(0) ;
}
if(WIFSIGNALED(status)) {
printf( » tué par le signal %d\n « ,WTERMSIG(status)) ; if(WCOREDUMP(status))
printf( » fichier core créé\n « ) ;
exit(1) ;
}
Le code de retour du fils est égal à 33 sauf si on le tue par l’envoi d’un signal. Le code de retour du père sera égal à 2 si la primitive fork a échoué, à 1 si le processus fils a été tué et à 0 si le processus fils s’est auto­ détruit en appelant la fonction exit. C’est parce que la dernière éventualité est considérée comme « normale » que la valeur du code de retour du père au processus shell « grand­père » est zéro.
En plus de l’autodestruction d’un processus, la fonction exit réalise la libération des ressources allouées au processus au cours de son exécution.
Autre façon d’éliminer les zombies
La prolifération de processus à l’état zombie doit être formellement évitée, aussi lorsque le père n’a pas à être synchronisé sur ses fils, on peut inclure dans le code du père l’instruction signal(SIGCHLD,SIG_IGN) pour éliminer les fils à l’état Z au fur et à mesure de leur apparition.

Le mé canisme du fork/exec

L’association du fork et de l’exec permet de créer son propre shell : le processus père saisie la commande de l’utilisateur et délègue à son fils l’exécution de la commande.
 Fonctionnement canonique d’un shell
Un interpréteur de commandes, utilisé de façon interactive, fonctionne en boucle infinie, selon le shéma suivant :
1. Lecture ligne de commandes
2. Interprétation (substitutions…)
3. Clonage du shell avec fork
4. Chez le père :attente du fils avec wait puis retour en 1
Chez le fils :changement de code avec exec puis mort.

Les signaux
Introduction
Les signaux disponibles
Sur un système donné, on dispose de NSIG signaux numérotés de 1 à NSIG. La constante NSIG, ainsi que les différents signaux et les prototypes des fonctions qui les manipulent, sont définis dans le fichier <signal.h>.
$grep SIGHUP /usr/include/signal.h
#define SIGHUP 1 /* Fin du processus leader de session */
$
Le comportement à la ré ception d’un signal
Terminologie des signaux
Un signal envoyé par le noyau ou par un autre processus est un signal pendant : cet envoi est mémorisé dans le BCP du processus.
Un signal est délivré (ou pris en compte) lorsque le processus concerné réalise l’action qui lui est associée dans son BCP , c’est à dire, au choix :
l’action par défaut : en général la mort du processus ignorer le signal
l’action définie par l’utilisateur (handler) : le signal est dit capté.
Un signal peut également être masqué (ou bloqué) : sa prise en compte sera différée jusqu’à ce que le signal ne soit plus masqué.
­ Limites des signaux
Lorsqu’un processus est en sommeil et qu’il reçoit plusieurs signaux :
Aucune mémorisation du nombre de signaux reçus : 10 signaux SIGINT  1 signal SIGINT.
Aucune mémorisation de la date de réception d’un signal : les signaux seront traités ultérieurement par ordre de numéro.
Aucun moyen de connaître le PID du processus émetteur du signal.
 ­ La délivrance des signaux
Attention, les signaux sont asynchrones : la délivrance des signaux non masqués a lieu un « certain temps » après leur envoi, quand le processus récepteur passe de l’état actif noyau à l’état actif utilisateur. Cela explique pourquoi un processus n’est pas interruptible lorsqu’il exécute un appel système.
– L’envoi des signaux
 La primitive kill
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int sig) ;
Les processus émetteur et récepteur doivent avoir le même propriétaire ! ! !
Le « faux » signal 0 peut être envoyé pour tester l’existence d’un processus.
Exemple
Le processus père teste l’existence de son fils avant de lui envoyer le signal SIGUSR1.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
int main(void) {
pid_t pid,pidBis;
int status;
switch(pid = fork())
{
case (pid_t)0:
while(1) sleep(1);
default:
sleep(10);
if(kill(pid,0) == -1) {
printf(« processus fils %d inexistant »,pid);
exit(1);
}
else {
printf(« envoi de SIGUSR1 au fils %d »,pid);
kill(pid,SIGUSR1);
}
pidBis=wait(&status);
printf(« Mort de fils %d avec status=%d »,pidBis,status); exit(0);
}
}
Le masquage des signaux
 Manipulation des ensembles de signaux
Le type sigset_t correspond à de tels ensembles.
Des fonctions sont disponibles pour construire et manipuler les ensembles de signaux avant de mettre en place un masquage collectif de ces signaux ou une suppression du masque courant.
 La primitive sigprocmask
#include <signal.h>
int sigprocmask(int opt,const sigset_t *new,sigset_t old) ;
Cette primitive permet l’installation manuelle d’un masque à partir de l’ensemble pointé par new et éventuellement du masque antérieur que l’on récupère au retour de la primitive à l’adresse old si le troisième paramètre n’est pas le pointeur nul.

1 Compilation et programmation
1.1 Préparation du fichier source
1.2 La compilation sous unix
1.3 Constitution d’une bibliothèque
1.4 Make
1.5 L’interface CUnix
1.6 L’interface shell
1.7 Aide à la mise au point
2 Les processus
2.1 Accès aux données du BCP
2.2 La primitive fork
2.3 Les primitives exec
2.4 La primitive wait
2.5 Le mécanisme du fork/exec
3 Les signaux
3.1 Introduction
3.2 L’envoi des signaux
3.3 Le masquage des signaux
3.4 Le captage des signaux
3.5 L’attente d’un signal
4 Les entréessorties
4.1 Les différentes tables utilisées
4.2 Les opérations de base
4.3 Manipulation de descripteurs
4.4 Manipulation de inoeuds
4.5 Consultation d’un inoeud
4.6 Modification des caractéristiques d’un inoeud
4.7 Les fichiers de type catalogue
4.8 La bibliothèque d’entréesortie standard
5 Les verrous
5.1 Introduction : Les accès concurrents
5.2 Les verrous externes
5.3 Les verrous internes
6 Les IPC
6.1 Introduction
6.2 Caractéristiques communes
6.3 Le fichier standard <sys/ipc.h>
6.4 La gestion des clés
6.5 Les commandes IPC System V
6.6 Les sémaphores
6.7 Segments de mémoire partagée
7 Les threads
7.1 Introduction
7.2 Les attributs d’un activité
7.3 Création et terminaison des activités
7.4 Synchronisation des activités
7.5 Exemple d’utilisation des mutex et des conditions

……..

Cours gratuitTélécharger le cours complet

Télécharger aussi :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *