staging.inyokaproject.org

Signalhandler Verständnisproblem (C/C++)

Status: Gelöst | Ubuntu-Version: Ubuntu MATE 22.04 (Jammy Jellyfish)
Antworten |

Dakuan

Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6532

Ich habe da vor ca. einem Jahr ein (für mich) größeres Projekt angefangen. Da ich dafür auf die Mithilfe anderer Programme angewiesen bin, hatte ich eine Klasse P_Exec erstellt, die diese Aufgabe per Fork() und execvp() erledigt. Diese Klasse enthält auch einen Signal-Handler, den ich im wesentlichen von einem früheren Projekt übernommen hatte. Man möchte ja unnötige Zombies vermeiden.

Auszug:

 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
P_Exec {
    ...
    struct sigaction    sig_new;
    struct sigaction    sig_old;
    static void         sighandler( int sn ); // our new signal handler
    int                 handler_on( void );
    int                 handler_off( void );
    static volatile sig_atomic_t pipe_flag; // indicates we got an broken pipe signal
    int                 handler_active;
    ...

//-----------------------------------------------------------------------------
//      Install our own signal handler
int
P_Exec::handler_on( void ) {
    int err = 0;
    pipe_flag = 0;
    err |= sigemptyset( &sig_new.sa_mask );
    sig_new.sa_handler = sighandler;
    sig_new.sa_flags = 0;
    err |= sigaction( SIGCHLD, &sig_new, &sig_old );
    err |= sigaction( SIGPIPE, &sig_new, 0 );
    return err;
}

// (static)
void
P_Exec::sighandler( int sn ) {
    int status;
    int pid;

    switch( sn ) {
        case SIGPIPE:
            pipe_flag = 1;  // broken pipe
            break;

        case SIGCHLD:
            pid = waitpid( -1, &status, WNOHANG );
            if( pid > 0 ) // remove from child list
                pid_rem( pid );
            break;

    }
}
...

Da das bisher immer funktioniert hat, habe ich da nicht weiter drüber nachgedacht.

Aber jetzt kommt ein neues Problem hinzu. Ich hatte kürzlich das Programm um eine einfache Netzwerkfunktionalität erweitert. Da passiert es immer wieder mal, das ich zwar einen Socket erstellen kann, aber anschließend der connect() Aufruf nicht erfolgreich ist. errno steht auf EINTR.

Als Abhilfe wird empfohlen, den Socket zu schließen und es noch einmal zu versuchen. Als Alternative wird angegeben einen Signalhandler mit den Flags SA_RESTART einzurichten.

Und da ist mein Problem. Der bisherige Signalhandler, der weiterhin aktiv bleiben soll, befindet sich in einer anderen Klasse. Die beiden kennen sich nicht. Ich muss das jetzt also irgendwie zusammenführen und dabei erkenne ich, dass ich wohl etwas wesentliches nicht verstanden habe.

Kann ich für jedes Signal einen eigenen Signalhandler einbauen, oder muss ich einem globalen Signalhandler alles beibringen?

Im letzten Fall stellt sich mir die Frage, ob ich die zu behandelnden Signale während der Laufzeit des Programms ändern kann.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

Du kannst einen Signal-Handler pro Signal setzen. siehe hier

Ich kenne jetzt Deinen Code nicht, aber ich würde das nicht mit der Funktionalität zum Forken und Ausführen in eine Klasse packen, denn beides hat nur wenig miteinander zu tun - wenn überhaupt.

PS: Ich weiß gar nicht, ob ich das mit dem Socket überhaupt über einen Signal-Handler machen würde. Der ist ja global und Du musst dann auf ziemlich lokale Daten zugreifen. Könnte u.U. auch Threadingprobleme geben.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6532

Du kannst einen Signal-Handler pro Signal setzen.

Danke. Das wollte ich eigentlich wissen.

... ich würde das nicht mit der Funktionalität zum Forken und Ausführen in eine Klasse packen, ...

Das könnte sein. Ich habe den Teil aus einem älteren Projekt in abgespeckter Form übernommen. Da hatte ich zu einem anderen Programm zwei Pipes eingerichtet (schreiben und lesen) und hatte Probleme bekommen weil ich SIGPIPE nicht behandelt hatte.

PS: Ich weiß gar nicht, ob ich das mit dem Socket überhaupt über einen Signal-Handler machen würde.

Da muss ich noch einmal drüber nachdenke. Möglicherweise habe ich da das Beispiel aus dem Buch Linux-UNIX-Programmierung in der eile falsch interpretiert. Das ist die Sendeseite, also der Server mit dem ich mich verbinden will. Da passiert folgendes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    ...
typedef void (*sighandler_t)(int);

static sighandler_t
my_signal( int sig_nr, sighandler_t signalhandler ) {
    struct sigaction neu_sig, alt_sig;

    neu_sig.sa_handler = signalhandler;
    sigemptyset( &neu_sig.sa_mask );
    neu_sig.sa_flags = SA_RESTART;
    if( sigaction( sig_nr, &neu_sig, &alt_sig ) < 0 )
        return SIG_ERR;
    return alt_sig.sa_handler;
}
...

    ...
    my_signal( SIGCHLD, SIG_IGN );  /* SIGCHLD ignorieren */
    ...

Kann es sein, dass da nur SA_RESTART zu einem bestehenden Handler hinzugefügt wird? Dieser Programmteil wird in dem Buchtext nicht explizit kommentiert. Die Kombination SIG_IGN mit SA_RESTART finde ich irgendwie verwirrend.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

Dakuan schrieb:

Kann es sein, dass da nur SA_RESTART zu einem bestehenden Handler hinzugefügt wird? Dieser Programmteil wird in dem Buchtext nicht explizit kommentiert. Die Kombination SIG_IGN mit SA_RESTART finde ich irgendwie verwirrend.

Ich verstehe den Code so, dass SIG_IGN als Signalhandler gesetzt wird und zu dem Zwecke ebenfalls SA_RESTART gesetzt wird, damit ein Syscall nicht durch ein ignoriertes Signal beendet wird. Kenne mich aber nicht gut genug aus. Schau mal https://www.man7.org/linux/man-pages/man7/signal.7.html, Sektion "Interruption of system calls and library functions by signal handlers".

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6532

Ja, auf der Seite war ich gestern auch schon. Aber die vielen Wenn's und Aber's haben mich eigentlich nur verunsichert.

Sovie ich weiß, ist SIG_IGN eine Konstante und keine Adresse. Ich kenne das im Zusammenhang mit fork() so, dass dem Kernel gesagt wird "das Kind interessiert mich nicht weiter, benachrichtige mich nicht". Damit erreicht man, dass der Kernel nicht mehr erwartet, dass der parent am Ende den Status abfragt. Sonst hätte man dann einen Zombie.

Aber ich hatte die Angabe

SIGCHLD      P1990      Ign     Child stopped or terminated

wohl nicht vollständig richtig interpretiert. Jedenfalls kommt mir jetzt der Verdacht, dass "Child stopped" sich auf die Unterbrechung der I/O Aktion bezieht und nicht auf die Beendigung des Prozesses.

Ich werde das wohl noch einmal in meinem anderen Buch "The Linux Programming Interface" nachlesen müssen. Vielleicht wird es da klarer. Interessant ist aber, dass in dem Buch der Autor für die Leseseite ein Beispiel angibt, das ohne SA_RESTART auskommt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
ssize_t
readLine(int fd, void *buffer, size_t n)
{
    ssize_t numRead;                    /* # of bytes fetched by last read() */
    size_t totRead;                     /* Total bytes read so far */
    char *buf;
    char ch;

    ...
    buf = buffer;                       /* No pointer arithmetic on "void *" */
    totRead = 0;
    for (;;) {
        numRead = read(fd, &ch, 1);
        if (numRead == -1) {
            if (errno == EINTR)         /* Interrupted --> restart read() */
                continue;
            else
                return -1;              /* Some other error */
    ...

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

Dakuan schrieb:

Sovie ich weiß, ist SIG_IGN eine Konstante und keine Adresse.

Beides. Aus /usr/include/asm-generic/signal-defs.h:

1
2
3
4
5
6
7
8
9
typedef void __signalfn_t(int);
typedef __signalfn_t *__sighandler_t;

typedef void __restorefn_t(void);
typedef __restorefn_t *__sigrestore_t;

#define SIG_DFL ((__sighandler_t)0)     /* default signal handling */
#define SIG_IGN ((__sighandler_t)1)     /* ignore signal */
#define SIG_ERR ((__sighandler_t)-1)    /* error return from signal */
SIGCHLD      P1990      Ign     Child stopped or terminated

wohl nicht vollständig richtig interpretiert. Jedenfalls kommt mir jetzt der Verdacht, dass "Child stopped" sich auf die Unterbrechung der I/O Aktion bezieht und nicht auf die Beendigung des Prozesses.

SIGCHLD wird ausgelöst, wenn ein Kind gestorben ist. Mit I/O hat das m.E. nix zu tun.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6532

Dann sehe ich in dem Beispiel mit dem Server keinen Sinn. Ich würde darin nur einen Sinn sehen, wenn eine Unterbrechung den Kindprozess stoppt.

Ein Beispiel für SA_RESTART habe ich gefunden. Da wird eine Tastatureingabe durch einen Timer unterbrochen. Das abgefangene Signal ist in dem Fall SIGALRM.

In den Dokus zu den I/O Funktionen steht leider nicht, durch welche Signale diese unterbrochen werden können. Ich könnte natürlich frecherweise mal SIGINT probieren. Aber obwohl ich meinen Wiederholungszähler auf Null gesetzt habe, tritt der Fehler seit zwei Tagen nicht mehr auf. Das ist schlecht zum Testen. Das scheint irgendwie ein Timing Problem zu sein, denn irgendwo hatte ich gelesen, das diese Funktionen beim Warten unterbrochen werden.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13242

Dakuan schrieb:

Dann sehe ich in dem Beispiel mit dem Server keinen Sinn. Ich würde darin nur einen Sinn sehen, wenn eine Unterbrechung den Kindprozess stoppt.

Verstehe gerade nicht, was Du meinst.

In den Dokus zu den I/O Funktionen steht leider nicht, durch welche Signale diese unterbrochen werden können.

Ich hätte jetzt angenommen, dass es da keine Unterscheidung gibt. Signale kommen ja völlig asynchron - unabhängig vom Signaltyp.

Ich könnte natürlich frecherweise mal SIGINT probieren. Aber obwohl ich meinen Wiederholungszähler auf Null gesetzt habe, tritt der Fehler seit zwei Tagen nicht mehr auf. Das ist schlecht zum Testen. Das scheint irgendwie ein Timing Problem zu sein, denn irgendwo hatte ich gelesen, das diese Funktionen beim Warten unterbrochen werden.

Was auch immer "diese Funktionen" sind.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6532

Was auch immer "diese Funktionen" sind.

Ich meinte alle Funktionen die einen EINTR Fehler werfen können. In meinem Fall sind das connect() sowie send() und receive() wenn der Filedescriptor ein Socket ist.

Ich hätte jetzt angenommen, dass es da keine Unterscheidung gibt. Signale kommen ja völlig asynchron - unabhängig vom Signaltyp.

Ja, aber wahrscheinlich ist es nicht immer sinnvoll die Aktion zu wiederholen. Dann gibt es wohl einen anderen Fehlercode und SA_RESTART bleibt ohne Wirkung.

Bei Stackexchange habe ich da eine interessante Diskussion gefunden.

Antworten |