SIGINT
当一个进程被可处理的信号(如或 )杀死,SIGTERM
但它不处理该信号时,该进程的退出代码是什么?
对于像这样无法处理的信号怎么办SIGKILL
?
据我所知,终止进程SIGINT
可能会导致退出代码130
,但这会因内核或 shell 实现而异吗?
$ cat myScript
#!/bin/bash
sleep 5
$ ./myScript
<ctrl-c here>
$ echo $?
130
我不确定如何测试其他信号......
$ ./myScript &
$ killall myScript
$ echo $?
0 # duh, that's the exit code of killall
$ killall -9 myScript
$ echo $?
0 # same problem
答案1
进程可以使用整数参数调用_exit()
系统调用(在 Linux 上,另请参阅 参考资料exit_group()
),以向其父进程报告退出代码。虽然它是一个整数,但只有 8 个最低有效位可供父级使用(例外情况是当waitid()
在父级中使用SIGCHLD 或处理程序来检索该代码,尽管不在 Linux 上)。
父级通常会执行 await()
或waitpid()
以获得地位他们的孩子作为整数(尽管waitid()
也可以使用稍微不同的语义)。
在 Linux 和大多数 Unices 上,如果进程正常终止,则该进程的第 8 到 15 位地位number 将包含传递给 的退出代码exit()
。如果不是,那么 7 个最低有效位(0 到 6)将包含信号编号,并且如果核心被转储,则第 7 位将被设置。
perl
例如,$?
包含由以下设置的数字waitpid()
:
$ perl -e 'system q(kill $$); printf "%04x\n", $?'
000f # killed by signal 15
$ perl -e 'system q(kill -ILL $$); printf "%04x\n", $?'
0084 # killed by signal 4 and core dumped
$ perl -e 'system q(exit $((0xabc))); printf "%04x\n", $?'
bc00 # terminated normally, 0xbc the lowest 8 bits of the status
$?
类似 Bourne 的 shell 还在自己的特殊参数中设置最后运行命令的退出状态。但是,它并不直接包含 所返回的数字waitpid()
,而是对其进行转换,并且在 shell 之间有所不同。
所有 shell 的共同点是,如果进程正常终止,则$?
包含退出代码的最低 8 位(传递给 的数字)。exit()
不同之处在于进程被信号终止时。在所有情况下,POSIX 都要求该数字大于 128。POSIX 没有指定该值可能是什么。但在实践中,在我所知道的所有 Bourne 类 shell 中,的最低 7 位$?
将包含信号编号。但是,n
信号编号在哪里,
在 ash、zsh、pdksh、bash、Bourne shell 中,
$?
是128 + n
.这意味着在这些 shell 中,如果您得到 a$?
of129
,您不知道是因为进程退出exit(129)
还是被信号杀死1
(HUP
在大多数系统上)。但基本原理是,当 shell 自行退出时,默认情况下会返回最后退出命令的退出状态。通过确保$?
永远不会大于 255,可以实现一致的退出状态:$ bash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"' bash: line 1: 16720 Terminated sh -c "kill \$\$" 8f # 128 + 15 $ bash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?" bash: line 1: 16726 Terminated sh -c "kill \$\$" 8f # here that 0x8f is from a exit(143) done by bash. Though it's # not from a killed process, that does tell us that probably # something was killed by a SIGTERM
ksh93
,$?
是256 + n
。这意味着您可以根据 的值$?
区分已终止的进程和未终止的进程。较新版本的ksh
,在退出时,如果$?
大于 255,则会使用相同的信号终止自身,以便能够向其父级报告相同的退出状态。虽然这听起来是个好主意,但这意味着ksh
如果进程被核心生成信号杀死,将生成额外的核心转储(可能会覆盖另一个):$ ksh -c 'sh -c "kill \$\$"; printf "%x\n" "$?"' ksh: 16828: Terminated 10f # 256 + 15 $ ksh -c 'sh -c "kill -ILL \$\$"; exit'; printf '%x\n' "$?" ksh: 16816: Illegal instruction(coredump) Illegal instruction(coredump) 104 # 256 + 15, ksh did indeed kill itself so as to report the same # exit status as sh. Older versions of `ksh93` would have returned # 4 instead.
你甚至可以说有一个错误,
ksh93
即使它$?
来自return 257
一个函数的完成,它也会自杀:$ ksh -c 'f() { return "$1"; }; f 257; exit' zsh: hangup ksh -c 'f() { return "$1"; }; f 257; exit' # ksh kills itself with a SIGHUP so as to report a 257 exit status # to its parent
yash
。yash
提供了一个折衷方案。它返回256 + 128 + n
。这意味着我们还可以区分被终止的进程和正确终止的进程。退出后,它会报告,128 + n
而不必自杀及其可能产生的副作用。$ yash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"' 18f # 256 + 128 + 15 $ yash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?" 8f # that's from a exit(143), yash was not killed
要从 的值获取信号$?
,可移植的方法是使用kill -l
:
$ /bin/kill 0
Terminated
$ kill -l "$?"
TERM
(为了可移植性,您不应该使用信号编号,而只能使用信号名称)
在非伯恩战线上:
csh
/tcsh
与fish
Bourne shell 相同,只是状态是 in$status
而不是$?
(请注意,zsh
还设置了与(除了)$status
的兼容性)。csh
$?
rc
:退出状态$status
也是如此,但是当被信号杀死时,该变量包含信号的名称(例如sigterm
或sigill+core
如果生成了核心)而不是数字,这是该 shell 良好设计的另一个证明。es
。退出状态不是变量。如果您关心它,请运行命令:status = <={cmd}
它将返回一个数字或sigterm
类似sigsegv+core
的数字rc
。
也许为了完整起见,我们应该提及zsh
's$pipestatus
和bash
's$PIPESTATUS
数组,其中包含最后一个管道组件的退出状态。
而且为了完整性,当涉及 shell 函数和源文件时,默认情况下函数返回最后一个命令运行的退出状态,但也可以使用内置函数显式设置返回状态return
。我们在这里看到一些差异:
bash
和mksh
(自 R41 起,显然是有意引入的回归^W变化) 会将数字(正数或负数)截断为 8 位。例如return 1234
将设置$?
为210
,return -- -1
将设置$?
为 255。zsh
和pdksh
(以及除 之外的导数mksh
)允许任何带符号的 32 位十进制整数(-2 31到 2 31 -1)(并将数字截断为 32 位)。ash
并yash
允许 0 到 2 31 -1 之间的任何正整数,并对其中的任何数字返回错误。ksh93
对于return 0
按原样return 320
设置,但对于其他任何内容,请截断为 8 位。$?
请注意,如前所述,返回 256 到 320 之间的数字可能会导致ksh
退出时自杀。rc
并es
允许返回任何内容,甚至是列表。
另请注意,某些 shell 还使用特殊值$?
/$status
来报告一些不是进程退出状态的错误条件,例如127
或126
for找不到命令或者不可执行(或源文件中的语法错误)...
答案2
当进程退出时,它会向操作系统返回一个整数值。在大多数 Unix 变体中,该值取模 256:忽略除低位之外的所有内容。子进程的状态通过一个 16 位整数返回给其父进程,其中
- 位 0-6(低 7 位)是用于终止进程的信号号,如果进程正常退出,则为 0;
- 如果进程被信号终止并转储核心,则设置位 7;
- 如果进程正常退出,则第 8-15 位是进程的退出代码;如果进程被信号终止,则为 0。
状态由返回wait
系统调用或其兄弟之一。 POSIX 没有指定退出状态和信号编号的确切编码;它只提供
- 一种判断退出状态对应于信号还是正常退出的方法;
- 如果进程正常退出,则可以访问退出代码;
- 如果进程被信号杀死,则访问信号编号的方法。
严格来说,没有出口代码当进程被信号杀死时:存在的是退出地位。
在 shell 脚本中,命令的退出状态通过特殊变量报告$?
。该变量以不明确的方式对退出状态进行编码:
- 如果进程正常退出,则
$?
为其退出状态。 - 如果进程被信号杀死,则
$?
在大多数系统上为 128 加上信号编号。在这种情况下,POSIX 仅要求$?
大于 128; ksh93 添加 256 而不是 128。我从未见过除了向信号编号添加常量之外执行任何其他操作的 UNIX 变体。
因此,在 shell 脚本中,您无法确定命令是被信号终止还是以大于 128 的状态代码退出,ksh93 除外。程序以大于 128 的状态代码退出的情况非常罕见,部分原因是程序员由于$?
不明确而避免这样做。
在大多数 Unix 变体上,SIGINT 是信号 2,因此$?
对于被 SIGINT 杀死的进程来说,SIGINT 是 128+2=130。您会看到 SIGHUP 为 129,SIGKILL 为 137,等等。
答案3
这取决于你的外壳。从bash(1)
手册页来看,外壳文法部分,简单命令小节:
a的返回值简单的命令是 [...] 128+n如果命令由信号终止n。
由于SIGINT
您的系统上的信号编号为 2,因此在 Bash 下运行时返回值为 130。
答案4
似乎是正确的地方提到 SVr4 在 1989 年引入了 waitid(),但到目前为止似乎还没有重要的程序使用它。 waitid() 允许从 exit() 代码中检索完整的 32 位。
大约 2 个月前,我重写了 Bourne Shell 的等待/作业控制部分,使用 waitid() 而不是 waitpid()。这样做是为了消除用 0xFF 屏蔽退出代码的限制。
waitid() 接口比以前的 wait() 实现要简洁得多,除了 1980 年来自 UNOS 的 cwait() 调用之外。
您可能有兴趣阅读以下位置的手册页:
http://schillix.sourceforge.net/man/man1/bosh.1.html
并检查当前从第 8 页开始的“参数替换”部分。
为 waitid() 接口引入了新变量 .sh.*。这个接口不再对 $? 已知的数字有不明确的含义。并使接口变得更加容易。
请注意,您需要有一个兼容 POSIX 的 waitid() 才能使用此功能,因此 Mac OS X 和 Linux 目前不提供此功能,但 waitid() 是在 waitpid() 调用上模拟的,因此非 POSIX 平台您仍然只能从退出代码中获得 8 位。
简而言之:.sh.status 是数字退出代码,.sh.code 是数字退出原因。
为了更好的可移植性,有: .sh.codename 表示退出原因的文本版本,例如“DUMPED”和 .sh.termsig ,表示终止进程的信号的信号名称。
为了更好地使用,有两个与退出无关的 .sh.codename 值:“NOEXEC”和“NOTFOUND”,当程序根本无法启动时使用。
FreeBSD 在我报告后 20 小时内修复了他们的 waitid() 内核错误,Linux 还没有开始修复。我希望在 POSIX 中引入此功能 26 年后,所有操作系统都将很快正确支持它。