I came up with this solution. Let me know if it is adequate or if its breaking any rules. I found out that there is a std::signal library which is probably an abstraction of signal.h. I dont understand these lines very well :
|
If the user defined function returns when handling SIGFPE, SIGILL, SIGSEGV or any other implementation-defined signal specifying a computational exception, the behavior is undefined.
|
I read that the signal handler function will return to the point in the program of its invocation such that if a SIGSEGV was signaled by accessing unallocated memory for example, there is a potential ( if the error was in a do .. while loop ) to loop over the part of the code that caused the SIGSEGV, repeating the error over and over again. This was my initial problem which was solved by the longjmp call which put the next point of execution outside of the do .. while loop.
Q1. Is this what the line above means by "undefined behavior". If so I fixed it with a longjmp. Or should I be expecting some other undesired and unexpected consequences later?
The code below seems to make it "bulletproof" for the signals that I was handling. That is to say, the signal handler is called once only and program cleanup ( closing of the serial port and turning off the robot ) happens only once, regardless of successive signals.
What of concurrent signals? By inserting a delay in the signal handler and pressing Ctrl C, a SIGINT is generated resulting in a second call to the signal handler while the first one hasn't finnished yet. Since I am using parallel threads, quite conceivably, another thread might issue a signal and call the signal handler synchronously.
Q2. How can I block any other signal while I am processing the first? Something like :
1 2 3 4 5 6 7 8 9
|
int main() {
signal_raised;
block_other_signals; // or ignore since we are terminating anyway
call_signal_function;
return
}
|
It seems quite possible also to "skip" past the error with longjmp by setjmp at the bottom of the while loop just before its condition (maintaining the program_running=true) such that we avoid the part of the code that caused the error this time only ( in the case that for example, the error was caused by the user not putting in two words when prompted, causing for example stoi(tokens[1]) to fail. And a separate handler can be called for this specific type of error as well as a setjmp different from all the other signals. ) It would seem that doing this would allow the user to be prompted a second time for correct input. (or a third or a fourth until correct number of words is recieved). The code demonstrates this.
Here is the working code :
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
|
#include <cstdlib>
#include <csignal>
#include <iostream>
#include <atomic>
#include <setjmp.h>
#include <unistd.h>
volatile std::sig_atomic_t gSignalStatus;
volatile std::atomic<bool> program_running;
jmp_buf return_to_main, return_to_bottom;
void sigIntHandler(int signal)
{
std::cout << "Turning off robot" <<std::endl; //this calls a toggle function. A second call will turn on the robot (not desired)
program_running=false;
usleep(5000000); //Delay so we can send a SIGINT with Ctrl C
std::cout << "Signal was " << signal << std::endl;
longjmp (return_to_main, 1);
}
void sigsegvhandler(int signal){
std::cout << "Try again " << std::endl;
longjmp (return_to_bottom, 1);
}
class Biscuit {
public:
//It makes sense for the object that opens the serial port to close it on destruction
Biscuit(): status(1) { std::cout << "Biscuit constructor (opens the serial port)" << std::endl; };
~Biscuit() { std::cout << "Biscuit destructor (closes the serial port)" << std::endl ; }
int status;
};
int main(int argc, char** argv) {
// Install a signal handler
std::signal(SIGSEGV, &sigsegvhandler);
std::signal(SIGINT, &sigIntHandler);
std::signal(SIGABRT, &sigIntHandler);
std::signal(SIGFPE, &sigIntHandler);
std::signal(SIGILL, &sigIntHandler);
std::signal(SIGTERM, &sigIntHandler);
Biscuit b;
int counter=0;
program_running=true;
if (setjmp(return_to_main)) {
program_running=false;
} else {
do {
std::cout << "Counter is " << counter << std::endl;
usleep(1000000);
if (counter > 5) {
std::raise(SIGSEGV);
}
//program would resume here normally after returning from the signal handler,
//incrementing counter and possibly running into another signal and call to the signal handler
//that performs the cleanup procedure a second time
//the longjmp in signal handler ensures we go back to the point of setjmp
counter++;
if (counter > 10) {
std::raise(SIGILL);
}
if (setjmp(return_to_bottom)){
std::cout << "Bottom from sigsegv " << std::endl;
} else
std::cout << "Normal Bottom " << std::endl;
} while (program_running);
}
return 0;
}
|
And here is the output:
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
|
Biscuit constructor (opens the serial port)
Counter is 0
Normal Bottom
Counter is 1
Normal Bottom
Counter is 2
Normal Bottom
Counter is 3
Normal Bottom
Counter is 4
Normal Bottom
Counter is 5
Normal Bottom
Counter is 6 //SIGSEGV raised here
Try again //sigsegv handler running
Bottom from sigsegv //sigsegv handler jumps to previous bottom of do .. while
Counter is 6 //it seems that it has forgotton the previous run, counter is still at 6 "turning back the time"
Normal Bottom //just recovered from a sigsegv as if nothing happened
Counter is 7
Normal Bottom
Counter is 8
Normal Bottom
Counter is 9
Normal Bottom
Counter is 10
Turning off robot
Signal was 4
Biscuit destructor (closes the serial port)
|
Thanks,
Chris