View Full Version : [C] Gestione dei segnali
Ciao e auguri a tutti :)
Volevo sapere se questa è una giusta gestione del segnale SIGINT, perché quando faccio partire il programma e premo CTRL-C mi va in segmentation fault e non riesco a capirne la causa. Questo è il codice del main (ancora provvisorio):
#include "functions.h"
int main(void){
int i;
pid_t pid;
int status;
ssize_t nread;
ssize_t nwrite;
struct sigaction sigint_handl;
char *buf=(char *)malloc(sizeof(char)*COMM_LENGTH);
char *name=(char *)malloc(sizeof(char)*COMM_LENGTH);
sigint_handl.sa_handler=sigint_handler;
if (sigemptyset(&sigint_handl.sa_mask)!=0){
perror("sigemptyset failed");
exit(SIGH_ERR);
}
sigint_handl.sa_flags=0;
if(sigaction(SIGINT,&sigint_handl,NULL)){
perror("sigaction failed");
exit(SIGH_ERR);
}
setjmp(chdir_return);
setjmp(sigint_return);
while(1){
sprintf(buf,"%s $ ", get_current_dir_name());
if ((nwrite=write(STDOUT_FILENO,buf,COMM_LENGTH)) != COMM_LENGTH){
perror("write error");
if (buf!=NULL){
free(buf);
buf=NULL;
}
if (name!=NULL){
free(name);
name=NULL;
}
exit(ERR_GEN);
}
if( (nread = read(STDIN_FILENO,buf,COMM_LENGTH)) <= 0){
perror("read error");
if (buf!=NULL){
free(buf);
buf=NULL;
}
if (name!=NULL){
free(name);
name=NULL;
}
exit(ERR_GEN);
}
strcpy(name,buf);
parse(name);
if(command_lines(name) == OK){
if((pid=fork()) < 0){
perror("fork error");
if (buf!=NULL){
free(buf);
buf=NULL;
}
if (name!=NULL){
free(name);
name=NULL;
}
exit(FORK_ERR);
}
if(pid > 0){
if (waitpid(pid,&status,0) == -1){
perror("waitpid error");
if (buf!=NULL){
free(buf);
buf=NULL;
}
if (name!=NULL){
free(name);
name=NULL;
}
}
}
}
}
return 0;
}
E questo quello del signal handler:
#include "functions.h"
void sigint_handler(int signo){
if(signo != SIGINT){
printf("Something wrong, handler for SIGINT");
exit(SIGH_ERR);
}
else
longjmp(sigint_return,0);
}
Grazie e buon natale a tutti. :cool:
Prendila come una mia opinione personale ... credo che non sia una buona idea mettere un longjmp() nell'handler.
Perché no? E come faccio a tornare qua "sprintf(buf,"%s $ ", get_current_dir_name());" (che sarebbe il prompt della shell)?
ilsensine
27-12-2006, 09:54
In quel caso la read ritorna -1 con errno==EINTR.
Ah quindi dovrei controllare se la read torna EINTR (e se lo torna lanciare il signal handler) ? Un'altra possibilità. Mettiamo il caso che io voglia eseguire ls e mi trovo in una directory con tantissimi files. L'esecuzione di ls non è istantanea ma ci mette un po' per listare tutto il contenuto. Mettiamo il caso che durante l'esecuzione di ls premo CTRL-C. Visto che ls lo dovrei eseguire tramite una exec (a meno di altri suggerimenti da parte vostra :) ) , come faccio a fargli eseguire il mio signal handler dato che la exec di un comando riporta a default il gestore del segnale?
Grazie.
ilsensine
27-12-2006, 10:21
Non ti seguo... il segnale viene inviato al tuo processo, non a ls
Cerco di spiegarmi meglio. Io per eseguire un comando con questa shell (perché in realtà il programma che sto realizzando è una shell) creo un figlio tramite fork() e faccio eseguire al figlio un comando tramite exec(). Il figlio eredita dal padre il signal handler impostato da me, ma nel momento in cui eseguo la exec di un comando, il signal handler ritorna quello di default. Io vorrei fare in modo (se possibile) di eseguire il mio signal handler dopo la exec. Per questo ti avevo fatto l'esempio di ls all'interno di una directory con migliaia di files, perché ad esempio mi stufo di vedere tutto il contenuto e ad un certo punto premo CTRL-C e ritorno al prompt. Spero di essere stato chiaro.
ilsensine
27-12-2006, 10:59
Innanzitutto, non puoi passare un _tuo_ signal handler tramite exec, in quanto il tuo spazio di indirizzi (compreso il codice dell'handler!) viene completamente riscritto dalla exec.
Puoi intercettare il segnale dal processo principale e uccidere il child con un segnale, se è ciò che intendi fare.
ma nel momento in cui eseguo la exec di un comando, il signal handler ritorna quello di default
Solo per il child, per il motivo che ti ho esposto.
Il parent continua ad utilizzare gli handler che ha definito.
Certo è proprio questo il problema, cioè che il child eseguendo la exec non ha più il signal handler del padre. E se faccio un controllo del tipo WIFSIGNALED(status) e WTERMSIG(status) al ritorno dalla wait? Potrei vedere se il figlio è stato terminato con un segnale e con quale segnale no? Però l'unica cosa è che quando so il segnale con il quale è stato terminato, se il segnale ad esempio è SIGINT, quale signal handler viene eseguito?
Grazie.
ilsensine
27-12-2006, 11:13
Continuo a non capire...
Come può il child beccarsi un segnale in seguito a ctrl+c dalla console? E' impossibile, a meno che non hai per errore invocato la exec nel ramo del parent della fork e non nel ramo del child...
Allora facciamo così, cerco di fare un esempio. Il figlio tramite la funzione exec() esegue ls -l e riceve questo output:
file1
file2
file3
file4
file5
ecc.
Mettiamo il caso che i file all'interno della directory che sto listando sono tantissimi (sono 10000 file). Io non voglio aspettare che finisca di elencarli tutti (magari ci mette un po) quindi ad un certo punto, prima che finisce di elencare tutto il contenuto, premo CTRL-C. Una volta premuto dovrà eseguire un signal handler (che per forza di cose sarà quello di default dato che ho eseguito la exec()). Io però devo fare in modo di tornare a questa istruzione "sprintf(buf,"%s $ ", get_current_dir_name()); ed è per questo che mi serve il mio signal handler.
ilsensine
27-12-2006, 11:29
Se posti un pò di codice forse possiamo intenderci meglio...
main.c
#include "functions.h"
int main(void){
extern int errno;
int i;
pid_t pid;
int status;
ssize_t nread;
ssize_t nwrite;
struct sigaction sigint_handl;
char *buf=(char *)malloc(sizeof(char)*COMM_LENGTH);
char *name=(char *)malloc(sizeof(char)*COMM_LENGTH);
sigint_handl.sa_handler=sigint_handler;
if (sigemptyset(&sigint_handl.sa_mask)!=0){
perror("sigemptyset failed");
exit(SIGH_ERR);
}
sigint_handl.sa_flags=0;
if(sigaction(SIGINT,&sigint_handl,NULL)){
perror("sigaction failed");
exit(SIGH_ERR);
}
while(1){
sprintf(buf,"%s $ ", get_current_dir_name());
if ((nwrite=write(STDOUT_FILENO,buf,COMM_LENGTH)) != COMM_LENGTH){
perror("write error");
if (buf!=NULL){
free(buf);
buf=NULL;
}
if (name!=NULL){
free(name);
name=NULL;
}
exit(ERR_GEN);
}
if( (nread = read(STDIN_FILENO,buf,COMM_LENGTH)) <= 0){
if(errno == EINTR)
continue;
else{
if (buf!=NULL){
free(buf);
buf=NULL;
}
if (name!=NULL){
free(name);
name=NULL;
}
exit(ERR_GEN);
}
}
strcpy(name,buf);
parse(name);
if(command_lines(name) == OK){
if((pid=fork()) < 0){
perror("fork error");
if (buf!=NULL){
free(buf);
buf=NULL;
}
if (name!=NULL){
free(name);
name=NULL;
}
exit(FORK_ERR);
}
if(pid == 0{
if(pid > 0){
if (waitpid(pid,&status,0) == -1){
perror("waitpid error");
if (buf!=NULL){
free(buf);
buf=NULL;
}
if (name!=NULL){
free(name);
name=NULL;
}
continue;
}
if(WIFSIGNALED(status))
signoris=WTERMSIG(status);
}
}
}
return 0;
}
functions.h
/* Librerie */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#include <glob.h>
#include <errno.h>
#include <setjmp.h>
#include <sys/wait.h>
#include <sys/types.h>
/* Definizione di costanti */
#define MAX_ARGS 256 /* Numero massimo di argomenti sulla linea di comando */
#define COMM_LENGTH 256 /* Lunghezza massima di un comando */
/* Costanti per errori */
#define OK 0 /* OK */
#define F_NOTEXEC 1 /* File non eseguibile */
#define F_NOEXIST 2 /* Accesso negato o file inesistente */
#define ERR_GEN 3 /* Errore generico */
#define READ_ERR 4 /* Read error */
#define WRITE_ERR 5 /* Write error */
#define FORK_ERR 6 /* Fork error */
#define SIGH_ERR 7 /* Errore del signal handler */
/* Variabili globali */
char *args[MAX_ARGS]; /* Argomenti sulla linea di comando */
glob_t pglob; /* Variabile per il trattamento del filename expansion */
/* Definizione di funzioni */
int command_lines(char *); /* Cerca la locazione del comando e restituisce il pathname completo */
void parse(char *); /* Analizza il comando passato suddividendo gli argomenti */
void sigint_handler(int); /* Signal handler per CTRL-C */
void sigquit_handler(int); /* Signal handler per CTRL-\ */
sigint_handler.c
#include "functions.h"
void sigint_handler(int signo){
if(signo != SIGINT){
printf("Something wrong, handler for SIGINT");
exit(SIGH_ERR);
}
else
return;
}
ilsensine
27-12-2006, 12:02
if(pid == 0{
if(pid > 0){
!? :D
Il codice non è completo (mica ho finito tutto) :D
E quello è proprio il famoso ramo child che devo implementare (sempre che quella soluzione vada bene per quello che devo fare io).
ilsensine
27-12-2006, 14:30
Comunque, come per le altre funzioni, in caso di segnale intercettato la waitpid ritorna -1 con errno==EINTR. Puoi, in questo caso, inviare manualmente un SIGTERM al child; ad es. qualcosa tipo:
if(pid > 0){
if (waitpid(pid, &status, 0) == -1) {
if (errno!=EINTR)
perror("waitpid")
else {
kill(pid, SIGTERM);
/* necessario per evitare uno zombie */
do {
if (waitpid(pid, &status, 0)!=-1)
break;
} while(errno==EINTR);
}
continue;
} else {
<ramo child>
}
Ok però in questo caso sono io che mando un segnale esplicitamente. Io invece devo intercettare un segnale proveniente dal figlio (nel mio caso CTRL-C). Perché, ripeto, durante la exec() nel ramo child, nell'ipotesi in cui io prema CTRL-C deve partire il signal handler creato da me. Nel tuo codice:
if (waitpid(pid, &status, 0) == -1) {
if (errno!=EINTR)
perror("waitpid")
else {
kill(pid, SIGTERM);
attendi che arrivi un segnale ma poi non capisco perché tu debba terminare il figlio con SIGTERM. Se io interrompo il figlio con SIGINT, ad esempio, la funzione waitpid() tornerà -1 quindi che bisogno c'è di riterminare il figlio con la kill()?
ilsensine
27-12-2006, 15:32
Ok però in questo caso sono io che mando un segnale esplicitamente. Io invece devo intercettare un segnale proveniente dal figlio (nel mio caso CTRL-C).
Non funziona così. Come può un segnale "partire dal figlio"?
Perché, ripeto, durante la exec() nel ramo child, nell'ipotesi in cui io prema CTRL-C deve partire il signal handler creato da me.
Infatti parte, nel parent. Non nel child, per i motivi che ti ho detto.
Se io interrompo il figlio con SIGINT, ad esempio, la funzione waitpid() tornerà -1
Se waitpid ritorna -1 vuol dire che è stata interrotta, non che il child è terminato.
Esercitati con questo programmino, modificandolo a tuo piacimento, per vedere come funziona il tutto:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
static void sigint_handler(int signo) {
fprintf(stderr, "Caught signal %d\n", signo);
}
int main()
{
struct sigaction sigint_handl;
pid_t pid;
sigint_handl.sa_handler = sigint_handler;
sigemptyset(&sigint_handl.sa_mask);
sigint_handl.sa_flags = 0;
sigaction(SIGINT, &sigint_handl, NULL);
pid = fork();
if (pid==0) {
char *args[] = { "sleep", "10", NULL };
/* Senza setsid, il ctrl+c giunge anche al child. Potrebbe essere
desiderabile, oppure no.
Nota che un kill -INT <pid del parent> non giunge anche al child! */
setsid();
execv("/bin/sleep", args);
perror("execv");
} else {
while(1) {
int status;
int ret = waitpid(pid, &status, 0);
if (ret<0) {
if (errno!=EINTR)
perror("waitpid");
else {
fprintf(stderr, "Killing child...\n");
/* Nel caso che:
* 1) Il segnale non sia stato inviato anche al child
* (ad es. un SIGINT mandato direttamente al parent)
* 2) Il child gestisce e non termina su SIGINT
* allora accertiamoci di terminare il child manualmente.
*/
kill(pid, SIGTERM);
}
} else {
fprintf(stderr, "Child done. status=%d\n", status);
break;
}
}
}
return 0;
}
ilsensine
27-12-2006, 15:37
...il tutto, beninteso, se ho capito cosa ti serve: su ctrl+c, accertarti che il child ("ls" nel tuo caso) sia annullato.
Ok, grazie ci proverò. :)
ilsensine
27-12-2006, 16:07
Ti riepilogo alcuni punti, visto che sul tema dei segnali e ctrl+c c'è un pò di confusione:
- Un segnale inviato al parent NON VIENE inviato anche ai child.
- ctrl+c viene inviato a tutti i programmi dello stesso "program group" del programma bersaglio. Questo perché bash utilizza la killpg (o la kill con pid "negativo"), non la semplice kill, per inviare il segnale.
- Dopo la fork, il child appartiene allo stesso "program group" del parent
- setsid fa sì che un processo venga eseguito in un nuovo "program group", rendendolo immune alla "propagazione" dei segnali in seguito alla killpg
- Se instanzi un tuo signal handler, sii pronto a gestire le funzioni che ritornano -1 con errno==EAGAIN. Se non sai se una funzione può ritornare o meno EINTR, dai una occhiata alla manpage. Normalmente tutte le funzioni "bloccanti" possono farlo. Trascurare questo comportamento è un bug in quanto tratteresti come errori degli eventi che errori non sono!
- Il signal handler di un processo non può essere ereditato tramite la exec
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.