简体   繁体   中英

Interrupt and, if necessary, terminate a program at regular intervals in c++

I need your help. Program A executes program B with fork(). Every 5 seconds the process belonging to program B is interrupted. If the user enters any key within a certain time, the process is continued and interrupted again after the same time interval. If no key is entered, both program A and program B are terminated prematurely. I have tried the following code, but it does not work. Any suggestions/tips that will help me?

#include <iostream>
#include <chrono>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

using namespace std;
using namespace chrono;

int pid;

void signal_handler(int signum) {
    cout << "Programm B is interrupted. Please enter any key within 5 or the programm will be terminated" << endl;
    kill(pid,SIGSTOP);
    alarm(5);
    pause();
    alarm(5);

}
int main(int argc, char* argv[]) {

  //Usage
  if(string(argv[1]) == "h" || string(argv[1]) == "help"){
    cout << "usage" << endl;
    return 0;
  }

  signal(SIGALRM, signal_handler);
  pid = fork();

  if (pid == 0) {

    cout << "Name of programm B: " << argv[1] << endl;
    cout << "PID of programm B: " << getpid() << endl;
    execvp(argv[1], &argv[1]);

  } else if (pid > 0) {

    cout << "PID of programm A: " << getpid() << endl;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    waitpid(pid, nullptr, 0);
    high_resolution_clock::time_point t2 = high_resolution_clock::now();

  
    auto duration = duration_cast<milliseconds>(t2 - t1).count();
    cout << "Computing time: " << duration << "ms" << endl;
  } else {

    cerr << "error << endl;
    return 1;

}
  return 0;
}

Any help or sulution. I am a beginner in c++ btw.

Signals can get tricky and there are lots of issues with your approach.

You should:

  • kick off the timer ( alarm(5) ) in main
  • do the sighandler registration and timer kick-off after you've spawned the child (or you somewhat risk running the signal handler in the child in between fork and execvp )
  • use sigaction rather than signal to register the signal, as the former has clear portable semantics unlike the latter
  • loop on EINTR around waitpid (as signal interruptions will cause waitpid to fail with EINTR )

As for the handler, it'll need to

  • use only async-signal-safe functions
  • register another alarm() around read
  • unblock SIGALRM for the alarm around read but not before you somehow mark yourself as being in your SIGALRM signal handler already so the potential recursive entry of the handler can do a different thing (kill the child and exit)

(For the last point, you could do without signal-unblocking if you register the handler with .sa_flags = SA_NODEFER , but that has the downside of opening up your application to stack-overflow caused by many externally sent (via kill) SIGALRMs. If you wanted to handle externally sent SIGALRM s precisely, you could register the handler with .sa_flags=SA_SIGINFO and use info->si_code to differentiate between user-sends and alarm-sends of SIGALRM , presumably aborting on externally-sent ones)

It could look something like this (based on your code):

#include <iostream>
#include <chrono>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>

//AS-safe raw io helper functions
ssize_t             /* Write "n" bytes to a descriptor  */
writen(int fd, const char *ptr, size_t n)
{
    size_t      nleft;
    ssize_t     nwritten;

    nleft = n;
    while (nleft > 0) {
        if ((nwritten = write(fd, ptr, nleft)) < 0) {
            if (nleft == n)
                return(-1); /* error, return -1 */
            else
                break;      /* error, return amount written so far */
        } else if (nwritten == 0) {
            break;
        }
        nleft -= nwritten;
        ptr   += nwritten;
    }
    return(n - nleft);      /* return >= 0 */
}
ssize_t writes(int fd, char const *str0) { return writen(fd,str0,strlen(str0)); }
ssize_t writes2(char const *str0) { return writes(2,str0); }

//AS-safe sigprockmask helpers (they're in libc too, but not specified as AS-safe)
int sigrelse(int sig){
    sigset_t set; sigemptyset(&set); sigaddset(&set,sig);
    return sigprocmask(SIG_UNBLOCK,&set,0);
}
int sighold(int sig){
    sigset_t set; sigemptyset(&set); sigaddset(&set,sig);
    return sigprocmask(SIG_BLOCK,&set,0);
}

#define INTERRUPT_TIME 5

using namespace std;
using namespace chrono;

int pid;
volatile sig_atomic_t recursing_handler_eh; //to differentiate recursive executions of signal_handler


void signal_handler(int signum) {
    char ch;
    if(!recursing_handler_eh){
        kill(pid,SIGSTOP);

        writes2("Programm B is interrupted. Please type enter within 5 seconds or the programm will be terminated\n");

        alarm(5);
        recursing_handler_eh = 1;
        sigrelse(SIGALRM);

        if (1!=read(0,&ch,1)) signal_handler(signum);
        alarm(0);
        sighold(SIGALRM);

        writes2("Continuing");
        kill(pid,SIGCONT);
        recursing_handler_eh=0;
        alarm(INTERRUPT_TIME);
        return;
    }
    kill(pid,SIGTERM);
    _exit(1);

}
int main(int argc, char* argv[]) {

  //Usage
  if(string(argv[1]) == "h" || string(argv[1]) == "help"){
    cout << "usage" << endl;
    return 0;
  }

  pid = fork();

  if (pid == 0) {
    cout << "Name of programm B: " << argv[1] << endl;
    cout << "PID of programm B: " << getpid() << endl;
    execvp(argv[1], &argv[1]);

  } else if (pid < 0) { cerr << "error" <<endl; return 1; }

  struct sigaction sa; sa.sa_handler = signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags=0; sigaction(SIGALRM, &sa,0);
  //signal(SIGALRM, signal_handler);
  alarm(INTERRUPT_TIME);

  cout << "PID of programm A: " << getpid() << endl;

  high_resolution_clock::time_point t1 = high_resolution_clock::now();
  int r;
  do r = waitpid(pid, nullptr, 0); while(r==-1 && errno==EINTR);
  high_resolution_clock::time_point t2 = high_resolution_clock::now();


  auto duration = duration_cast<milliseconds>(t2 - t1).count();
  cout << "Computing time: " << duration << "ms" << endl;
  return 0;
}

Not that the above will wait only for an enter key. To wait for any key, you'll need to put your terminal in raw/cbreak mode and restore the previous settings on exit (ideally on signal deaths too).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM