我正在用 C 语言编写一个非常基本的 shell,带有一些基本的作业控制。如果用户输入非内置命令,则父级会分叉并创建一个子级,然后子级可以执行程序。
我有信号处理程序来处理 SIGCHLD、SIGINT 和 SIGTSTP。这些处理程序能够捕获终端发送的信号(即,如果我按 ctrl+z,那么终端会发送 SIGTSTP,并且 SIGTSTP 信号会被我的 sigtstp_handler 捕获)。
但是我需要处理程序也捕获子进程发送的信号,即如果子进程执行像 ./prog 这样的程序,并且 prog 中是一条指令“kill(prog_pid, SIGTSTP)”,那么我需要我的 sigtstp_handler 来捕获那个信号。
例如,这是我的 sigtstp_handler:
void sigtstp_handler(int sig)
{
pid_t pid;
if ((pid = fgpid(jobs)) > 0) { //make sure foreground job exists
kill(-pid, SIGTSTP);
printf("caught SIGTSTP");
}
return;
}
然后我的主要功能基本上(因为太长而删掉了很多)看起来像:
main() {
Signal(SIGTSTP, sigtstp_handler); //install sigtstp_handler
if ((output = builtin_cmd(argv)) == 0) {
sigset_t mask, prev_mask;
sigemptyset(&mask);
sigaddset(&mask, SIGTSTP);
//block signal
sigprocmask(SIG_BLOCK, &mask, &prev_mask);
pid = fork();
//CHILD
if (pid == 0) {
//unblock signal for child
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
setpgid(0,0); //set pgid
//run user program
if (execve(argv[0], argv, environ) < 0) { //
printf("Command not found\n");
exit(0);
}
}
//PARENT
if (pid != 0){
addjob(pid);
//unblock signals for parent
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
if (!background) {
waitfg(pid);
}
}
}
return;
}
因此,如果信号是通过 ctrl+z 从终端发送的,则 sigtstp_handler 会捕获我的信号。但如果它是由子进程通过kill(pid, SIGTSTP)发送的,它不会捕获它(我知道它不会捕获它,因为它不执行打印语句)。
为什么它没有捕获子进程的信号?我认为当我安装处理程序时,它基本上会在发送 SIGTSTP 信号时执行我的处理程序,而不是执行默认的操作。那么无论它来自什么,它不应该捕获 SIGTSTP 吗?当我的 sigint_handler 来自子进程时无法捕获 SIGINT,我也遇到了同样的问题。
我怎样才能确保它确实捕获了该信号?
我在想也许是我的信号拦截器有问题?所以我在父级中阻止 SIGTSTP,然后我分叉。然后在孩子中我解锁所有信号然后运行程序。因此,当子进程调用kill()时,信号将被解除阻塞。在父级中,我添加作业,然后取消阻止,然后等待前台进程完成。但是父级可以很好地接受 SIGCHILD 信号,所以我不知道为什么它会阻止来自kill()的信号。
我也知道(至少非常确定)我的信号处理程序仅作用于前台进程,这不是问题,因为它是一个调用kill(pid, SIGTSTP)的前台进程。
我感谢任何帮助。
您必须专门为子进程安装信号处理程序。正如 Barmar 在评论中提到的:
子进程在调用
后无法使用父进程的处理程序,因为子进程中不再存在这些信号函数。exec()
根据手册页:
调用过程映像中设置为默认操作 (
) 的信号应设置为新过程映像中的默认操作。除了SIG_DFL
之外,被调用过程映像设置为忽略的信号 (SIG_IGN) 应被新过程映像设置为忽略。设置为由调用过程映像捕获的信号应设置为新过程映像中的默认操作(请参阅SIGCHLD
)。<signal.h>
如果
SIGCHLD
信号设置为被调用过程映像忽略,则未指定 SIGCHLD
信号是否设置为忽略或设置为新过程映像中的默认操作。
您应该注意的其他一些事项:
main() {
无效 C. 正如 Jonathan Leffer 在评论中提到的:请注意,main() { 不是有效的 C99,更不用说 C17(或 C23)了——您必须指定返回类型。如果忽略命令行参数,通常应该使用 int main(void)。还有,回报; main() 末尾应该返回 0;或类似的东西。即使在 C90 规则下,函数也应该返回一个值,因为类型是隐式 int 的。 (在 C99 或更高版本的规则下,您可以完全省略 return ,这相当于添加 return 0;,但我认为该规则是令人厌恶的。)
printf()
是异步信号不安全。根据 POSIX 标准和 C 标准,在信号处理程序中调用它具有未定义的行为。不过,POSIX 指定 write()
是异步信号安全的,并且可以在信号处理程序中使用。
关于
Signal(SIGTSTP, sigtstp_handler); //install sigtstp_handler
,不存在 Signal
这样的东西,除非您将其定义为 signal()
调用的包装器。根据 signal()
的手册页:
的唯一便携用途是将信号的处置设置为signal()
或SIG_DFL
。SIG_IGN
将其替换为
sigaction()
。