Signal机制

32 篇文章 1 订阅
2 篇文章 0 订阅

信号(signal)是一种进程间通信机制,它给应用程序提供一种异步的软件中断,使应用程序有机会接受其他程序活终端发送的命令(即信号)。应用程序收到信号后,有三种处理方式:忽略,默认,或捕捉。进程收到一个信号后,会检查对该信号的处理机制。如果是SIG_IGN,就忽略该信号;如果是SIG_DFT,则会采用系统默认的处理动作,通常是终止进程或忽略该信号;如果给该信号指定了一个处理函数(捕捉),则会中断当前进程正在执行的任务,转而去执行该信号的处理函数,返回后再继续执行被中断的任务。

Java中提供了Signal的机制,Signal机制在Linux中是一个非常常用的进程间通信机制。在sun.misc包下,属于非标准包。重要涉及到两个类:Signal和SignalHandler。其中Signal主要使用了静态方法Signal.handle(Signal, SignalHandler),而SignalHandler是一个接口,有一个抽象方法void handle(Signal var1);需要我们自己实现SignalHandler接口处理。

在Linux下支持的信号(具体信号kill -l命令查看):
SEGV, ILL, FPE, BUS, SYS, CPU, FSZ, ABRT, INT, TERM, HUP, USR1, USR2, QUIT, BREAK, TRAP, PIPE

kill  -15  ${pid} 来杀死程序,这样,在jvm退出的时候,会将ShutdownHook中的内容执行一遍,这样就可以在里面定义自己的资源释放(一般是连接池)的操作了 。

不要使用kill -9  ${pid}  ,这个会直接杀死jvm的进程,hook是来不及执行的,kill后面的数字参数如下:

HUP     1    终端断线
INT     2    中断(同 Ctrl + C)
QUIT    3    退出(同 Ctrl + \)
TERM    15   终止
KILL    9    强制终止
CONT    18   继续(与STOP相反, fg/bg命令)
STOP    19   暂停(同 Ctrl + Z)

默认的kill参数是 -15 (TERM) .

在Windows下支持的信号:

SIGINT(INT)     Ctrl+C中断

SIGILL (ILL)      非法指令

SIGFPE(FPE)      浮点异常

SIGSEGV(SEGV)   无效的内存引用

SIGTERM(TERM)   kill发出的软件终止

SIGBREAK(BREAK) Ctrl+Break中断

SIGABRT(ABRT)   调用abort导致

一、信号类型

Linux系统共定义了64种信号,分为两大类:可靠信号与不可靠信号,前32种信号为不可靠信号,后32种为可靠信号。

  • 不可靠信号: 也称为非实时信号,不支持排队,信号可能会丢失, 比如发送多次相同的信号, 进程只能收到一次. 信号值取值区间为1~31;

  • 可靠信号: 也称为实时信号,支持排队, 信号不会丢失, 发多少次, 就可以收到多少次. 信号值取值区间为32~64

一、首先看下java中Signal枚举有哪些各代表什么含义:每个新号都对应一个整数值 

SIGUSR1 (USR1):用户自定义信号1(10) 默认处理:进程终止
SIGUSR2 (USR2):用户自定义信号2(12) 默认处理:进程终止

推荐:用户自定义信号USR1和USR2,其他信号我同事说可能会隐患别的问题,最好不用.

TERM:终止信号(15)

KILL:Kill信号(9)

INT:键盘中断(2)

HUP :终端挂起或者控制进程终止(1)

BUS:总线错误(7)

上面这5个出现的比较多,下面是其他的信号

ALRM:警告

CHLD:子进程结束信号

CONT:进程继续(曾被停止的进程)

FPE:浮点异常

ILL:非法指令

IO:某I/O操作现在可以进行了

IOT:IO捕获指令

PIPE:管道破裂: 写一个没有读端口的管道

PROF:Profiling定时器到

PWR:电源故障

QUIT:键盘的退出键被按下

SEGV:无效的内存引用

STKFLT:协处理器堆栈错误

STOP:终止进程

TRAP:跟踪/断点捕获

TSTP:控制终端(tty)上按下停止键

TTIN:后台进程企图从控制终端读

TTOU:后台进程企图从控制终端写

VTALRM: 实际时间报警时钟信号

WINCH:窗口大小改变

XCPU:超出设定的CPU时间限制

XFSZ:超出设定的文件大小限制

二、信号产生

信号来源分为硬件类和软件类:

2.1 硬件方式

  • 用户输入:比如在终端上按下组合键ctrl+C,产生SIGINT信号;
  • 硬件异常:CPU检测到内存非法访问等异常,通知内核生成相应信号,并发送给发生事件的进程;

2.2 软件方式

通过系统调用,发送signal信号:kill(),raise(),sigqueue(),alarm(),setitimer(),abort()

  • kernel,使用 kill_proc_info()等
  • native,使用 kill() 或者raise()等
  • java,使用 Procees.sendSignal()等

Linux中signal机制的模型:

每个进程都会采用一个进程控制块对其进行描述,进程控制块中设计了一个signal的位图信息,其中的每位与具体的signal相对应,这与中断机制是保持一致的。当系统中一个进程A通过signal系统调用向进程B发送signal时,设置进程B的对应signal位图,类似于触发了signal对应中断。发送signal只是“中断”触发的一个过程,具体执行会在两个阶段发生:

1、  system call返回。进程B由于调用了system call后,从内核返回用户态时需要检查他拥有的signal位图信息表,此时是一个执行点。

2、  中断返回。进程被系统中断打断之后,系统将CPU交给进程时,需要检查即将执行进程所拥有的signal位图信息表,此时也是一个执行点。

综上所述,signal的执行点可以理解成从内核态返回用户态时,在返回时,如果发现待执行进程存在被触发的signal,那么在离开内核态之后(也就是将CPU切换到用户模式),执行用户进程为该signal绑定的signal处理函数,从这一点上看,signal处理函数是在用户进程上下文中执行的。当执行完signal处理函数之后,再返回到用户进程被中断或者system call(软中断或者指令陷阱)打断的地方。

 Signal机制实现的比较灵活,用户进程由于中断或者system call陷入内核之后,将断点信息都保存到了堆栈中,在内核返回用户态时,如果存在被触发的signal,那么直接将待执行的signal处理函数push到堆栈中,在CPU切换到用户模式之后,直接pop堆栈就可以执行signal处理函数并且返回到用户进程了。Signal处理函数应用了进程上下文,并且应用实际的中断模拟了进程的软中断过程。

import sun.misc.*;

@SuppressWarnings("restriction")
public class TestSignal implements SignalHandler {

    public void handle(Signal arg0) {
        System.out.println(arg0.getName() + "is recevied.");
        System.exit(0);
    }
}

import sun.misc.*;

public class App {
    @SuppressWarnings("restriction")
    public static void main(String[] args) {
        TestSignal handler = new TestSignal();
        Signal.handle(new Signal("TERM"), handler);
        Signal.handle(new Signal("INT"), handler);
        Signal.handle(new Signal("ILL"), handler);
        for (;;) {
            System.out.println("do something");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

深入JVM关闭与关闭钩子

Java虚拟机退出包括两个阶段:

第一个阶段:会以某种未指定的顺序启动所有已注册钩子,并且允许它们同时运行直至结束

第二个阶段:如果已启用runFinalizersOnExit设置为true,则运行所有未调用的终结方法(finalizer方法)

System.exit()底层调用的Runtime.getRuntime().exit(),调用后终止当前正在运行的Java虚拟机。参数作为状态代码,按照惯例,一个非零状态码表示异常终止。

前面说到, Signal的一大作用是关闭进程, 然而Java提供了Shutdown Hook(关闭钩子)机制,它让我们在程序正常退出或者发生异常时能有机会做一些清场工作。

关闭钩子使用的方法也很简单,Runtime.getRuntime().addShutdownHook(Thread hook)即可。关闭钩子其实可以看成是一个已经初始化了的但还没启动的线程,当JVM关闭时会并发地执行注册的所有关闭钩子

JVM的关闭方式可以分为三种:

  • 正常关闭:当最后一个非守护线程结束或者调用了System.exit或者通过其他特定平台的方法关闭(发送SIGINT,SIGTERM信号等)
  • 强制关闭:通过调用Runtime.halt方法或者是在操作系统中直接kill(发送SIGKILL信号)掉JVM进程
  • 异常关闭:运行中遇到RuntimeException异常, OOM错误等。

注意: Hook线程在JVM 正常关闭才会执行,在强制关闭和异常关闭时不会执行。Spring在初始化容器的时候就会注册一个hook线程用于清理容器.

  • 不能在钩子调用System.exit(),否则卡住JVM的关闭过程,但是可以调用Runtime.halt()。halt方法不会执行钩子函数和finalizer方法,而是直接退出。
  • 不能再钩子中再进行钩子的添加和删掉操作,否则将会抛出IllegalStateException。
  • System.exit()之后添加的钩子无效。
  • 当JVM收到SIGTERM命令(比如操作系统在关闭时)后,如果钩子线程在一定时间没有完成,那么Hook线程可能在执行过程中被终止。
  • Hool线程中同样会抛出异常,如果抛出异常又不处理,那么钩子的执行序列就会被停止。
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值