linux 的线程和信号
基于 NPTL 的线程库,多线程应用中的每个线程有自己独特的线程 ID,并共享同一个进程ID。应用程序可以通过调用 kill(getpid(),signo)
将信号发送到进程,如果进程中当前正在执行的线程没有阻碍此信号,则会被中断,线号处理函数会在此线程的上下文背景中执行。应用程序也可以通过调用 pthread_kill(pthread_t thread, int sig)
将信号发送给指定的线程,则线号处理函数会在此指定线程的上下文背景中执行。
java里信号掩码的集合
unblocked_sigs | SIGILL SIGSEGV SIGBUS SIGFPE SR_signum SHUTDOWN1_SIGNAL(SIGHUP) SHUTDOWN2_SIGNAL(SIGINT) SHUTDOWN3_SIGNAL(SIGTERM) |
vm_sigs | BREAK_SIGNAL (SIGQUIT) |
allowdebug_blocked_sigs | SHUTDOWN1_SIGNAL(SIGHUP) SHUTDOWN2_SIGNAL(SIGINT) SHUTDOWN3_SIGNAL(SIGTERM) |
SIG_BLOCK | NULL |
在多线程的应用中,每个线程可以通过调用pthread_signmask()设置本线程的信号掩码,可以设置阻塞的信号,但信号SIGKILL/SIGSTOP是不能被设置成阻塞的。
在java中,每个线程都设置了在表格中的信号掩码,特别提到的是vm_sigs,这是只有一个quit 的信号结合,当没有设置启动参数 -Xrs (=ReduceSignalUsage)的时候,其他的线程设置成阻塞的,除了vm thread。
void os::Linux::hotspot_sigmask(Thread* thread) {
//Save caller's signal mask before setting VM signal mask
sigset_t caller_sigmask;
pthread_sigmask(SIG_BLOCK, NULL, &caller_sigmask);
OSThread* osthread = thread->osthread();
osthread->set_caller_sigmask(caller_sigmask);
pthread_sigmask(SIG_UNBLOCK, os::Linux::unblocked_signals(), NULL);
if (!ReduceSignalUsage) {
if (thread->is_VM_thread()) {
// Only the VM thread handles BREAK_SIGNAL ...
pthread_sigmask(SIG_UNBLOCK, vm_signals(), NULL);
} else {
// ... all other threads block BREAK_SIGNAL
pthread_sigmask(SIG_BLOCK, vm_signals(), NULL);
}
}
}
Java里信号处理的函数
在linux里可以设置进程级别的信号的处理函数,在内核中信号值及进程针对该信号的处理函数建立了映射关系,主要有2个函数来设置信号处理函数:signal(),sigaction()。函数具体可以参考http://www.ibm.com/developerworks/cn/linux/l-ipc/part2/index1.html
在java 里,下面的2个函数是用于设置信号处理的函数
void os::Linux::set_signal_handler(int sig, bool set_installed)
void* os:signal(int signal_number, void* handler)
- 函数set_signal_handler设置了信号的处理函数signalHandler
- 函数os:signal是允许外部直接设置信号处理函数
- 以下程序片段可以看到当每个java 线程起来的时候,对信号SIGSEGV ,SIGPIPE,SIGBUS,SIGILL,SIGFPE,SIGXGSZ,使用了专门的处理函数signalHandler
在signalhandler 调用了JVM_handler_linux_signal 而该函数在不同的架构下是不一样的,x86架构下的定义在os_linux_x86.cpp中。... 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); ......
- 对信号SIGQUIT源码中并没有看到处理函数,实际上当java虚拟机启动Signal Dispatcher 线程的时候,程序里调用了os::signal(SIGNBREAK,os::user_handler()) (os.cpp os::signal_init())(该线程在打开 -Xrs的时候是不启动的)
下面的源码是java里面定义的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);
}
在函数的末尾调用了os:signal_notify,从而和os::signal_wait 对应起来,关于signal dispatcher 的具体实现可以参考笔者的另一篇博客(http://blog.csdn.net/raintungli/article/details/7034005)。
signal dispatcher 线程通过sem_wait会在等待,当进程接到信号SIGQUIT的时候,只有vm thread会被中断(见上面分析),而进入UserHandler 函数,通过调用 os::signal_notify 去通告signal dispatcher 线程,让 signal dispatch 线程去处理信号。
在信号设计里,因为信号中断是在内核态调用的,内核调用了线程注入了自己的信号函数,一般只允许在该函数里处理简单的事物,所以在java里面专门设计了处理信号后续的线程(signal dispatcher),接受到信号的线程通过信号函数notify到处理信号的线程(signal dispatcher ),最后由该线程做后续的事情。比如线程dump
关于信号中断的内核态和用户态的如何嵌入,可以参考(Linux内核信号处理机制介绍)