(Archi-2) Sémaphores Lecture 2007-2008 Institut Universitaire de Technologie de Montpellier (Archi-2).1
Les objectifs du cours sont : Comprendre les problèmes engendrés par la concurrence Comprendre le concept de Savoir utiliser les s en C sous Linux (Archi-2).2
La programmation dans un environnement multi-tâche nécessite la définition de sections critiques Une section critique est une section du programme qui ne sera exécutée par une seule séquence de contrôle (thread/tâche). La séquence de contrôle qui entre dans la section critique s assure de l exclusivité des ressources de la section critique. (Archi-2).3
Exemple procedure_retrait (retrait) : 1 x = lecture_solde() ; 2 x = x - retrait ; 3 enregistrer_solde(x) ; procedure_depot(depot) : 1 y = lecture_solde() ; 2 y = y + depot ; 3 enregistrer_solde(y) ; (Archi-2).4
Exemple d ordonnancement 1 1 x = lecture_solde() ; 2 x = x - retrait ; 3 enregistrer_solde(x) ; 4 >perte de la CPU< 5 6 1 2 3 4 y = lecture_solde() ; 5 y = y + depot ; 6 enregistrer_solde(y) ; (Archi-2).5
Exemple d ordonnancement 2 1 x = lecture_solde() ; 2 x = x - retrait ; 3 >perte de la CPU< 4 5 6 enregistrer_solde(x) ; 1 2 3 y = lecture_solde() ; 4 y = y + depot ; 5 enregistrer_solde(y) ; 6 >perte de la CPU< (Archi-2).6
Comprendre le problème Nous avons besoin de définir une section critique pour n autoriser qu une seule séquence d exécution La section critique dans l exemple précédant se trouve entre la lecture du solde et sa sauvegarde (Archi-2).7
La solution Pour se prémunir des problèmes causés par l accès concurrent à des ressources partagées nous allons : définir un mécanisme à base de jeton(s) seule la séquence d exécution qui s octroie un jeton peut accéder à la section critique (Archi-2).8
Edsger Dijkstra On peut réaliser ce mécanisme de plusieurs façons (plus ou moins faciles à utiliser) Edsger Dijkstra a défini le concept de qui est très général et très facile à utiliser (Archi-2).9
C est quoi un On peut comprendre le comme une variable spéciale sur laquelle on peut effectuer deux opérations : 1 P(sem) (Puis-je y aller?) 2 V(sem) (Vas-y!) (Archi-2).10
Sémaphore Le le plus simple est un binaire qui peut donc prendre deux valeurs : 0 ou 1. Les s qui peuvent prendre des valeurs > 1 sont appelés des s généralisé (Archi-2).11
Algorithme de P() et V() Si sem est une variable alors : P(sem) : Si sem > 0, décrémenter sem. Si sem = 0 alors suspendre l activité de la séquence de contrôle appelante. V(sem) : Si un processus a été suspendu en attendant sem alors reprendre son activité. Si aucun processus n est suspendu alors incrémenter sem. (Archi-2).12
Revenons sur notre exemple On rajoute le sem initialisé à 1. procedure_retrait (retrait) : procedure_depot(depot) : 1 P(sem) ; 1 P(sem) ; 2 x = lecture_solde() ; 2 y = lecture_solde() ; 3 x = x - retrait ; 3 y = y + depot ; 4 enregistrer_solde(x) ; 4 enregistrer_solde(y) ; 5 V(sem) ; 5 V(sem) ; (Archi-2).13
exemple d ordonnancement 2 avec 1 P(sem) ; 2 x = lecture_solde() ; 3 x = x - retrait ; 4 >perte de la CPU< 5 6 7 enregistrer_solde(x) ; 8 V(sem) ; 1 2 3 4 P(sem) ; 5 >suspension du processus< 6 7 8 9 y = lecture_solde() ; 10 y = y + depot ; 11 enregistrer_solde(y) ; 12 V(sem) ; (Archi-2).14
Sémaphore Linux Nous allons maintenant voir comment programmer concrètement avec les s sous Linux Attention : Les fonctions s de Linux opèrent sur des tableaux de s (Archi-2).15
Sémaphore Linux #include <sys/sem.h> int semctl(int sem_id, int sem_num, int command,...); int semget(key_t key, int num_sems, int sem_flags); int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops); Signatures des fonctions de gestion des s Linux. (Archi-2).16
semget int semget(key_t key, int num_sems, int sem_flags); Cette fonction permet de créer un et de retourner un identifiant local (une sorte de descripteur pour le ) key : ce paramètre représente une clé qui permet à plusieurs processus indépendants de parler du même. Si on chercher à créer un interne au processus (entre threads) il faut mettre : IPC_PRIVATE num_sems : nombre de s désirés (1 en général) sem_flags : des drapeaux pour spécifier des droits sur le. On peut également rajouter des drapeaux comme : 0666 IPC_CREAT pour demander création du s il n existe pas ; 0666 IPC_CREAT IPC_EXCL pour préciser la création d un nouveau qui ne doit pas déjà exister. (Archi-2).17
semop int semop(int sem_id, struct sembuf *sem_ops,\ size_t num_sem_ops); Cette fonction est utilisée pour changer la valeur du. sem_id : est l identifiant retourné par semget sem_ops : un pointeur vers un tableau de structures qui précise l opération à effectuer num_sem_ops : nombre d opérations dans le tableau. (Archi-2).18
struct sembuf struct sembuf { short sem_num; short sem_op; short sem_flg; Cette structure précise les opérations qu il faut effectuer sur un. sem_num : précise le numéro du dans le tableau. sem_op : la valeur par laquelle modifier le : -1 correspond à P() et +1 correspond à V(). sem_flg : on mettra ce flag à SEM_UNDO pour que le système d exploitation puisse relâcher le si le processus se termine sans le libérer. (Archi-2).19
xxx int semctl(int sem_id, int sem_num, int command,...); Cette fonction permet un contrôle direct du. sem_id : l identifiant du retourné par semget sem_num : l indice du dans le tableau de s command : une commande... : un argument de la commande qui est une union. (Archi-2).20
union semun La structure de l union qui représente l argument de la commande doit contenir au moins : union semun { int val; struct semid_ds *buf; unsigned short *array; (Archi-2).21
Les commandes de semctl On dispose de plusieurs commandes, mais on ne présentera ici que deux : SETVAL : initialise un avec une valeur. On utilisera le champs val dans l union. Attention : L initialisation d un est une étape très importante! IPC_RMID : utilisé pour effacer/libérer l identifiant d un. Pour les commandes SETVAL et IPC_RMID, la fonction semctl retourne 0 en cas de succès et -1 en cas d erreur. (Archi-2).22
Exemple de fonction générique : initialisation int semaphore_init(int semid, { union semun sem_union; sem_union.val = val; int num, int val) if (semctl(semid, num, SETVAL, sem_union) == -1) return(0); return(1); (Archi-2).23
Exemple de fonction générique : libération void semaphore_liberer(int sem_id) { union semun sem_union; if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1) { fprintf(stderr, "Erreur d effacement du semaphore\n"); (Archi-2).24
Exemple de fonction générique : opération P() int semaphore_p(int sem_id, int num) { struct sembuf sem_b; sem_b.sem_num = num; sem_b.sem_op = -1; /* Je fais un Puis-je */ sem_b.sem_flg = SEM_UNDO; if (semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "erreur de semaphore_p\n"); return(0); return(1); (Archi-2).25
Exemple de fonction générique : opération V() int semaphore_v(int sem_id, int num) { struct sembuf sem_b; sem_b.sem_num = num; sem_b.sem_op = 1; /* Je fais un Vas-y */ sem_b.sem_flg = SEM_UNDO; if (semop(sem_id, &sem_b, 1) == -1) { fprintf(stderr, "erreur de semaphore_v\n"); return(0); return(1); (Archi-2).26
Exemple #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/sem.h> #include "semaphore.h" #define CLE_SEMAPHORE 1234 int main(int argc, char *argv[]) { int i, temps_pause,semid; char open_char = ( ; char close_char = ) ; //demande de creation d un semaphore. //si le semaphore existe semget retourne egalement sans erreur. semid = semget((key_t)cle_semaphore, 1, 0666 IPC_CREAT); //Initialisation du semaphore if (!semaphore_init(semid,0,1)) { fprintf(stderr, "Erreur\n"); exit(exit_failure); i = fork();... (Archi-2).27
Exemple... if(i==0){//je suis le fils. //initialisation du generateur aleatoire srand((unsigned int)getpid()); open_char = { ; close_char = ; for(i = 0; i < 10; i++) { if (!semaphore_p(semid,0)) { exit(exit_failure); printf("%c", open_char); fflush(stdout); temps_pause = rand() % 3; sleep(temps_pause); printf("%c", close_char); fflush(stdout); if (!semaphore_v(semid,0)) exit(exit_failure); temps_pause = rand() % 2; sleep(temps_pause);... (Archi-2).28
Exemple... else{//le pere srand((unsigned int)getpid()); for(i = 0; i < 10; i++) { if (!semaphore_p(semid,0)) {exit(exit_failure); printf("%c", open_char); fflush(stdout); temps_pause = rand() % 3; sleep(temps_pause); printf("%c", close_char); fflush(stdout); if (!semaphore_v(semid,0)) exit(exit_failure); temps_pause = rand() % 2; sleep(temps_pause); sleep(10); semaphore_liberer(semid); (Archi-2).29