-1

I have this simple code that loops the word "SIGNALS ARE COOL" I'm trying to make it take signals like (SIGFPE. SIGABRT, SIGINT, SIGSEGV.) and show the signal type and the time I made this code that takes "SIGINT" signal how do I add more signals and how to control what my program show when the signals are triggered by the user.

// ConsoleApplication3.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <csignal>

using namespace std;

void signalHandler(int signum) {
    cout << "Interrupt signal (" << signum << ") received.\n";

    // cleanup and close up stuff here  
    // terminate program  

    exit(signum);
}

int main() {
    // register signal SIGINT and signal handler  
    signal(SIGINT, signalHandler);
    

    while (1) {
        cout << "SIGNALS ARE COOL" << endl;
        
    }
    

    return 0;
}
Hade Zedan
  • 21
  • 2
  • 2
    A signal handler is very constrained what it can/cannot do. For instance, it cannot do most I/O (it can do `read` or `write`) or memory allocation/free. See `man 2 sigaction` for the list of what can be done. – Eljay Jun 23 '21 at 13:19

2 Answers2

2

I see that this looks like an assignment; so what I'm saying may not be relevant to you (but might be to someone someday).

--EDIT--

I see you've also got stdafx.h, which I think is a Visual Studio Windows thing, and here I am suggesting a POSIX solution (not pure C++). I didn't read carefully enough, and that invalidates my whole answer (I think). You probably can't use my suggestion, and for that I'm sorry.

However, I'm going to leave it here in case someone one day finds this and needs to work with signals in a Unix system.

--

I've found that it's often a lot more practical to avoid signal handling functions like this altogether, and take signals on your own terms. As noted by others, there's a lot of rules about what you can and can't do within a signal handler, because they can be invoked at any time, in any thread, unless you take extra precautions. I've seen this result in a lot of messy code, things like 'have a global bool got_signal that gets checked by things all over the application to know if they're supposed to shut down'. There's obviously nice ways to do signal handling, but at this point I try to avoid it altogether in favor of other options.

The functions pthread_sigmask and sigwait can be used to invert control here and allow you to accept signals within the defined flow of program execution where you want it, and then you don't need to worry about taking invalid actions when you handle them. Using pthread_sigmask you can tell the OS not to interrupt your program to deliver signals and instead queue them up, and then sigwait can be used to handle them at an appropriate time. You can't do this with all signals (some things like kill -9 and a SEGFAULT can't/shouldn't be ignored), but it works well for most of them.

Using an approach like this, it's really easy to interact with signals in a larger application too. You can block signals at the start of main, and that will propagate to all children threads, and then you can designate one specific child thread to just wait for signals an pass events into the rest of the application in whatever method is appropriate for the framework of your application.

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

#include <initializer_list>
#include <functional>
#include <algorithm>
#include <iostream>

sigset_t make_sigset(std::initializer_list<int32_t> signals)
{
    sigset_t set;
    const int32_t result = sigemptyset(&set);

    std::for_each(signals.begin(), signals.end(), std::bind(&sigaddset, &set, std::placeholders::_1));

    return set;
}

int main()
{
    const auto signal_list = make_sigset({SIGTERM, SIGSEGV, SIGINT, SIGABRT});

    pthread_sigmask(SIG_BLOCK, &signal_list, nullptr);

    int32_t last_signal;
    do
    {
        sigwait(&signal_list, &last_signal);
        std::cout << "Got signal " << last_signal << std::endl;

        // Exit on sigint so ctrl+c still works
    } while (last_signal != SIGINT);

    return 0;
}
Ipiano
  • 234
  • 1
  • 8
  • thanks for your comment but can you help me when I tried your code (sigset_t, sigemptyset int32_T, ) is undefined – Hade Zedan Jun 23 '21 at 16:49
  • See my edit at the top. It's probably because you're using windows and I just assumed you had a Unix system; this method only works on Unix or other systems with the full POSIX signalling support. – Ipiano Jun 24 '21 at 13:20
0

As already mentioned by @Eljay in the comments, you have to be careful with the things you do in a signal handler.

I'd also suggest not using namespace std, but that's a story for another time link.

I'd recommend you this page which explains a lot about what signals can and cannot do, according to the c++ standard. Now what they actually do in your compiler (which I assume is MSVC) may be different.

Some of the important bits, as already mentioned, you shouldn't do I/O, you shouldn't throw, etc...

To answer your question, you were on the right track, adding other signals can be done via:

// catch SIGTERM
std::signal(SIGTERM, signalHandler);
std::signal(SIGSEGV, signalHandler);
std::signal(SIGINT, signalHandler);
std::signal(SIGABRT, signalHandler);
// insert others

Then, what I'd suggest is storing the value of your signal into some atomic variable, like: gSignalThatStoppedMe.

std::atomic<int> gSignalThatStoppedMe = -1;

// I also added 'extern "C"' because the standard says so
extern "C" void signalHandler(int signum) {
  gSignalThatStoppedMe.store(signum);
}

Then, your while loop would check for != -1, or pick another value for this, I've not checked if some implementations use -1 as a valid value for signals

// ...
while(gSignalThatStoppedMe.load() == -1)
{
  // your old code
}

Now, do a switch of sorts, with the values inside and output the signal that stopped it, something like:

switch(gSignalThatStoppedMe.load())
{
  case SIGINT:
    std::puts("It was SIGINT");
    break;
  case SIGTERM:
    std::puts("It was SIGTERM");
    break;
  default:
    break;
}

I think this has less undefined behavior, which is always a good thing.

EDIT: here's a compiler explorer link

The output with CTRL-C:

SIGNALS ARE COOL
SIGNALS ARE COOL
SIGNALS ARE COOL
SIGNALS ARE COOL
SIGNALS ARE COOL
SIGNALS ARE COOL
SIGNALS ARE COOL
SIGNALS ARE COOL
SIGNALS ARE COOL
Interrupt signal SIGINT (2) received.
ben10
  • 221
  • 1
  • 8
  • is there another way other than {getSignalThatStoppedme}? I tried it and it didn't work I don't know if I did something wrong but is there a way to catch a signal in a more simple way if not can you help me put what you did in my code please if not I understand anyway thank you for your help. – Hade Zedan Jun 23 '21 at 16:25
  • @HadeZedan added a compiler explorer link – ben10 Jun 24 '21 at 08:37
  • Thank you so much but i have a question, so if I press control+c it will show that the SIGINT signal but how do I catch the other 3 signals (SIGFPE. SIGABRT, SIGSEGV) – Hade Zedan Jun 24 '21 at 11:32
  • at lines 15,16 in the link I sent, you can find calls to the `std::signal` function. You would need to add nore calls to that with the signals you mentioned. Something like `std::signal(SIGFPE, signalHandler); std::signal(SIGABRT, signalHandler); std::signal(SIGABRT, SIGSEGV);`. On Windows I'm not sure how to do the others, but on Linux you can use `kill - ` – ben10 Jun 24 '21 at 12:21