Rappel (CSC3102)
int kill(pid_t pid, int sig);
sig
au processus pid
sig
?
9
): pas portable (dépend de
l’OS)SIGKILL
) définie dans
signal.h
Voici un exemple de programme utilisant la fonction
kill
:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc, char**argv) {
if(argc != 2) {
(stderr, "usage: %s PID\n", argv[0]);
fprintfreturn EXIT_FAILURE;
}
= atoi(argv[1]);
pid_t pid int signo = SIGKILL;
("Sending signal %d to %d\n", signo, pid);
printf(pid, signo);
kill
return EXIT_SUCCESS;
}
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum
struct sigaction
est une structure de la forme:struct sigaction {
void (*sa_handler)(int); // pointeur sur la fonction à appeler
void (*sa_sigaction)(int, siginfo_t *, void *);
;
sigset_t sa_maskint sa_flags;
void (*sa_restorer)(void);
};
Il est possible d’utiliser sigaction
pour “intercepter”
tout signal sauf les signaux SIGKILL
et
SIGSTOP
struct sigaction
La valeur prise par sa_handler
est: * l’adresse d’une
fonction (par ex: void signal_handler(int signo)
* Le
paramètre signo
est le numéro du signal reçu * la valeur
SIG_DFL
pour restaurer l’action par défaut (tuer le
processus) * la valeur SIG_IGN
pour ignorer le signal: à la
réception de ce signal, aucune action ne sera effectuée
Sauf cas d’usages particuliers, les autres champs de la structure
sigaction
sont à mettre à 0
.
oldact
La fonction sigaction
modifie le comportant du processus
lorsqu’il reçoit le signal signum
. Si oldact
n’est pas NULL
, l’ancien comportement y est stocké.
Si la fonction traitant le signal manipule des variables globales, il
est conseillé de les déclarer volatile
. Par exemple:
volatile int var;
Lorsqu’une variable est déclarée volatile
, le
compilateur limite les optimisations faites sur cette variable. Par
exemple, le compilateur ne met pas en cache (dans un registre) la
variable.
Si une fonction (par exemple foo
) qui manipule la
variable var
non volatile
est interrompue par
un traitant de signal (sig_handler
) modifiant
var
, la modification de la variable risque de ne pas être
“vue” par foo
qui travaille sur une copie en cache de la
variable. La fonction foo
risque donc de travailler sur une
version obsolète de la variable.
Voici un exemple de programme utilisant sigaction
pour
intercepter un signal:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
/* Function to call upon signal reception */
void signal_handler(int signo) {
("Received: signal %d\n", signo);
printf}
int main(int argc, char**argv) {
if(argc != 2) {
(stderr, "usage: %s signo\n", argv[0]);
fprintfreturn EXIT_FAILURE;
}
/* Initialize the sigaction structure */
int signo = atoi(argv[1]);
struct sigaction s;
.sa_handler = signal_handler;
s
/* Install the signal handler */
("Installing signal handler for signal %d\n", signo);
printfint retval = sigaction(signo, &s, NULL);
if(retval<0) {
("sigaction failed");
perror();
abort}
/* Wait to receive signals */
while(1) {
("[%d] Sleeping...\n", getpid());
printf(1);
sleep}
return EXIT_SUCCESS;
}
int pause( );
int alarm(unsigned int s);
SIGALRM
après s
secondes Pour programmer une alarme avec une granularité plus fine, utilisez
la fonction setitimer
. Cette fonction permet de programmer
des alarmes périodiques (qui se répètent) avec une granularité de
l’ordre de la microseconde.
Les 3 sous-sections suivantes présentent des notions pour les étudiant·e·s Ninja qui ont vraiment envie d’aller encore plus loin.
sigsetjmp
et
siglongjmp
Permet de faire un “saut (goto
) non local”
int sigsetjmp(sigjmp_buf env, int savesigs);
void siglongjmp(sigjmp_buf env, int val);
env
sigsetjmp
Ces fonctions peuvent être utile pour la gestion des signaux :
sigsetjmp
permet de définir l’endroit du code où revenir
après l’appel à siglongjmp
.signal_handler
); appeler siglngjmp
.Elles sont également utilisées pour mettre en place des mécanismes d’exception.
Exemple : suivez le lien pour visualiser cet exemple tiré du livre “Développement système sous Linux” de Christophe BLAESS. FYI, ce livre est disponible à la médiathèque.
libuv permet de faire de la programmation événementielle
libuv
.libuv
is not much different from
directly using the BSD socket interface, some things are easier, all are
non-blocking, but the concepts stay the same. In addition libuv offers
utility functions to abstract the annoying, repetitive and low-level
tasks like setting up sockets using the BSD socket structures, DNS
lookup, and tweaking various socket parameters.” (extrait du
chapitre Networking de la documentation libuv).Pour récupérer des exemples, c’est ici.
Il existe aussi d’autres bibliothèques, mais qui nous semblent moins pertinentes (vu leurs dernières dates de mise à jour): libevent et libev.
Cet extrait de l’exemple de tcp-echo-server
de
libuv
(cf. libuv-1.48.0/docs/code/tcp-echo-server/main.c
)
illustre le problème de lisibilité:
void echo_write(uv_write_t *req, int status) {
if (status) {
(stderr, "Write error %s\n", uv_strerror(status));
fprintf}
(req);
free_write_req}
void echo_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) {
if (nread > 0) {
*req = (write_req_t*) malloc(sizeof(write_req_t));
write_req_t ->buf = uv_buf_init(buf->base, nread);
req((uv_write_t*) req, client, &req->buf, 1, echo_write);
uv_writereturn;
}
// ...
}
void on_new_connection(uv_stream_t *server, int status) {
// ...
*client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
uv_tcp_t (loop, client);
uv_tcp_initif (uv_accept(server, (uv_stream_t*) client) == 0) {
((uv_stream_t*) client, alloc_buffer, echo_read);
uv_read_start}
// ...
}
Quand une nouvelle connexion est établie avec ce serveur, la
fonction on_new_connection
est appelée. Cette fonction
appelle la fonction uv_read_start
qui contient un paramètre
echo_read
, i.e. la fontion à appeler quand la lecture sur
la socket par uv_read_start
sera terminée.
Et echo_read
appelle la fonction
uv_write
avec le paramètre echo_write
, i.e. la
fonction à appeler quand l’écriture sur la socket sera
terminée.
Ce serait bien d’avoir une seule fonction qui enchaîne ces 3 codes. Cela supposerait une fonction capable de relâcher la main en plein milieu de son exécution et reprendre à cet endroit quand on lui redonnerait la main. C’est la notion de coroutine.
Hélas, cette notion n’est pas implémentée en langage C. Mais, il existe des contournements, cf. articles que nous vous invitons à lire :