I have written a program that invokes a system command from ins开发者_开发百科ide:
#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?
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
According to IEEE Std 1003.1-2008 (POSIX):
The
system()
function shall behave as if a child process were created usingfork()
, ...The
system()
function shall ignore theSIGINT
andSIGQUIT
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 fromsystem()
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.
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.
精彩评论