Reputation: 73
I got a problem in C when I try to pause an execution of a system()
call.
A thread calls some application (e.g. some benchmark) repeatedly. Whenever it gets a signal SIGUSR1
, the execution shall be paused and resumed on receiving SIGUSR2
.
The source looks like this:
#include <signal.h>
#include <pthread.h>
void* run_app(sigset_t* signalsBetweenControllerandLoad)
{
/* assign handler */
signal(SIGUSR1, pausesignal_handler)
signal(SIGUSR2, pausesignal_handler)
pthread_sigmask(SIG_UNBLOCK, signalsBetweenControllerandLoad, NULL))
/* call application repeatedly */
while(1) {
system(SOMECOMMAND);
}
return(0);
}
static void pausesignal_handler(int signo)
{
int caughtSignal;
caughtSignal = 0;
/* when SIGUSR1 is received, wait until SIGUSR2 to continue execution */
if (signo == SIGUSR1) {
signal(signo, pausesignal_handler);
while (caughtSignal != SIGUSR2) {
sigwait (signalsBetweenControllerandLoad, &caughtSignal);
}
}
}
When I use some commands (e.g. a for loop as below that makes some computations) instead of system(SOMECOMMAND)
this code works. But a program called by system() is not paused when the handler is active.
int i;
for(i=0;i<10;i++) {
sleep(1);
printf("Just a text");
}
Is there a way to pause the execution of the system() command by using thread signals? And is there even a way to stop the application called by system without needing to wait until the program is finished?
Thank you very much in advance!
Upvotes: 2
Views: 1060
Reputation: 73
I want to present you my (shortened) resulting code after the help of @rici. Again, thank you very much.
Shortly described, the code forks a new process (calling fork
) and executes there a command with exec
. The parent then catches user defined signals SIGNAL_PAUSE
and SIGNAL_RESUME
and forwards signals to the forked child accordingly. Whenever the command finishes - catched by waitpid
- the parent forks again and restarts the load.
This gets repeated until SIGNAL_STOP
is sent where the child gets a SIGINT
and gets cancelled.
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#define SIGNAL_PAUSE (SIGUSR1)
#define SIGNAL_RESUME (SIGUSR2)
#define SIGNAL_STOP (SIGSYS)
/* File scoped functions */
static void pausesignal_handler(int signo);
static void stopsignal_handler(int signo);
void send_signal_to_load_child(int signo);
/*Set file scope variables as handlers can only have signal-number as argument */
sigset_t* signalsBetweenControllerandLoad;
int restart_benchmark;
pid_t child_pid;
void* Load(char* load_arguments[MAX_NR_LOAD_ARGS], sigset_t* signalsToCatch) {
int load_ID;
pid_t p;
signalsBetweenControllerandLoad = signalsToCatch;
/* set signal handlers to catch signals from controller */
signal(SIGNAL_PAUSE, pausesignal_handler)
signal(SIGNAL_RESUME, pausesignal_handler)
signal(SIGNAL_STOP, stopsignal_handler)
pthread_sigmask(SIG_UNBLOCK, signalsBetweenControllerandLoad[load_ID], NULL)
/* Keep restarting benchmark until Stop signal was received */
restart_benchmark[load_ID] = 1;
/* execute benchmark, repeat until stop signal received */
while(restart_benchmark[load_ID])
{
if (child_pid == 0) {
if ((p = fork()) == 0) {
execv(load_arguments[0],load_arguments);
exit(0);
}
}
/* Parent process: Wait until child with benchmark finished and restart it */
if (p>0) {
child_pid = p; /* Make PID available for helper functions */
wait(child_pid); /* Wait until child finished */
child_pid = 0; /* Reset PID when benchmark finished */
}
}
return(0);
}
static void pausesignal_handler(int signo) {
static double elapsedTime;
int caughtSignal;
caughtSignal = 0;
if (signo == SIGNAL_PAUSE) {
send_signal_to_load_child(SIGSTOP);
printf("Load Paused, waiting for resume signal\n");
while (restart_benchmark == 1 && caughtSignal != SIGNAL_RESUME) {
sigwait (signalsBetweenControllerandLoad, &caughtSignal);
if (caughtSignal == SIGNAL_STOP) {
printf("Load caught stop signal when waiting for resume\n");
stopsignal_handler(caughtSignal);
} else if (caughtSignal != SIGNAL_RESUME) {
printf("Load caught signal %d which is not Resume (%d), keep waiting...\n",caughtSignal,SIGNAL_RESUME);
}
}
if (restart_benchmark[load_ID]) {
send_signal_to_load_child(SIGCONT, load_ID);
printf("Load resumed\n");
}
} else {
printf("Load caught unexpected signal %d.\n",signo);
}
/* reassign signals for compatibility reasons */
signal(SIGNAL_PAUSE, pausesignal_handler);
signal(SIGNAL_RESUME, pausesignal_handler);
}
static void stopsignal_handler(int signo) {
double elapsedTime;
signal(SIGNAL_STOP, stopsignal_handler);
if (signo == SIGNAL_STOP) {
restart_benchmark = 0;
send_signal_to_load_child(SIGINT);
printf("Load stopped.\n");
} else {
printf("catched unexpected stop-signal %d\n",signo);
}
}
void send_signal_to_load_child(int signo) {
int dest_pid;
dest_pid = child_pid;
printf("Error sending %d to Child: PID not set.\n",signo);
kill(dest_pid, signo);
}
Upvotes: 0
Reputation: 241671
system
runs the command in a separate process, which doesn't even share address space with the invoking program, never mind signal handlers. The process which called system
is sitting in a waitpid
(or equivalent), so pausing and unpausing it will have little effect (except that if it is paused, it won't return to the loop to call system
again.)
In short, there is no way to use signals sent to the parent process to pause an executable being run in a child, for example with the system()
call or with fork()/exec()
.
If the executable itself implements the feature (which is unlikely, unless you wrote it yourself), you could deliver the signal to that process, not the one which called system
.
Alternatively, you could send the SIGSTOP
signal to the executable's process, which will unconditionally suspend execution. To do that, you'll need to know its pid, which suggests the use of the fork()/exec()/waitpid()
sequence -- a little more work than system()
, but cleaner, safer, and generally more efficient -- and you'll need to deal with a couple of issues:
A process cannot block or trap SIGSTOP
, but it can trap SIGCONT
so the sequence is not necessarily 100% transparent.
Particular care needs to be taken if the stopped process is the terminal's controlling process, since when it is resumed with SIGCONT
it will need to reacquire the terminal. Furthermore, if the application has placed the terminal in a non-standard state -- for example, by using the readline
or curses
libraries which typically put the terminal into raw mode and disable echoing -- then the terminal may be rendered unusable.
Your process will receive a SIGCHLD
signal as a result of the child processed being stopped. So you need to handle that correctly.
Upvotes: 3