如果我点击"跳过",它会继续运行,永远不会停止.
我复制了那个.所以让我们来看看发生了什么.
gdb -q ./tsh
(gdb) break tsh.c:191
(gdb) b tsh.c:191
Breakpoint 1 at 0x40156a: file tsh.c, line 191.
(gdb) run
Starting program: /tmp/tsh
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
tsh> aaa
[Detaching after fork from child process 182]
aaa: command not found
Breakpoint 1, eval (cmdline=0x7fffffffd930 "aaa\n") at tsh.c:191
191 sigprocmask(SIG_SETMASK, &prev, NULL);
(gdb) n
在这一点上,一切都是悬而未决的,所以我们必须被困在sigprocmask
,对吗?
实际上,我们不是.
^C
Program received signal SIGINT, Interrupt.
0x00007ffff7eb5c37 in __GI___wait4 (pid=-1, stat_loc=0x7fffffffcd5c, options=3, usage=0x0)
at ../sysdeps/unix/sysv/linux/wait4.c:30
30 return SYSCALL_CANCEL (wait4, pid, stat_loc, options, usage);
(gdb) bt
#0 0x00007ffff7eb5c37 in __GI___wait4 (pid=-1, stat_loc=0x7fffffffcd5c, options=3, usage=0x0)
at ../sysdeps/unix/sysv/linux/wait4.c:30
#1 0x0000000000401a02 in sigchld_handler (sig=17) at tsh.c:383
#2 <signal handler called>
#3 __GI___pthread_sigmask (how=2, newmask=<optimized out>, oldmask=0x0) at pthread_sigmask.c:43
#4 0x00007ffff7e18d8d in __GI___sigprocmask (how=<optimized out>, set=<optimized out>, oset=<optimized out>)
at ../sysdeps/unix/sysv/linux/sigprocmask.c:25
#5 0x0000000000401583 in eval (cmdline=0x7fffffffd930 "aaa\n") at tsh.c:191
#6 0x000000000040142d in main (argc=1, argv=0x7fffffffde68) at tsh.c:149
现在我们来看看actually是怎么回事.sigprocmask
解锁SIGCHLD
,这导致immediate传递将要返回信号just before sigprocmask
.这进而调用sigchld_handler
,而sigchld_handler
在一个永无止境的循环中调用waitpid
.
为什么循环不终止?因为代码期望waitpid
在没有子级的情况下返回0
,但这是不正确的:在这种情况下,waitpid
返回-1
.
下面的修复使tsh
的工作,因为人们可能会期望:
diff -u tsh.c.orig tsh.c
--- tsh.c.orig 2024-01-20 21:42:47.915401415 -0800
+++ tsh.c 2024-01-20 21:43:20.145996657 -0800
@@ -383,7 +383,7 @@
pid = waitpid(-1, &status, WNOHANG | WUNTRACED);
// 如果没有僵尸进程,则退出
- if (pid == 0)
+ if (pid == 0 || pid == -1)
return;
// 如果子进程终止导致waitpid从阻塞中恢复
./tsh
tsh> aaa
aaa: command not found
tsh> bbb
bbb: command not found
tsh>
以下是来自Linux man page的相关文本:
如果在Options中设置了WNOHANG
来调用waitpid()
,则它至少有一个由pid
指定的子进程的状态不可用,并且状态对于任何由PID指定的进程都不可用,则返回0.否则,返回-1