1. Interruptions
1.1. Configuration de la réception d’une interruption système
Le système envoie régulièrement des interruptions systèmes aux programmes en cours. Permettant ainsi d’interrompre un programme ou de transmettre un message. Chaque processus hérite d’une configuration par défaut des actions à réaliser lors de la réception des interruptions.
La configuration des actions à réaliser lors de la réception d’interruptions est faite par la fonction sigaction.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void handler(int sig) {
// Do something
}
int main() {
struct sigaction new_action = {};
new_action.sa_handler = handler;
struct sigaction old_action = {};
if (sigaction(SIGINT, &new_action, &old_action) == -1) {
perror("Failed to call sigaction system call");
return 0;
}
}
L’appel système sigaction possède trois paramètres:
-
Le signal que l’on souhaite configurer
-
Un pointeur vers la nouvelle action à paramétrer (
struct sigaction) -
Un pointeur utilisé pour récupérer l’action précédente (
struct sigaction) ouNULLsi l’on ne souhaite pas la récupérer
La structure struct sigaction possède plusieurs attributs, dans notre cas seul l’attribut sa_handler sera utilisé.
Il permet de configurer la fonction qui sera exécutée lors de la prochaine interruption.
1.2. SIG_IGN et SIG_DFL
Question :
Que représente SIG_DFL ? (man 2 sigaction)
Question :
Que représente SIG_IGN ? (man 2 sigaction)
1.3. Compilation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <signal.h>
#include <stdio.h>
void handler(int sig) { (2)
printf("Received Signal: %d\n", sig);
struct sigaction new_action = {};
new_action.sa_handler = SIG_DFL; (3)
if (sigaction(SIGINT, &new_action, NULL) == -1) { (1)
perror("Failed to call sigaction system call in handler");
}
}
int main() {
struct sigaction new_action = {};
new_action.sa_handler = handler;
struct sigaction old_action = {};
if (sigaction(SIGINT, &new_action, &old_action) == -1) { (1)
perror("Failed to call sigaction system call");
return 0;
}
while (1);
}
| 1 | Configuration de l’action à réaliser lors de la réception de l’interruption SIGINT (Ctrl + C) |
| 2 | Fonction passée en argument de sigaction qui sera appelée lors d’une interruption de type SIGINT |
| 3 | Voir question ! |
Question :
Compilez et exécutez le programme.
Combien de Ctrl + C faut-il pour terminer le programme ?
Expliquez.
1.4. Émission d’une interruption
Question :
En utilisant l’appel système kill et getpid modifiez le programme précédent pour envoyer autant d’interruptions que nécessaire pour terminer le programme avant la boucle while(1);.
2. Tubes
2.1. Création d’un tube
Les tubes (pipes) permettent de transmettre des données entre deux programmes.
C’est le système qui est utilisé lorsque l’on exécute la commande ls | wc -l dans un terminal.
Il permet de transmettre la sortie standard du programme ls vers l’entrée standard du programme wc -l.
En C ce mécanisme est accessible via l’appel système pipe (bien nommé ce coup-ci).
La fonction prend en argument un tableau de deux descripteurs de fichiers.
Elle se charge d’initialiser le tableau en y insérant deux descripteurs de fichiers initialisés.
Le premier élément du tableau permet de lire les données dans le tube. Le second élément du tableau permet d’écrire des données dans le tube.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int file_descriptors[2] = {}; (1)
int result = pipe(file_descriptors); (2)
if (result == -1) { (3)
perror("Unable to open text.txt file: "); (3)
exit(errno); (3)
}
return EXIT_SUCCESS;
}
| 1 | Initialisation d’un tableau de deux éléments |
| 2 | Création du tube et initialisation des descripteurs de fichier |
| 3 | Gestion de l’erreur |
2.2. Lecture depuis un tube
La lecture dans un tube se fait à l’aide de la fonction read.
Voici un exemple d’utilisation :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void read_from_pipe(const int *pipe, char buffer[200]) {
char character = 0;
int index = 0;
do {
ssize_t result = read(pipe[STDIN_FILENO], &character, 1); (1)
if (result == -1) {
perror("Unable to read from pipe:");
return;
} else if (result == 1) {
buffer[index] = character; (2)
index++;
} else {
printf("Nothing to read...");
}
} while (character != '\0'); (3)
buffer[index] = 0;
}
| 1 | Lecture caractère par caractère |
| 2 | Ajout du caractère lu dans le buffer |
| 3 | Lecture jusqu’à réception d’un '\0' |
2.3. Écriture dans un tube
L’écriture dans un tube se fait à l’aide de la fonction write.
1
2
3
static void write_to_pipe(int pipe[], const char *message) {
write(pipe[1], message, strlen(message) + 1); (1)
}
| 1 | On envoie un octet de plus pour envoyer le '\0' de la chaine. |
2.4. A vous de jouer
Question : Modifiez le code ci-dessous pour que le processus fils envoi un message vers le processus père en utilisant le même tube.
-
Le processus père doit écrire son premier message puis attendre la réponse (
readest bloquant) -
Le processus fils doit attendre le premier message puis écrire une réponse.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>
static void child_process(const int *pipe_fd) {
char character = 0;
char buffer[200];
int index = 0;
printf("Child process attempts to read from pipe...\n");
do {
if (read(pipe_fd[0], &character, 1) == 1) {
buffer[index] = character;
index++;
printf("Read character: %c\n", character);
}
} while (character != '\0');
buffer[index] = 0;
printf("Read message: %s\n", buffer);
}
static void parent_process(const int *pipe_fd) {
char *message = "I am your father!";
write(pipe_fd[1], message, strlen(message) + 1);
printf("Parent process sent message to child process...\n");
wait(NULL);
}
int main(int argc, char *argv[]) {
int pipe_fd[2];
pipe(pipe_fd);
int pid = fork();
if (pid > 0) {
parent_process(pipe_fd);
} else if (pid == 0) {
child_process(pipe_fd);
} else {
perror("Unable to fork process...");
}
return EXIT_SUCCESS;
}
2.5. Avec un second tube
Question : Modifiez le code ci-dessous pour que le processus fils envoi un message vers le processus père en utilisant un second tube.
-
Le processus père doit
-
Écrire son premier message dans un premier tube
-
Lire la réponse dans un second tube
-
Afficher le résultat sur la sortie standard
-
-
Le processus fils doit
-
Attendre le premier message depuis le premier tube
-
Écrire la réponse dans le second tube
-
3. Mémoire Partagée
Créez les trois fichiers suivants:
1
2
3
4
5
6
7
8
9
10
11
#ifndef _SHARED_MEMORY_H
#define _SHARED_MEMORY_H
#define KEY 12345
typedef struct {
int a;
double b;
} shared_structure;
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <sys/types.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "shared-memory.h"
int main() {
int mem_ID = shmget(KEY, sizeof(shared_structure), 0666 | IPC_CREAT);
if (mem_ID < 0) {
perror("shmget");
return EXIT_FAILURE;
}
shared_structure *shared_memory = shmat(mem_ID, NULL, 0);
if (shared_memory == (void *) -1) {
perror("shmat");
return EXIT_FAILURE;
}
shared_memory->a = 2;
shared_memory->b = 2.6544;
for (int i = 0; i < 20; i++) {
shared_memory->a = i;
printf("Read data from shared memory: data.a = %d, data.b = %lf\n", shared_memory->a, shared_memory->b);
sleep(1);
}
shmdt(shared_memory);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "shared-memory.h"
int main() {
int mem_ID = shmget(KEY, sizeof(shared_structure), 0444);
if (mem_ID < 0) {
perror("shmget");
return EXIT_FAILURE;
}
shared_structure *shared_memory = shmat(mem_ID, NULL, 0);
if (shared_memory == (void *) -1) {
perror("shmat");
return EXIT_FAILURE;
}
for (int i = 0; i < 20; i++) {
printf("Read data from shared memory: data.a = %d, data.b = %lf\n", shared_memory->a, shared_memory->b);
sleep(1);
}
shmdt(shared_memory);
return EXIT_SUCCESS;
}
3.1. Création
Question : Compilez et exécutez les deux programmes dans des terminaux différents.
-
Expliquez le résultat
-
Détaillez l’effet des fonctions
shmget(),shmat()etshmdt()
Question :
Utilisez la commande ipcs pour afficher les informations sur la mémoire partagée.
Pourquoi est-elle encore présente ?
3.2. Suppression
Question : Créez un programme permettant de supprimer la mémoire partagée.
utilisez la fonction shmctl pour controller et supprimer la mémoire.
N’oubliez pas que les pages de manuel contiennent toutes les informations dont vous avez besoin.
|