java 中关于自定义信号在linux下的实现

在java 中调用Signal的方法handle可以去注册一个信号的处理函数,方法的如下:

 public static synchronized SignalHandler handle(Signal sig, 
						    SignalHandler handler) {
....
}

比如常用的addShutdownHook钩子函数里,就是在 Terminator.setup();的时候将Shutdown.exit 的函数注册到了信号 SHUTDOWN1_SIGNAL(SIGHUP), SHUTDOWN2_SIGNAL(SIGINT), SHUTDOWN3_SIGNAL(SIGTERM)中,当线程接受到信号时,通过调用函数Shutdown.exit的调用hook中的钩子函数。

在笔者的文章(java 中关于信号的处理在linux下的实现)也提到jdk如何处理信号的,那么调用handle里是不是直接就把这个方法注册进了信号处理呢?

请注意,handle是一个java的方法,而注册信号函数是c的代码,显然不能简单的将java的方法直接提供给c调用,其次信号处理函数是在内核态中处理,安全性和执行时间的长短将影响到内核的信号调度。

先看java源码,如下面所示

  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: " + sig);
	}
	signals.put(new Integer(sig.number), sig);
	synchronized (handlers) {
	    SignalHandler oldHandler = (SignalHandler)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);
	    }
	}
    }
在native code hand0里并没有将handle的方法传进去,只是传了一个整型值。

在c++代码中hand0里调用了函数 JVM_RegisterSignal,具体来看一下这个函数的实现

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 INTERRUPT_SIGNAL:
    case SIGFPE:
    case SIGILL:
    case SIGSEGV:

    /* 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::Linux::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

void* newHandler = handler == (void *)2
                   ? os::user_handler()
                   : handler;
因为传进的值是2,那么真正在c++里的信号处理函数实际上os::user_handler(),同时jvm也保护了几个信号,不允许外部改变信号的处理函数。

一切豁然开朗,笔者的博客(java 中关于信号的处理在linux下的实现)已经提到过这个函数,通过os:signal_notify 去通知signal dispatcher 线程的os::signal_wait,也就是接受到信号的线程通过信号函数notify到处理信号的线程(signal dispatcher ),最后由该线程做后续的事情。

具体来看signal dispatcher 的thread entry

static void signal_thread_entry(JavaThread* thread, TRAPS) {
      ....
      default: {
        // Dispatch the signal to java
        HandleMark hm(THREAD);
        klassOop k = SystemDictionary::resolve_or_null(vmSymbolHandles::sun_misc_Signal(), THREAD);
        KlassHandle klass (THREAD, k);
        if (klass.not_null()) {
          JavaValue result(T_VOID);
          JavaCallArguments args;
          args.push_int(sig);
          JavaCalls::call_static(
            &result,
            klass,
            vmSymbolHandles::dispatch_name(),
            vmSymbolHandles::int_void_signature(),
            &args,
            THREAD
          );
        }
        ....
}

也就是在jvm的c++源码中,反调用了java的方法,也就是signal.java中的dispatch(int number),方法dispatch中才是真正的调用了在文章开头提到的注册到Signal的方法handle。

dispatch方法中仍然起了一个新的线程去处理handle,这样就不会block signal dispatcher 线程。

你可能感兴趣的:(java,jvm,thread,linux,user,Signal)