How can I enable ctrl-c / ctrl+break after calling

2020-07-23 04:09发布

I have written a program that invokes a system command from inside:

#include <stdlib.h>

int main(void)
{
    while(1)
    {
        system("ls 2>&1 1>/dev/null"); // comment this line out to enable ctrl+break
    }

    return 0;
}

However, when it is running, CTRL+C and CTRL+BREAK no longer work and appear to be ignored.

I am trying to write a program that performs some operations in the background involving the shell, but I also want to be able to break out of the program when the user wants to break.

Is there a way to make it work the way I want? Should I change the architecture to perform some kind of fork / exec?

3条回答
混吃等死
2楼-- · 2020-07-23 04:29

From the POSIX specification for system():

The system() function ignores the SIGINT and SIGQUIT signals, and blocks the SIGCHLD signal, while waiting for the command to terminate. If this might cause the application to miss a signal that would have killed it, then the application should examine the return value from system() and take whatever action is appropriate to the application if the command terminated due to receipt of a signal.

So, in order to respond properly to signals, you need to examine the return value of system().

system() returns the termination status of the command language interpreter in the format specified by waitpid()

And the docs of waitpid() refer to the docs for wait(), which instruct you to use the following macros to find out why a process exited:

  • WIFEXITED(stat_val)
    Evaluates to a non-zero value if status was returned for a child process that terminated normally.
  • WEXITSTATUS(stat_val)
    If the value of WIFEXITED(stat_val) is non-zero, this macro evaluates to the low-order 8 bits of the status argument that the child process passed to _exit() or exit(), or the value the child process returned from main().
  • WIFSIGNALED(stat_val)
    Evaluates to non-zero value if status was returned for a child process that terminated due to the receipt of a signal that was not caught (see ).
  • WTERMSIG(stat_val)
    If the value of WIFSIGNALED(stat_val) is non-zero, this macro evaluates to the number of the signal that caused the termination of the child process.
  • WIFSTOPPED(stat_val)
    Evaluates to a non-zero value if status was returned for a child process that is currently stopped.
  • WSTOPSIG(stat_val)
    If the value of WIFSTOPPED(stat_val) is non-zero, this macro evaluates to the number of the signal that caused the child process to stop.
  • WIFCONTINUED(stat_val)
    Evaluates to a non-zero value if status was returned for a child process that has continued from a job control stop.

Here is an example of how you would use this information, without having to fork a separate process. Note that you won't actually receive the signal in the parent process, but you can determine the signal sent to the child process:

#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    while(1)
    {
        int result = system("ls 2>&1 1>/dev/null");
        if (WIFEXITED(result)) {
          printf("Exited normally with status %d\n", WEXITSTATUS(result));
        } else if (WIFSIGNALED(result)) {
          printf("Exited with signal %d\n", WTERMSIG(result));
          exit(1);
        } else {
          printf("Not sure how we exited.\n");
        }
    }

    return 0;
}

And if you run it, you get:

$ ./sys
Exited normally with status 0
Exited normally with status 0
Exited normally with status 0
Exited normally with status 0
Exited normally with status 0
Exited normally with status 0
^CExited with signal 2
查看更多
别忘想泡老子
3楼-- · 2020-07-23 04:40

From San Jacinto's comment above:

system() essentially forks, blocks the parent, and ignores certain signals in the child, as per the POSIX spec links. You can bypass this by first creating another process for system() to block. This leaves the original process (the grandparent of the process which the shell is running on) free to accept kill signals.

#include <stdlib.h>
#include <unistd.h>
#include <wait.h>

int main(void)
{
    pid_t pid;

    while(1)
    {
        pid = fork();

        if(pid > 0) // parent
        {
            wait(0);
        }
        else if(pid == 0) // child
        {
            system("ls 2>&1 1>/dev/null");
            return 0;
        }
        else // could not fork
        {
            return 1;
        }
    }

    return 0;
}

On the surface, this appears to do what I need.

查看更多
Evening l夕情丶
4楼-- · 2020-07-23 04:45

According to IEEE Std 1003.1-2008 (POSIX):

  • The system() function shall behave as if a child process were created using fork(), ...

  • The system() function shall ignore the SIGINT and SIGQUIT signals, and shall block the SIGCHLD signal, while waiting for the command to terminate. If this might cause the application to miss a signal that would have killed it, then the application should examine the return value from system() and take whatever action is appropriate to the application if the command terminated due to receipt of a signal.

  • The system() function shall not return until the child process has terminated.

查看更多
登录 后发表回答