1

I have a strange problem. I want to write a simple C programm that outputs "Daytime" repeatedly unless I press Ctrl+C (a SIGINT signal) which makes it to switch to "Nighttime" and vice versa. This part of the programm works. I also want to implement the termination of the programm, which would work over the SIGUSR1 signal. Yet when I type kill -SIGUSR1 <pid> or kill -10 <pid> it doesn't work. Why?

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>


volatile int termination;
volatile int state;

void handler(int num) {
    if (state == 0) {
        state = 1;
    } else {
        state = 0;
    }
}

void terminator(int num) {
    // kill -SIGUSR1 3289
    // kill -USR1 2040
    // kill -USR 2040
    // kill -10 3289
    printf("I work!\n");
    termination = 0;
}

int main(void) {
    struct sigaction act_int, act_usr;

    act_int.sa_handler = handler;
    act_usr.sa_handler = terminator;

    sigaction(SIGUSR1, &act_usr, NULL);
    sigaction(SIGINT, &act_int, NULL);

    termination = 1; 
    state = 1;

    printf("My PID is: %d\n", getpid());

    while (termination == 1) {
        sleep(5);
        if (state == 0) {
            printf("Daytime\n");
        } else {
            printf("Nighttime\n");
        }
    }
    printf("Thank you for using my function! :)\n");
    return 0;
}

Kind regards

EDIT (My modified new code):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>

volatile int termination = 1;
volatile int state = 1;

void handler(int num) {
    if (state == 0) {
        state = 1;
    } else {
        state = 0;
    }
}

void terminator(int num) {
    // kill -SIGUSR1 20560
    // kill -10 20560
    write(1, "Terminated\n", 10);
    termination = 0;
}

int main(void) {
    struct sigaction act_int, act_usr;

    memset(&act_int, 0, sizeof act_int);
    memset(&act_usr, 0, sizeof act_usr);

    act_int.sa_handler = handler;
    act_usr.sa_handler = terminator;

    sigaction(SIGINT, &act_int, NULL);
    sigaction(SIGUSR1, &act_usr, NULL);

    printf("My PID is: %d\n", getpid());

    while (termination == 1) {
        if (state == 0) {
            printf("Daytime\n");
        } else {
            printf("Nighttime\n");
        }
        sleep(5);
    }
    printf("Thank you for using my function! :)\n");
    return 0;
}

EDIT: I ran my program with strace and redirected the output to a file, here are the contents:

execve("./program.x", ["./program.x"], 0x7ffe2fc5b530 /* 58 vars */) = 0
brk(NULL)                               = 0x55f3ca56b000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd50daca00) = -1 EINVAL (Das Argument ist ungültig)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f586663f000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (Datei oder Verzeichnis nicht gefunden)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=114855, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 114855, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5866622000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0i8\235HZ\227\223\333\350s\360\352,\223\340."..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=2216304, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2260560, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f58663fa000
mmap(0x7f5866422000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7f5866422000
mmap(0x7f58665b7000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7f58665b7000
mmap(0x7f586660f000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x214000) = 0x7f586660f000
mmap(0x7f5866615000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f5866615000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f58663f7000
arch_prctl(ARCH_SET_FS, 0x7f58663f7740) = 0
set_tid_address(0x7f58663f7a10)         = 20560
set_robust_list(0x7f58663f7a20, 24)     = 0
rseq(0x7f58663f80e0, 0x20, 0, 0x53053053) = 0
mprotect(0x7f586660f000, 16384, PROT_READ) = 0
mprotect(0x55f3c96a2000, 4096, PROT_READ) = 0
mprotect(0x7f5866679000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7f5866622000, 114855)          = 0
rt_sigaction(SIGINT, {sa_handler=0x55f3c96a0229, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f586643c520}, NULL, 8) = 0
rt_sigaction(SIGUSR1, {sa_handler=0x55f3c96a0257, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f586643c520}, NULL, 8) = 0
getpid()                                = 20560
newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}, AT_EMPTY_PATH) = 0
getrandom("\x62\x94\x42\xcc\xdf\x17\xbc\x20", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x55f3ca56b000
brk(0x55f3ca58c000)                     = 0x55f3ca58c000
write(1, "My PID is: 20560\n", 17)      = 17
write(1, "Nighttime\n", 10)             = 10
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=5, tv_nsec=0}, 0x7ffd50dac930) = 0
write(1, "Nighttime\n", 10)             = 10
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=5, tv_nsec=0}, 0x7ffd50dac930) = 0
write(1, "Nighttime\n", 10)             = 10
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=5, tv_nsec=0}, 0x7ffd50dac930) = 0
write(1, "Nighttime\n", 10)             = 10
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=5, tv_nsec=0}, {tv_sec=4, tv_nsec=592627880}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
rt_sigreturn({mask=[]})                 = -1 EINTR (Unterbrechung während des Betriebssystemaufrufs)
write(1, "Daytime\n", 8)                = 8
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=5, tv_nsec=0}, {tv_sec=3, tv_nsec=876879211}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
rt_sigreturn({mask=[]})                 = -1 EINTR (Unterbrechung während des Betriebssystemaufrufs)
write(1, "Nighttime\n", 10)             = 10
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=5, tv_nsec=0}, {tv_sec=4, tv_nsec=383598465}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
rt_sigreturn({mask=[]})                 = -1 EINTR (Unterbrechung während des Betriebssystemaufrufs)
write(1, "Daytime\n", 8)                = 8
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=5, tv_nsec=0}, {tv_sec=4, tv_nsec=611480158}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
rt_sigreturn({mask=[]})                 = -1 EINTR (Unterbrechung während des Betriebssystemaufrufs)
write(1, "Nighttime\n", 10)             = 10
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=5, tv_nsec=0}, 0x7ffd50dac930) = 0
write(1, "Nighttime\n", 10)             = 10
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=5, tv_nsec=0}, 0x7ffd50dac930) = 0
write(1, "Nighttime\n", 10)             = 10
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=5, tv_nsec=0}, 0x7ffd50dac930) = 0
write(1, "Nighttime\n", 10)             = 10
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=5, tv_nsec=0}, 0x7ffd50dac930) = 0
write(1, "Nighttime\n", 10)             = 10
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=5, tv_nsec=0}, {tv_sec=0, tv_nsec=558127083}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGHUP {si_signo=SIGHUP, si_code=SI_USER, si_pid=20542, si_uid=1000} ---
+++ killed by SIGHUP +++

My platform information (obtained with uname -a):

Linux Luxdragon 5.15.0-76-generic #83-Ubuntu SMP Thu Jun 15 19:16:32 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

So basically Linux Mint 21.1. I tested the code in VSCodium, in the Linux terminal and on Onlinegdb.

EDIT: If I type kill -SIGUSR1 in a separate terminal, then my program terminates as it should. Why can't I terminate it in the same terminal, where the program is run? Why does Ctrl+C then get processed correctly?

  • "It doesn't work"? What **does** it do? "It doesn't work" doesn't describe anything. – Andrew Henle Jul 02 '23 at 20:15
  • That exact code works perfectly for me (Ubuntu 20.04). Are you sure you're using the correct PID? – paxdiablo Jul 02 '23 at 20:20
  • 2
    You probably want to `memset()` your structures to zero, unless you're happy to have random junk in them. – pmacfarlane Jul 02 '23 at 20:22
  • 1
    @pmacfarlane, that's quite probably the issue here, random junk would affect interrupt masking, etc. May want to make it an answer. – paxdiablo Jul 02 '23 at 20:25
  • Well by it doesn't do anything, I mean that it normally prints Nightime and Daytime and also successfully toggles between the two if I enter Ctrl+C, but if I enter kill -SIGUSR1 where is the task's id that gets printed by the getpid(), the program doesn't print "Thank you for using my function! :)\n" but instead continues –  Jul 02 '23 at 20:37
  • I set termination to 1 and state to 1 at the beginning, it didn't fix the issue. –  Jul 02 '23 at 20:40
  • 1
    You can't use `printf()` in a signal handler... See https://man7.org/linux/man-pages/man7/signal-safety.7.html – Shawn Jul 02 '23 at 20:51
  • Maybe run it with `strace` to see what happens when you send `SIGUSR1`. – pmacfarlane Jul 02 '23 at 20:54
  • I replaced printf() with write(1, "Terminated\n", 10); it doesn't fix the issue –  Jul 02 '23 at 20:57
  • I ran strace and edited my post with the ouput of strace –  Jul 02 '23 at 21:03
  • So which signal are you sending to end it? SIGUSR1 (10) or SIGHUP (1)? It looks like SIGHUP. – pmacfarlane Jul 02 '23 at 21:06
  • I sent a few SIGUSR1s to it and then killed the program by just closing the terminal –  Jul 02 '23 at 21:06
  • What is the return value of `sigaction(SIGUSR1)`? If it's `-1` then what is the value of `errno`? – Jim Morrison Jul 02 '23 at 21:12
  • I wrote int ret = sigaction(SIGUSR1, &act_usr, NULL); and then printed ret. The return value is 0 –  Jul 02 '23 at 21:14
  • Can you edit your question (either tags or text or both) to indicate which OS, version and platform you are running on? Your code works fine for me on Ubuntu 22.04. – pmacfarlane Jul 02 '23 at 21:27
  • Yeah I'm running on Linux Mint 21.1 (so Ubuntu 22.04) –  Jul 02 '23 at 21:34
  • Try this. Run this in one terminal: `strace sleep 1000`. In another terminal, find the pid of the sleep with something like `ps ax` and send `kill -SIGUSR1` to that. Does the sleep get the signal? – pmacfarlane Jul 02 '23 at 21:40
  • Yes it does, and it terminates the sleep –  Jul 02 '23 at 21:43
  • Okay here's the oddity: I can terminate the programm with kill -SIGUSR1 but only if I type that in a different terminal. Why can't I terminate my program in the same terminal as it is being run? Why does Ctrl+C then successfully toggle Nighttime and Daytime? –  Jul 02 '23 at 21:46
  • 3
    Because the terminal where it is run is running your program. The shell is not accepting input. But the shell will intercept ^-C and send a signal to your program. – pmacfarlane Jul 02 '23 at 21:48
  • Ah, I didn't know that. Thanks for helping me out! –  Jul 02 '23 at 21:51
  • 1
    You could type `kill -SIGUSR1` in the same terminal but only if you put your process in the background (use `./prog &` or press Ctrl+Z while your program is running and then execute `bg`) – Jim Morrison Jul 03 '23 at 09:57

2 Answers2

1

Main problem

As deduced from comments, the questioner was typing the kill -SIGUSR1 command into the same terminal / shell that was running their program. In that shell, input is fed into the running program, not into the shell. The ^-C were intercepted by the shell and converted into signals, but normal text typed was just going nowhere.

Other problems

struct sigaction contains more than just the sa_handler field. It contains other things too - e.g. on my Ubuntu 22.04 box, the manpage shows it as:

struct sigaction {
    void     (*sa_handler)(int);
    void     (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;
    int        sa_flags;
    void     (*sa_restorer)(void);
};

Your struct sigaction variables are local variables, and hence have undefined values unless they are somehow initialised, or have all their fields set. As you provide no initialisation, and only set one field in each of them, then you will invoke undefined behaviour when sigaction() accesses the uninitialised structure fields.

The simplest solution might be to use memset() to set them to zero, before you start setting the fields you care about. e.g.

    struct sigaction act_int, act_usr;

    memset(&act_int, 0, sizeof act_int);
    memset(&act_usr, 0, sizeof act_usr);

    act_int.sa_handler = handler;
    act_usr.sa_handler = terminator;
pmacfarlane
  • 3,057
  • 1
  • 7
  • 24
  • Thank you for your answer. I added the memset lines to my code, but sadly this did not fix the issue. Nighttime and Daytime are successfully displayed and I can toggle between the two, but when I enter kill -SIGUSR1 pid or kill -10 pid, it does not terminate the program –  Jul 02 '23 at 20:46
0

You have this local declaration in your main() routine:

    struct sigaction act_int, act_usr;

then you initialize

    act_int.sa_handler = handler;
    act_usr.sa_handler = terminator;

but don't initialize the other fields (which should be zeroed) leading to Undefined Behaviour.

Just initialize it in this alternate form:

    struct sigaction act_int = {
        .sa_handler = handler,
    }, act_usr = {
        .sa_handler = terminator,
    };

This initialization will zero all the other fields, as you are initializing the whole structure in this way.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31