• Aucun résultat trouvé

Endormir un processus

Dans le document Programmation système en C sous (Page 102-105)

La fonction la plus simple pour endormir temporairement un processus est sleep( ) . qui est déclarée ainsi dans <unistd.h> :

unsigned int sleep (unsigned int nb_secondes);

Cette fonction endort le processus pendant la durée demandée et revient ensuite. À cause de la charge du système, il peut arriver que sleep( ) dure un peu plus longtemps que prévu. De même, si un signal interrompt le sommeil du processus, la fonction sleep( ) revient plus tôt que prévu, en renvoyant le nombre de secondes restantes sur la durée initiale.

Notez que sleep( ) est une fonction de bibliothèque, qui n'est donc pas concernée par l'attribut SA_RESTART des gestionnaires de signaux, qui ne sert à relancer que les appels-système lents.

Voici un exemple dans lequel deux processus exécutent un appel à sleep( ). Le processus père dort deux secondes avant d'envoyer un signal à son fils. Ce dernier essaye

exemple_sleep.c

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <unistd.h>

#include <sys/wait.h>

void

gestionnaire_sigusr1 (int numero) {

} int main (void) { pid_t pid;

unsigned int duree_sommeil;

struct sigaction action;

if ((pid = fork( )) < 0) {

fprintf (stderr, "Erreur dans fork \n");

exit (1);

}

action . sa_handler = gestionnaire sigusr1;

sigemptyset (& action . sa_mask);

action . sa_flags = SA_RESTART;

if (sigaction (SIGUSR1, & action, NULL) != 0) { fprintf (stderr, "Erreur dans sigaction \n");

exit (1);

}

if (pid == 0) {

system ("date +\"%H:%M:%S\"");

duree_sommeil = sleep (10);

system ("date +\"%H:%M:%S\"");

fprintf (stdout, "Durée restante %u\n", duree_sommeil);

} else { sleep (2);

kill (pid, SIGUSR1);

waitpid (pid, NULL, 0);

}

return (0);

}

Voici un exemple d'exécution :

$ ./exemple_sleep 12:31:19

12:31:21

Durée restante 8

$

La fonction sleep( ) étant implémentée à partir de l'appel-système alarm( ), il est vraiment déconseillé de les utiliser ensemble dans la même portion de programme. La bibliothèque GlibC implémente sleep( ) en prenant garde aux éventuelles interactions avec une alarme déjà programmée, mais ce n'est pas forcément le cas sur d'autres systèmes sur lesquels on peut être amené à porter le programme.

De même, si un signal arrive pendant la période de sommeil et si le gestionnaire de ce signal modifie le comportement du processus vis-à-vis de SIGALRM, le résultat est totalement imprévisible. Egalement, si le gestionnaire de signal se termine par un saut non local siglongjmp( ), le sommeil est définitivement interrompu.

Lorsqu'on désire assurer une durée de sommeil assez précise malgré le risque d'interruption par un signal, on pourrait être tenté de programmer une boucle du type : void

sommeil (unsigned int duree_initiale) {

unsigned int duree_restante = duree_initiale;

while (duree_restante > 0)

duree_restante = sleep (duree_restante);

}

Malheureusement, ceci ne fonctionne pas, car lors d'une invocation de la fonction sleep(

) si un signal se produit au bout d'un dixième de seconde par exemple, la durée renvoyée sera quand même décrémentée d'une seconde complète. Si ce phénomène se produit à plusieurs reprises, un décalage certain peut se produire en fin de compte. Pour l'éviter, il faut recadrer la durée de sommeil régulièrement. On peut par exemple utiliser l'appel-système time( ), qui est défini dans <time.h> ainsi :

time_t time (time_t * t);

Cet appel-système renvoie l'heure actuelle, sous forme du nombre de secondes écoulées depuis le le janvier 1970 à 0 heure GMT. De plus, si t n'est pas un pointeur NULL, cette valeur y est également stockée. Le format time_t est compatible avec un unsigned long.

Nous reviendrons sur les fonctions de traitement du temps dans le chapitre 25.

Voici un exemple de routine de sommeil avec une durée précise : void

sommeil (unsigned int duree_initiale) {

time_t heure_fin;

time_t heure_actuelle;

heure_fin = time (NULL) + duree_initiale;

while ((heure_ actuelle = time(NULL)) < heure_fin) sleep (heure_fin - heure_actuelle);

}

Cette routine peut quand même durer un peu plus longtemps que prévu si le système est très chargé, mais elle restera précise sur des longues durées, même si de nombreux signaux sont reçus par le processus.

Si on désire avoir une résolution plus précise que la seconde, la fonction usleep( ) est disponible. Il faut imaginer que le «u» représente en réalité le «µ» de microseconde. Le prototype de cette fonction est déclaré dans <unistd.h> ainsi :

void usleep (unsigned long nb_micro_secondes);

L'appel endort le processus pendant le nombre indiqué de microsecondes, à moins qu'un signal ne soit reçu entre-temps. La fonction usleep( ) ne renvoyant pas de valeur, on ne sait pas si la durée voulue s'est écoulée ou non. Cette fonction est implémentée dans le bibliothèque GlibC en utilisant l'appel-système select( ), que nous verrons dans le chapitre consacré aux traitements asynchrones.

Voici une variante du programme précédent, utilisant usleep( ). Nous allons montrer que cette fonction peut aussi être interrompue par l'arrivée d'un signal.

exemple_usleep.c :

#include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <unistd.h>

#include <sys/wait.h>

void

gestionnaire_sigusr1 (int numero) {

} int main (void) {

pid_t pid;

struct sigaction action;

if ((pid = fork( )) < 0) {

fprintf (stderr, "Erreur dans fork \n");

exit (1);

}

action . sa_handler = gestionnaire_sigusrl;

sigemptyset (& action . sa_mask);

action . sa_flags = SA_RESTART;

if (sigaction (SIGUSR1, & action, NULL) != 0) { fprintf (stderr, "Erreur dans sigaction \n");

exit (1):

}

if (pid == 0)

system ("date +\"%H:%M:%S\"");

usleep (10000000); /* 10 millions de ps = 10 secondes */

system ("date +\"%H:%M:%S\"");

} else {

usleep (2000000): /* 2 millions de ps = 2 secondes */

kill (pid, SIGUSR1);

waitpid (pid, NULL, 0);

}

}

return (0);

}

Le sommeil du processus fils, censé durer 10 secondes, est interrompu au bout de 2 secondes par le signal provenant du père, et ce malgré l'option SA_RESTART de sigaction( ), comme le montre l'exécution suivante :

$ ./exemple_usleep 08:42:34

08:42:36

La fonction usleep( ) étant implémentée à partir de l'appel-système select( ), elle n'a pas d'interaction inattendue avec un éventuel gestionnaire de signal (sauf si ce dernier se termine par un saut siglongjmp( ) bien entendu).

Il existe encore une autre fonction de sommeil, offrant une précision encore plus grande : nanosleep( ). Cette fonction est définie par Posix.1. Elle est déclarée ainsi dans

<time.h> :

int nanosleep (const struct timespec * voulu, struct timespec * restant);

Le premier argument représente la durée de sommeil désirée, et le second argument, s'il est non NULL, permet de stocker la durée de sommeil restante lorsque la fonction a été inter-rompue par l'arrivée d'un signal. Si nanosleep( ) dort pendant la durée désirée, elle renvoie 0. En cas d'erreur, ou si un signal la réveille prématurément, elle renvoie -1 (et place EINTR dans errno dans ce dernier cas).

La structure timespec servant à indiquer la durée de sommeil a été décrite dans le chapitre précédent, avec l'appel-système sigwaitinfo( ). Elle contient deux membres : l'un précisant le nombre de secondes, l'autre contenant la partie fractionnaire exprimée en nanosecondes.

Il est illusoire d'imaginer avoir une précision de l'ordre de la nanoseconde, ou même de la microseconde, sur un système multitâche. Même sur un système monotâche dédié à une application en temps-réel, il est difficile d'obtenir une telle précision sans avoir recours à des boucles d'attente vides, ne serait-ce qu'en raison de l'allongement de durée dû à l'appel-système proprement dit.

Quoi qu'il en soit, l'ordonnancement est soumis au séquencement de l'horloge interne, dont les intervalles sont séparés de 1/HZ seconde. La constante Hz est définie dans

<asm/param.h>. Elle varie suivant les machines et vaut par exemple 100 sur les architectures x86. Dans ce cas, le sommeil d'un processus est arrondi aux 10 ms supérieures.

Bien que la précision de cette fonction soit illusoire, elle présente quand même un gros avantage par rapport aux deux précédentes. La fonction usleep( ) ne renvoyait pas la durée de sommeil restante en cas d'interruption, sleep( ), nous l'avons vu, arrondissait cette valeur à la seconde supérieure, mais la pseudo-précision de nanosleep( ) permet de reprendre le sommeil interrompu en la rappelant directement avec la valeur de sommeil qui restait. Le calibrage à 1/HZ seconde (soit 1 centième de seconde sur les machines x86) permet de conserver une relative précision, même en cas de signaux fréquents. Dans le programme suivant, on va effectuer un sommeil de 60 secondes pendant lequel, le processus recevra chaque seconde cinq signaux (disons plutôt qu'il en recevra plusieurs, car certains seront certainement regroupés). A chaque interruption, nanosleep( ) est

exemple_nanosleep.c : #include <stdio.h>

#include <stdlib.h>

#include <signal.h>

#include <time.h>

#include <unistd.h>

#include <sys/wait.h>

void

gestionnaire_sigusr1 (int numero) {

} int main (void) {

pid_t pid;

struct sigaction action;

struct timespec spec;

int i;

if ((pid = fork( )) < 0) {

fprintf (stderr, "Erreur dans fork \n");

exit (1);

}

action . sa_handler = gestionnaire_sigusr1;

sigemptyset (& (action . sa_mask));

action . sa_flags = SA_RESTART;

if (sigaction (SIGUSR1, & action, NULL) != 0) { fprintf (stderr, "Erreur dans sigaction \n");

exit (1);

}

if (pid == 0) {

spec . tv_sec = 60; spec . tv_nsec = 0;

system ("date +\"%H:%M:%S\"");

while (nanosleep (& spec, & spec) != 0) ;

system ("date +\"%H:%M:%S\"");

} else {

sleep (2); /* Pour éviter d'envoyer un signal pendant */

/* l'appel system( ) à /bin/date */

for (i = 0 ; i < 59; i++) { sleep (1);

kill (pid, SIGUSR1);

kill (pid, SIGUSR1);

kill (pid, SIGUSR1);

kill (pid, SIGUSR1);

kill (pid, SIGUSR1);

}

waitpid (pid, NULL, 0);

}

return (0);

}

L'exécution suivante nous montre qu'à un niveau macroscopique (la seconde), la précision est conservée, même sur une durée relativement longue comme une minute, avec une charge système assez faible.

$ ./exemple_nanosleep 13:04:05

13:05:05

$

Bien sûr dans notre cas, le gestionnaire de signaux n'effectuait aucun travail. Si le gestionnaire consomme vraiment du temps processeur, et si la précision du délai est critique, on se reportera au principe évoqué avec sleep( ), en recadrant la durée restante régulièrement grâce à la fonction time( ).

Notons que depuis Linux 2.2, des attentes de faible durée (inférieures à 2 ms) sont finalement devenues possibles de manière précise avec nanosleep( ) en utilisant un ordonnancement temps-réel du processus. Dans ce cas, le noyau effectue une boucle active. L'attente est toute-fois prolongée jusqu'à la milliseconde supérieure.

Dans le document Programmation système en C sous (Page 102-105)