Providing/passing argument to signal handler
Asked Answered
M

8

83

Can I provide/pass any arguments to signal handler?

/* Signal handling */
struct sigaction act;
act.sa_handler = signal_handler;
/* some more settings */

Now, handler looks like this:

void signal_handler(int signo) {
    /* some code */
}

If I want to do something special i.e. delete temp files, can I provide those files as an argument to this handler?

Edit 0: Thanks for the answers. We generally avoid/discourage use of global variables. And in this case, If you have a huge program, things can go wrong at different places and you might need to do a lot of cleanup. Why was the API designed this way?

Meson answered 7/8, 2011 at 1:56 Comment(0)
W
74

You can't have data of your own passed to the signal handler as parameters. Instead you'll have to store your parameters in global variables. (And be really, really careful if you ever need to change those data after installing the the signal handler).

Response to edit 0: Historical reasons. Signals are a really old and really low-level design. Basically you're just given the kernel a single address to some machine code and asking it to go to this specific address if such and such happens. We're back in the "portable assembler" mindset here, where the kernels provide a no-frills baseline service, and whatever the user process can reasonably be expected to to for itself, it must do itself.

Also, the usual arguments against global variables don't really apply here. The signal handler itself is a global setting, so there is no relevant possibility of having several different sets of user-specified parameters for it around. (Well, actually it is not entirely global but only thread-global. But the threading API will include some mechanism for thread-local storage, which is just what you need in this case).

Winthorpe answered 7/8, 2011 at 2:0 Comment(7)
Signal handlers are not per-thread. Changing the handler for a signal applies to all threads.Illyrian
Right. But it still makes sense to want the signal handler to react to which thread it is handling for, which is what TLS can achieve.Winthorpe
Actually it's unclear to me whether accessing TLS from a signal handler is valid. With glibc/NPTL it's almost surely not valid in general, because a signal could be delivered immediately after the thread is created but before control is passed to the start function, while the thread's copy of TLS is still being initialized. If you're careful to have signals blocked at this time, it may be okay, but it's a dubious practice still...Illyrian
By the way, even without TLS, there's one way to identify which thread the signal handler is running from in an async-signal-safe way: &errno is unique for each thread! However, I believe this may not be safe in glibc/NPTL for the same reason I just described. If it's not, I'm pretty sure that's a conformance bug, but it's probably a sufficiently rare race to go undetected...Illyrian
@R.. Signal handlers can be on a per thread basis. POSIX provides functionality that you can use to send signals to individual threads within the same process.Keramic
@Curious: Your two sentences are unrelated. Yes you can send a signal to a particular thread but the handler is global state. There is no way to set different handlers on a per-thread basis short of implementing your own dispatcher as the main signal handler, and this is both (1) difficult to coordinate between different libraries that may need to set a per-thread handler, and (2) technically impossible without UB because there's no AS-safe way for a signal handler to determine which thread it's running in.Illyrian
What's all the "Edit 0" stuff in this question and several answers? Was there a user called "Edit 0" whose comments have been deleted, or who has changed their user name? In many places there is half of a useful-sounding dialog marked "Edit 0" and I'd really like to read the other half...Paphlagonia
I
20

A signal handler registration is already a global state equivalent to global variables. So it's no greater offense to use global variables to pass arguments to it. However, it's a huge mistake (almost certainly undefined behavior unless you're an expert!) to do anything from a signal handler anyway. If you instead just block signals and poll for them from your main program loop, you can avoid all these issues.

Illyrian answered 7/8, 2011 at 2:49 Comment(10)
Thanks for the response. I am not entirely sure what you are suggesting here. Can you please elaborate? My concern is, lets say I have 5 functions in a program and something wrong can happen in any of them, how can I avoid having them declare global variables to take care of clean up?Meson
Unless you're an expert with signals, it's very dangerous to do anything from a signal handler. Instead you could just use the signal handler to save a flag that the signal happened, and check for it from various points outside the signal handler, or you could block signals and use sigpending or sigwait to poll for signals.Illyrian
Wow, this is completely new for me. I've seen code doing clean up like temp files deletion or child process clean up before the main program terminate. Also when you say check for flags, how can that work if you get SIGTERM? Doesn't your program terminate right away if you do not handle that in your handler? Why is it dangerous to do things inside a handler?Meson
A signal handler can interrupt the program at any point. This means, unless you've taken care to ensure this can't happen, it could run with various data objects it wants to work with in inconsistent state. For instance the buffer pointers inside a FILE might be only partially updated, or the linked list of open FILEs might have a halfway-inserted or halfway-removed item partly linked to it. Or the internal malloc data structures might have halfway-freed memory referenced... If the signal handler calls functions that depend on that state being consistent, very bad things will happen!Illyrian
POSIX addresses these issues by defining a set of functions which are "async-signal-safe", meaning they can be called from signal handlers without any worries. Any other function, which is "async-signal-unsafe", is not legal to call from a signal handler unless you can be sure that the signal handler could not possibly have "interrupted" any other async-signal-unsafe function.Illyrian
Thanks for the information and explanation. I also read a bit more about signal handlers.Meson
By the way, one classic favorite "safe" way to use signal handlers is to open a pipe to yourself, and store the file descriptors somewhere global. The signal handler can write to the pipe (this is safe) and you can wait for input from the pipe in your main select/poll loop along with other file descriptors you're waiting for input from.Illyrian
Oh, okay. I am not sure if I understand what you want to convey, but thanks. I will look up on web and try to understand it. Appreciate your help.Meson
I just posted a follow-up question: #6971701Meson
@R.. there is signalfd() on Linux which does exactly that. Still a good idea. Few people consider using pipes to communicate within a process. Makes communication between threads much more easier and explicit.Keyes
G
12

Absolutely. You can pass integers and pointers to signal handlers by using sigqueue() instead of the usual kill().

http://man7.org/linux/man-pages/man2/sigqueue.2.html

Gleet answered 12/1, 2016 at 5:58 Comment(0)
G
12

This is a really old question but I think I can show you a nice trick that would have answered your problem. No need to use sigqueue or whatever.

I also dislike the use of globals variables so I had to find a clever way, in my case, to send a void ptr (which you can later cast to whatever suits your need).

Actually you can do this :

signal(SIGWHATEVER, (void (*)(int))sighandler); // Yes it works ! Even with -Wall -Wextra -Werror using gcc

Then your sighandler would look like this :

int sighandler(const int signal, void *ptr) // Actually void can be replaced with anything you want , MAGIC !

You might ask : How to get the *ptr then ?

Here's how : At initialization

signal(SIGWHATEVER, (void (*)(int))sighandler)
sighandler(FAKE_SIGNAL, your_ptr);

In your sighandler func :

int sighandler(const int signal, void *ptr)
{
  static my_struct saved = NULL;

  if (saved == NULL)
     saved = ptr;
  if (signal == SIGNALWHATEVER)
     // DO YOUR STUFF OR FREE YOUR PTR
   return (0);
}
Graphy answered 13/4, 2017 at 19:20 Comment(1)
Nice trick, but unfortunately it's undefined by the standard. See 6.3.2.3(8): if a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.Orten
T
1

Store the names of the files in a global variable and then access it from the handler. The signal handler callback will only be passed one argument: the ID for the actual signal that caused the problem (eg SIGINT, SIGTSTP)

Edit 0: "There must be a rock solid reason for not allowing arguments to the handler." <-- There is an interrupt vector (basically, a set of jump addresses to routines for each possible signal). Given the way that the interrupt is triggered, based on the interrupt vector, a particular function is called. Unfortunately, it's not clear where the memory associated with the variable will be called, and depending on the interrupt that memory may actually be corrupted. There is a way to get around it, but then you can't leverage the existing int 0x80 assembly instruction (which some systems still use)

Terchie answered 7/8, 2011 at 2:2 Comment(0)
C
1

I think you it's better to use SA_SIGINFO in sa_flags so the handler will get void signal_handler(int sig, siginfo_t *info, void *secret) in siginfo_t you can provide your params. Ty:HAPPY code

Caine answered 26/11, 2020 at 9:14 Comment(1)
You setup some pointer or other data in a sigval union then pass this to sigqueue() to enqueue a realtime (user-defined) signal, then you get back whatever you specified in the signal handler via the sigval union in the siginfo_t parameter. You cannot specify the user-defined data for a none realtime signal though. Moreover, using sigqueue() has already been mentioned earlier...Ringworm
Q
0

Despite the very old question the problem still exists today. Basically to close temporary files, terminate threads correctly, etc. for example, this logic could be used:

volatile sig_atomic_t sig_received = 0;

void sigterm_handler(int signum)
{
    printf("SIGTERM. PID: %d\n", getpid());
    sig_received = 1;
}
    void sigint_handler(int signum)
{
    fprintf(stderr, "SIGINT. PID: %d\n", getpid());
}

...

int main()
{
    struct sigaction action;
    action.sa_handler = sigterm_handler;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    sigaction(SIGTERM, &action, NULL);

    action.sa_handler = sigint_handler;
    sigaction(SIGINT, &action, NULL);

    pthread_t writer_thread, reader_thread;
    struct master_argv writer_args, reader_args;

    buffer_init(&(writer_args.buffer));
    buffer_init(&(reader_args.buffer));

    writer_args.pipename = PIPE_CAPOSC;
    reader_args.pipename = PIPE_CAPOLET;

    if (pthread_create(&writer_thread, NULL, master, (void *)&writer_args) != 0)
    {
        exit(1);
    }

    if (pthread_create(&reader_thread, NULL, master, (void *)&reader_args) != 0)
    {
        exit(1);
    }

    while (!sig_received)
    {
        sleep(1);
    }

    pthread_join(writer_thread, NULL);
    pthread_join(reader_thread, NULL);

    buffer_destroy(&(writer_args.buffer));
    buffer_destroy(&(reader_args.buffer));

    return 0;
}

Basically, within the signal manager, a sig_atomic_t flag is set which guarantees atomic access to this variable and volatile to signal to the compiler that this variable must not be optimized since it could undergo unexpected changes such as the modification by a signal .

Using this flag you can handle the closure, in the example of a series of threads, in a safe way by not using global variables

Quadrature answered 17/5, 2023 at 11:10 Comment(0)
S
-2

You can use a signal handler which is a method of a class. Then that handler can access member data from that class. I'm not entirely sure what Python does under the covers here around the C signal() call, but it must be re-scoping data?

I was amazed that this works, but it does. Run this and then kill the process from another terminal.

import os, signal, time

class someclass:
    def __init__(self):
        self.myvalue = "something initialized not globally defined"
        signal.signal(signal.SIGTERM, self.myHandler)
    def myHandler(self, s, f):
        # WTF u can do this?
        print "HEY I CAUGHT IT, AND CHECK THIS OUT", self.myvalue


print "Making an object"
a = someclass()

while 1:
    print "sleeping.  Kill me now."
    time.sleep(60)
Stylistic answered 25/4, 2012 at 15:43 Comment(1)
The question was about C ... Though Python most likely remembers the signal somewhere to process it later as soon as possible (it's dangerous to do things inside of signal handlers). Possibly using a global variable.Keyes

© 2022 - 2024 — McMap. All rights reserved.