本文是对《从 Java 程序优雅停机到 Linux 信号机制初窥》的补充。
其实到现在为止,已经发现信号机制没那么复杂,本质是通过 sigaction
函数去注册 Handler。接下来就是在源码中走一遍流程。
那么这个流程主要要关注两个,注册和触发(可能在嵌入式领域这两个词描述不太准确),即 Java 程序去注册 Hook(Handler),JVM 收到指定信号后触发 Handler(这个说法不对,不是 JVM 去触发,是内核去调度,但是这一块不在本次探讨范围之内)。
在这之前线简单介绍一下 sigaction
函数(Linux 下 signal
函数就是由 sigaction
函数实现的,这个函数非常重要,涉及到信号的很多知识,过于庞大,本文暂不深入探讨),其原型如下:
#include <signal.h>
struct sigaction {
union __sigaction_u __sigaction_u; /* signal handler */
sigset_t sa_mask; /* signal mask to apply */
int sa_flags; /* see signal options below */
};
int sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact);
sig
是指信号编号(可以是除了 SIGKILL
和 SIGSTOP
之外的任何信号),sigaction
是具体的处理函数,如果 act 不是空指针,那么就会为 sig 设置新的信号处理函数。
在《从 Java 程序优雅停机到 Linux 信号机制初窥》也已经介绍过了,Java 程序默认会处理 HUP
、 TERM
、 INT
这三种 Signal:
java.lang.System#initializeSystemClass
方法:
// Setup Java signal handlers for HUP, TERM, and INT (where available).
Terminator.setup();
java.lang.Terminator#setup
方法:
static void setup() {
if (handler != null) return;
SignalHandler sh = new SignalHandler() {
public void handle(Signal sig) {
Shutdown.exit(sig.getNumber() + 0200);
}
};
handler = sh;
// When -Xrs is specified the user is responsible for
// ensuring that shutdown hooks are run by calling
// System.exit()
try {
Signal.handle(new Signal("HUP"), sh);
} catch (IllegalArgumentException e) {
}
try {
Signal.handle(new Signal("INT"), sh);
} catch (IllegalArgumentException e) {
}
try {
Signal.handle(new Signal("TERM"), sh);
} catch (IllegalArgumentException e) {
}
}
就是基于 Signal.handle
方法去注册 Handler,接下来就是要将这个方法与 JDK 源码联系起来:
public static synchronized SignalHandler handle(Signal sig,
SignalHandler handler)
throws IllegalArgumentException {
long newH = (handler instanceof NativeSignalHandler) ?
((NativeSignalHandler)handler).getHandler() : 2;
long oldH = handle0(sig.number, newH);
if (oldH == -1) {
throw new IllegalArgumentException
("Signal already used by VM or OS: " + sig);
}
signals.put(sig.number, sig);
synchronized (handlers) {
SignalHandler oldHandler = handlers.get(sig);
handlers.remove(sig);
if (newH == 2) {
handlers.put(sig, handler);
}
if (oldH == 0) {
return SignalHandler.SIG_DFL;
} else if (oldH == 1) {
return SignalHandler.SIG_IGN;
} else if (oldH == 2) {
return oldHandler;
} else {
return new NativeSignalHandler(oldH);
}
}
}
关注点就要放在 sun.misc.Signal#handle0
这个 native
方法上了。运行 OpenJDK 12,发现已经进入到这里:
一步步往下走:
细节方面其实不需要过于关注:
完整的宏定义如下:
JVM_ENTRY_NO_ENV(void*, JVM_RegisterSignal(jint sig, void* handler))
// Copied from classic vm
// signals_md.c 1.4 98/08/23
void* newHandler = handler == (void *)2
? os::user_handler()
: handler;
switch (sig) {
/* The following are already used by the VM. */
case SIGFPE:
case SIGILL:
case SIGSEGV:
#if defined(__APPLE__)
/* On Darwin, memory access errors commonly results in SIGBUS instead
* of SIGSEGV. */
case SIGBUS:
#endif
/* The following signal is used by the VM to dump thread stacks unless
ReduceSignalUsage is set, in which case the user is allowed to set
his own _native_ handler for this signal; thus, in either case,
we do not allow JVM_RegisterSignal to change the handler. */
case BREAK_SIGNAL:
return (void *)-1;
/* The following signals are used for Shutdown Hooks support. However, if
ReduceSignalUsage (-Xrs) is set, Shutdown Hooks must be invoked via
System.exit(), Java is not allowed to use these signals, and the the
user is allowed to set his own _native_ handler for these signals and
invoke System.exit() as needed. Terminator.setup() is avoiding
registration of these signals when -Xrs is present.
- If the HUP signal is ignored (from the nohup) command, then Java
is not allowed to use this signal.
*/
case SHUTDOWN1_SIGNAL:
case SHUTDOWN2_SIGNAL:
case SHUTDOWN3_SIGNAL:
if (ReduceSignalUsage) return (void*)-1;
if (os::Posix::is_sig_ignored(sig)) return (void*)1;
}
void* oldHandler = os::signal(sig, newHandler);
if (oldHandler == os::user_handler()) {
return (void *)2;
} else {
return oldHandler;
}
JVM_END
欢迎关注公众号