signal 线程的启动

经常使用jstack查看jvm线程的同学对于signal dispatch线程不会陌生, 该线程用于处理jvm接受到的信号。该线程的启动位于sigal_init()中,

void os::signal_init() {
  if (!ReduceSignalUsage) {
    // Setup JavaThread for processing signals
    EXCEPTION_MARK;
    Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK);
    instanceKlassHandle klass (THREAD, k);
    instanceHandle thread_oop = klass->allocate_instance_handle(CHECK);

    const char thread_name[] = "Signal Dispatcher";
    Handle string = java_lang_String::create_from_str(thread_name, CHECK);

    // Initialize thread_oop to put it into the system threadGroup
    Handle thread_group (THREAD, Universe::system_thread_group());
    JavaValue result(T_VOID);
    JavaCalls::call_special(&result, thread_oop,
                           klass,
                           vmSymbols::object_initializer_name(),
                           vmSymbols::threadgroup_string_void_signature(),
                           thread_group,
                           string,
                           CHECK);

    KlassHandle group(THREAD, SystemDictionary::ThreadGroup_klass());
    JavaCalls::call_special(&result,
                            thread_group,
                            group,
                            vmSymbols::add_method_name(),
                            vmSymbols::thread_void_signature(),
                            thread_oop,         // ARG 1
                            CHECK);

    os::signal_init_pd();

    { MutexLocker mu(Threads_lock);
      JavaThread* signal_thread = new JavaThread(&signal_thread_entry);

      // At this point it may be possible that no osthread was created for the
      // JavaThread due to lack of memory. We would have to throw an exception
      // in that case. However, since this must work and we do not allow
      // exceptions anyway, check and abort if this fails.
      if (signal_thread == NULL || signal_thread->osthread() == NULL) {
        vm_exit_during_initialization("java.lang.OutOfMemoryError",
                                      "unable to create new native thread");
      }

      java_lang_Thread::set_thread(thread_oop(), signal_thread);
      java_lang_Thread::set_priority(thread_oop(), NearMaxPriority);
      java_lang_Thread::set_daemon(thread_oop());

      signal_thread->set_threadObj(thread_oop());
      Threads::add(signal_thread);
      Thread::start(signal_thread);
    }
    // Handle ^BREAK
    os::signal(SIGBREAK, os::user_handler());
  }
}

signal_init()会初始化一个JavaThread,该Thread的入口是signal_thread_entry,该线程是daemon的。该线程初始化后,会加入到jvm的线程列表中,同时调用Thread::start()启动该线程。

static void signal_thread_entry(JavaThread* thread, TRAPS) {
  os::set_priority(thread, NearMaxPriority);
  while (true) {
    int sig;
    {
      // FIXME : Currently we have not decieded what should be the status
      //         for this java thread blocked here. Once we decide about
      //         that we should fix this.
      sig = os::signal_wait();
    }
    if (sig == os::sigexitnum_pd()) {
       // Terminate the signal thread
       return;
    }

    switch (sig) {
      case SIGBREAK: {
        // Check if the signal is a trigger to start the Attach Listener - in that
        // case don't print stack traces.
        if (!DisableAttachMechanism && AttachListener::is_init_trigger()) {
          continue;
        }
        // Print stack traces
        // Any SIGBREAK operations added here should make sure to flush
        // the output stream (e.g. tty->flush()) after output.  See 4803766.
        // Each module also prints an extra carriage return after its output.
        VM_PrintThreads op;
        VMThread::execute(&op);
        VM_PrintJNI jni_op;
        VMThread::execute(&jni_op);
        VM_FindDeadlocks op1(tty);
        VMThread::execute(&op1);
        Universe::print_heap_at_SIGBREAK();
        if (PrintClassHistogram) {
          VM_GC_HeapInspection op1(gclog_or_tty, true /* force full GC before heap inspection */);
          VMThread::execute(&op1);
        }
        if (JvmtiExport::should_post_data_dump()) {
          JvmtiExport::post_data_dump();
        }
        break;
      }
      default: {
        // 省略
        ......
      }
    }
  }
}

signal_init内部是一个while(true)循环,循环内部的开始会调用os::signal_wait()将当前线程挂起,等待后续的信号唤醒该线程。该线程被唤醒后,会进入到信号的处理流程。

信号处理函数的注册

信号的注册分为两部分,一部分是jvm初始化的时候设置的,另一部分是在jvm运行阶段通过jdk设置的。

jvm初始阶段注册信号处理函数

该阶段的信号注册操作在install_signal_handlers函数中,

void os::Bsd::install_signal_handlers() {
  if (!signal_handlers_are_installed) {
    signal_handlers_are_installed = true;

    // signal-chaining
    typedef void (*signal_setting_t)();
    signal_setting_t begin_signal_setting = NULL;
    signal_setting_t end_signal_setting = NULL;
    begin_signal_setting = CAST_TO_FN_PTR(signal_setting_t,
                             dlsym(RTLD_DEFAULT, "JVM_begin_signal_setting"));
    if (begin_signal_setting != NULL) {
      end_signal_setting = CAST_TO_FN_PTR(signal_setting_t,
                             dlsym(RTLD_DEFAULT, "JVM_end_signal_setting"));
      get_signal_action = CAST_TO_FN_PTR(get_signal_t,
                            dlsym(RTLD_DEFAULT, "JVM_get_signal_action"));
      libjsig_is_loaded = true;
      assert(UseSignalChaining, "should enable signal-chaining");
    }
    if (libjsig_is_loaded) {
      // Tell libjsig jvm is setting signal handlers
      (*begin_signal_setting)();
    }

    set_signal_handler(SIGSEGV, true);
    set_signal_handler(SIGPIPE, true);
    set_signal_handler(SIGBUS, true);
    set_signal_handler(SIGILL, true);
    set_signal_handler(SIGFPE, true);
    set_signal_handler(SIGXFSZ, true);

#if defined(__APPLE__)
    kern_return_t kr;
    kr = task_set_exception_ports(mach_task_self(),
        EXC_MASK_BAD_ACCESS | EXC_MASK_ARITHMETIC,
        MACH_PORT_NULL,
        EXCEPTION_STATE_IDENTITY,
        MACHINE_THREAD_STATE);

    assert(kr == KERN_SUCCESS, "could not set mach task signal handler");
#endif

    if (libjsig_is_loaded) {
      // Tell libjsig jvm finishes setting signal handlers
      (*end_signal_setting)();
    }

    // We don't activate signal checker if libjsig is in place, we trust ourselves
    // and if UserSignalHandler is installed all bets are off
    if (CheckJNICalls) {
      if (libjsig_is_loaded) {
        if (PrintJNIResolving) {
          tty->print_cr("Info: libjsig is activated, all active signal checking is disabled");
        }
        check_signals = false;
      }
      if (AllowUserSignalHandlers) {
        if (PrintJNIResolving) {
          tty->print_cr("Info: AllowUserSignalHandlers is activated, all active signal checking is disabled");
        }
        check_signals = false;
      }
    }
  }
}

begin_signal_setting()函数标记现在的状态jvm_signal_installing=true,然后调用set_signal_handler将下面的几个信号量的处理函数设置为内置的signalHandler,

SIGSEGV
SIGPIPE
SIGBUS
SIGILL
SIGFPE
SIGXFSZ

set_signal_handler的实现如下,为了不影响阅读,下面的代码中省略了部分不太重要的代码,

void os::Linux::set_signal_handler(int sig, bool set_installed) {
  // Check for overwrite.
  struct sigaction oldAct;
  sigaction(sig, (struct sigaction*)NULL, &oldAct);

  // 此处省略
  ......

  struct sigaction sigAct;
  sigfillset(&(sigAct.sa_mask));
  sigAct.sa_handler = SIG_DFL;
  if (!set_installed) {
    sigAct.sa_flags = SA_SIGINFO|SA_RESTART;
  } else {
    sigAct.sa_sigaction = signalHandler;
    sigAct.sa_flags = SA_SIGINFO|SA_RESTART;
  }
  // Save flags, which are set by ours
  assert(sig > 0 && sig < MAXSIGNUM, "vm signal out of expected range");
  sigflags[sig] = sigAct.sa_flags;

  int ret = sigaction(sig, &sigAct, &oldAct);
  assert(ret == 0, "check");

  void* oldhand2  = oldAct.sa_sigaction
                  ? CAST_FROM_FN_PTR(void*, oldAct.sa_sigaction)
                  : CAST_FROM_FN_PTR(void*, oldAct.sa_handler);
  assert(oldhand2 == oldhand, "no concurrent signal handler installation");
}

set_signal_handler将sigaction的sa_sigaction设置为signalHandler,然后调用sicaction函数完成信号处理函数的注册。

当设置操作完成后调用end_signal_setting()将jvm_signal_installing设置为false,vm_signal_installed设置为true。

运行时注册信号处理函数

举例

该阶段的注册任务主要通过sun.misc包下面的Signal和SignalHandler两个类完成,下面使用一个简单的例子来介绍该阶段如何完成信号处理函数的注册,

屏蔽掉TERM信号,仅仅打印提示信息: 也就是说在调用kill -15 pid杀进程的时候,只打印信息

import sun.misc.Signal;
import sun.misc.SignalHandler;

import java.lang.reflect.Field;
import java.util.Hashtable;

public class SignalHandlerTest {

    public static class TermSignalHandler implements SignalHandler  {
        @Override
        public void handle(Signal signal) {
            System.out.println("jvm terminal");
        }
    }

    public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {
        Signal signal = new Signal("TERM");
        TermSignalHandler signalHandler = new TermSignalHandler();
        Signal.handle(signal, signalHandler);
        Thread.sleep(100000);
    }
}

运行上面的代码,然后使用jps查看进程,

2864 Launcher
2880 Jps
2865 SignalHandlerTest
623

使用kill发送terminal信号,

kill -15 2865

运行kill后可以看到进程打印了”jvm terminal”,再次使用jps查看进程可以发现目标进程仍然还在运行。

那么上面的代码究竟干了什么呢?上面的代码分为以下几步,

  1. 实现接口SignalHandler,实现类用于处理对应的信号
  2. 通过Signal的静态方法handle将对应的Handler注册到对应的Signal中

TermSignalHandler实现了SignalHandler,用于打印相关信息。

可以注册哪些信号呢?

上一节提到了自定义信号处理的方法,在注册信号处理的Handler的时候首先要定义对应的Signal。这时候出现了两个问题,

  1. 由于Signal的构造方法只能传入字符串,所以我们在自定义信号处理之前需要知道信号对应的字符串是什么。
  2. 我们可以处理哪些信号呢?

想要回答上面的问题,我们需要看一下Signal的实现。下面是Signal的构造方法,

    public Signal(String var1) {
        this.number = findSignal(var1);
        this.name = var1;
        if (this.number < 0) {
            throw new IllegalArgumentException("Unknown signal: " + var1);
        }
    }

Signal在初始化的时候会通过native方法findSignal来查找信号对应的常量,如果常量小于0则抛出异常。下面来看一下findSignal的实现,

JVM_ENTRY_NO_ENV(jint, JVM_FindSignal(const char *name))

  /* find and return the named signal's number */

  for(uint i=0; i<ARRAY_SIZE(siglabels); i++)
    if(!strcmp(name, siglabels[i].name))
      return siglabels[i].number;

  return -1;

JVM_END

通过上面的代码可以大概猜到所有支持的信号都定义在siglabels这个数组里面。由于每个系统可以处理的信号不同,下面已linux为例,

struct siglabel siglabels[] = {
  /* derived from /usr/include/bits/signum.h on RH7.2 */
   "HUP",       SIGHUP,         /* Hangup (POSIX).  */
  "INT",        SIGINT,         /* Interrupt (ANSI).  */
  "QUIT",       SIGQUIT,        /* Quit (POSIX).  */
  "ILL",        SIGILL,         /* Illegal instruction (ANSI).  */
  "TRAP",       SIGTRAP,        /* Trace trap (POSIX).  */
  "ABRT",       SIGABRT,        /* Abort (ANSI).  */
  "IOT",        SIGIOT,         /* IOT trap (4.2 BSD).  */
  "BUS",        SIGBUS,         /* BUS error (4.2 BSD).  */
  "FPE",        SIGFPE,         /* Floating-point exception (ANSI).  */
  "KILL",       SIGKILL,        /* Kill, unblockable (POSIX).  */
  "USR1",       SIGUSR1,        /* User-defined signal 1 (POSIX).  */
  "SEGV",       SIGSEGV,        /* Segmentation violation (ANSI).  */
  "USR2",       SIGUSR2,        /* User-defined signal 2 (POSIX).  */
  "PIPE",       SIGPIPE,        /* Broken pipe (POSIX).  */
  "ALRM",       SIGALRM,        /* Alarm clock (POSIX).  */
  "TERM",       SIGTERM,        /* Termination (ANSI).  */
#ifdef SIGSTKFLT
  "STKFLT",     SIGSTKFLT,      /* Stack fault.  */
#endif
  "CLD",        SIGCLD,         /* Same as SIGCHLD (System V).  */
  "CHLD",       SIGCHLD,        /* Child status has changed (POSIX).  */
  "CONT",       SIGCONT,        /* Continue (POSIX).  */
  "STOP",       SIGSTOP,        /* Stop, unblockable (POSIX).  */
  "TSTP",       SIGTSTP,        /* Keyboard stop (POSIX).  */
  "TTIN",       SIGTTIN,        /* Background read from tty (POSIX).  */
  "TTOU",       SIGTTOU,        /* Background write to tty (POSIX).  */
  "URG",        SIGURG,         /* Urgent condition on socket (4.2 BSD).  */
  "XCPU",       SIGXCPU,        /* CPU limit exceeded (4.2 BSD).  */
  "XFSZ",       SIGXFSZ,        /* File size limit exceeded (4.2 BSD).  */
  "VTALRM",     SIGVTALRM,      /* Virtual alarm clock (4.2 BSD).  */
  "PROF",       SIGPROF,        /* Profiling alarm clock (4.2 BSD).  */
  "WINCH",      SIGWINCH,       /* Window size change (4.3 BSD, Sun).  */
  "POLL",       SIGPOLL,        /* Pollable event occurred (System V).  */
  "IO",         SIGIO,          /* I/O now possible (4.2 BSD).  */
  "PWR",        SIGPWR,         /* Power failure restart (System V).  */
#ifdef SIGSYS
  "SYS",        SIGSYS          /* Bad system call. Only on some Linuxen! */
#endif
  };

我们可以在上面的信号中找到例子中提到的TERM。

自定义信号处理的原理

到这里相信大多数人已经知道如何在运行时注册信号处理函数了,以及我们可以针对哪些信号注册处理函数。那么java是如何实现自定义信号处理的呢?

回顾下上面的例子是如何做的,

  1. 定义handler
  2. 注册handler

下面我们看一下handler是如何注册的,

    public static synchronized SignalHandler handle(Signal var0, SignalHandler var1) throws IllegalArgumentException {
        long var2 = var1 instanceof NativeSignalHandler ? ((NativeSignalHandler)var1).getHandler() : 2L;
        long var4 = handle0(var0.number, var2);
        if (var4 == -1L) {
            throw new IllegalArgumentException("Signal already used by VM or OS: " + var0);
        } else {
            signals.put(var0.number, var0);
            Hashtable var6 = handlers;
            synchronized(handlers) {
                SignalHandler var7 = (SignalHandler)handlers.get(var0);
                handlers.remove(var0);
                if (var2 == 2L) {
                    handlers.put(var0, var1);
                }

                if (var4 == 0L) {
                    return SignalHandler.SIG_DFL;
                } else if (var4 == 1L) {
                    return SignalHandler.SIG_IGN;
                } else {
                    return (SignalHandler)(var4 == 2L ? var7 : new NativeSignalHandler(var4));
                }
            }
        }
    }

上面的代码主要干了两件事,

  1. 通过native方法handle0,将handler注册到jvm中
  2. 然后将信号写入到signals,将处理器写入到handlers。

下面看一下handle0是如何实现的的,

JNIEXPORT jlong JNICALL
Java_sun_misc_Signal_handle0(JNIEnv *env, jclass cls, jint sig, jlong handler)
{
    return ptr_to_jlong(JVM_RegisterSignal(sig, jlong_to_ptr(handler)));
}

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:
    case BREAK_SIGNAL:
      return (void *)-1;
    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

如果handler == 2,也就是说值用户自定义的信号处理函数,则将newHandler设置为os::user_handler()

新的handler通过os::signal()来完成注册的,

void* os::signal(int signal_number, void* handler) {
  struct sigaction sigAct, oldSigAct;

  sigfillset(&(sigAct.sa_mask));
  sigAct.sa_flags   = SA_RESTART|SA_SIGINFO;
  sigAct.sa_handler = CAST_TO_FN_PTR(sa_handler_t, handler);

  if (sigaction(signal_number, &sigAct, &oldSigAct)) {
    // -1 means registration failed
    return (void *)-1;
  }

  return CAST_FROM_FN_PTR(void*, oldSigAct.sa_handler);
}

os::signal首先设置sigaction的掩码和handler, 然后通过sigaction方法将handler注册。

int sigaction(int sig, const struct sigaction *act, struct sigaction *oact) {
  int res;
  bool sigused;
  struct sigaction oldAct;

  signal_lock();

  sigused = (sig < MAXSIGNUM) && ((MASK(sig) & jvmsigs) != 0);
  if (jvm_signal_installed && sigused) {
    /* jvm has installed its signal handler for this signal. */
    /* Save the handler. Don't really install it. */
    if (oact != NULL) {
      *oact = sact[sig];
    }
    if (act != NULL) {
      sact[sig] = *act;
    }

    signal_unlock();
    return 0;
  } else if (sig < MAXSIGNUM && jvm_signal_installing) {
    /* jvm is installing its signal handlers. Install the new
     * handlers and save the old ones. */
    res = call_os_sigaction(sig, act, &oldAct);
    sact[sig] = oldAct;
    if (oact != NULL) {
      *oact = oldAct;
    }

    /* Record the signals used by jvm */
    jvmsigs |= MASK(sig);

    signal_unlock();
    return res;
  } else {
    /* jvm has no relation with this signal (yet). Install the
     * the handler. */
    res = call_os_sigaction(sig, act, oact);

    signal_unlock();
    return res;
  }
}

上面的代码分为三种情况,

  1. 如果jvm已经完成了信号handler的安装(jvm_signal_installed & sigused),那么将handler保存在sact中,sact原来的handler保存在oact中
  2. 如果jvm处于信号装载过程(jvm_signal_installing),则调用系统的sigaction完成handler的装载,将老的handler保存在sact中,同时将老的handler保存在oact中。这些都处理完,设置掩码
  3. 否则直接调用系统的sigaction完成handler的装载。

信号处理

SignalHandler

在虚拟机初始化阶段,SIGILL等几个信号的处理函数被设置为了SignalHandler,该处理函数的实现如下,

void signalHandler(int sig, siginfo_t* info, void* uc) {
  assert(info != NULL && uc != NULL, "it must be old kernel");
  int orig_errno = errno;  // Preserve errno value over signal handler.
  JVM_handle_linux_signal(sig, info, uc, true);
  errno = orig_errno;
}

signalHandler调用JVM_Handle_linux_signal,该方法的实现较长,这里不展开,只列举重要的几个步骤,

  1. 针对信号的不同类型,调用特定的函数做检查
  2. 调用os::Linux::chained_handler处理信号。这个方法会调用JVM_get_signal_action从sact中获取对应的处理函数。

UserHandler

上面提到了对于用户自定义的信号处理函数,注册到系统的handler为os::user_handler(),

static void
UserHandler(int sig, void *siginfo, void *context) {
  // 4511530 - sem_post is serialized and handled by the manager thread. When
  // the program is interrupted by Ctrl-C, SIGINT is sent to every thread. We
  // don't want to flood the manager thread with sem_post requests.
  if (sig == SIGINT && Atomic::add(1, &sigint_count) > 1)
      return;

  // Ctrl-C is pressed during error reporting, likely because the error
  // handler fails to abort. Let VM die immediately.
  if (sig == SIGINT && is_error_reported()) {
     os::die();
  }

  os::signal_notify(sig);
}

UserHandler仅仅调用os::signal_notify()唤醒signal dispatch线程。signal dispatch线程唤醒后执行下面的代码,

        // Dispatch the signal to java
        HandleMark hm(THREAD);
        Klass* k = SystemDictionary::resolve_or_null(vmSymbols::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,
            vmSymbols::dispatch_name(),
            vmSymbols::int_void_signature(),
            &args,
            THREAD
          );
        }
        if (HAS_PENDING_EXCEPTION) {
          // tty is initialized early so we don't expect it to be null, but
          // if it is we can't risk doing an initialization that might
          // trigger additional out-of-memory conditions
          if (tty != NULL) {
            char klass_name[256];
            char tmp_sig_name[16];
            const char* sig_name = "UNKNOWN";
            InstanceKlass::cast(PENDING_EXCEPTION->klass())->
              name()->as_klass_external_name(klass_name, 256);
            if (os::exception_name(sig, tmp_sig_name, 16) != NULL)
              sig_name = tmp_sig_name;
            warning("Exception %s occurred dispatching signal %s to handler"
                    "- the VM may need to be forcibly terminated",
                    klass_name, sig_name );
          }
          CLEAR_PENDING_EXCEPTION;
        }

上面的核心代码是通过JavaCalls::call_static方法调用Singal类的dispatch方法完成信号的处理,dispatch方法从Signal的handlers中获取对应的handler,然后启动新的线程调用handler的handle方法。

    private static void dispatch(int var0) {
        final Signal var1 = (Signal)signals.get(var0);
        final SignalHandler var2 = (SignalHandler)handlers.get(var1);
        Runnable var3 = new Runnable() {
            public void run() {
                var2.handle(var1);
            }
        };
        if (var2 != null) {
            (new Thread(var3, var1 + " handler")).start();
        }

    }

INT和TERM信号的注册

在虚拟机初始化阶段,会调用call_initializeSystemClass,

static void call_initializeSystemClass(TRAPS) {
  Klass* k =  SystemDictionary::resolve_or_fail(vmSymbols::java_lang_System(), true, CHECK);
  instanceKlassHandle klass (THREAD, k);

  JavaValue result(T_VOID);
  JavaCalls::call_static(&result, klass, vmSymbols::initializeSystemClass_name(),
                                         vmSymbols::void_method_signature(), CHECK);
}

该方法会调用System.initializeSystemClass,这个方法内部会调用Terminator.setup方法完成INT和TERM信号的注册,

class Terminator {

    private static SignalHandler handler = null;

    /* Invocations of setup and teardown are already synchronized
     * on the shutdown lock, so no further synchronization is needed here
     */

    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("INT"), sh);
        } catch (IllegalArgumentException e) {
        }
        try {
            Signal.handle(new Signal("TERM"), sh);
        } catch (IllegalArgumentException e) {
        }
    }

    static void teardown() {
        /* The current sun.misc.Signal class does not support
         * the cancellation of handlers
         */
    }

}