Exception_catch
异常的来源
异常控制流(Exceptional Control Flow)
- 系统层
- 应用层
系统层的异常
异常(Exception),就是控制流中的突变,用来响应处理器状态中的某些变化。
示例图
当处理器检测到有异常事件发生时,它就通过一个叫做异常表(exception table)的跳转表,进行一个间接过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序(exception handler))。当异常处理程序完成处理后,根据引起异常的事件的类型,会发生一下 3 中情况中的一种:
- 处理程序将控制返回给当前指令 Icurr,
- 处理程序将控制返回给 Inext
- 处理程序终止被中断的程序。即“崩溃”
异常处理
异常的类别
异常可以分为四类:中断、陷阱、故障、终止
类别 | 原因 | 异步/同步 | 返回行为 |
---|---|---|---|
中断 | 来自 I/O 设备的信号 | 异步 | 总是返回到下一条指令 |
陷阱 | 有意的异常 | 同步 | 总是返回到下一条指令 |
故障 | 潜在可恢复到错误 | 同步 | 可能返回到当前指令 |
终止 | 不可回复的错误 | 同步 | 不会返回 |
终止
终止是不可恢复的知名错误造成的结果,通常是一些硬件错误,但是某些严重疏忽导致的 Bug 也会终止。终止处理程序从不将控制返回给应用程序,而是讲控制返回给一个 abort 例程,该例程会终止这个应用程序——崩溃。
进程的终止
进程会因为三种原因终止:
- 收到一个信号,该信号的默认行为是终止进程;
- 从主程序返回;
- 调用 exit 函数;
信号
一个信号就是一条小消息,它通知jinching系统中发生了一个某种类型的事件。每种信号类型都对应于某种系统事件。此层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程是不可见的。信号提供了一种机制,通知用户进程发生了这些异常。
下图展示了系统定义的不同类型的信号:
/* */ #define SIGHUP 1 /* hangup */ #define SIGINT 2 /* interrupt */ #define SIGQUIT 3 /* quit */ #define SIGILL 4 /* illegal instruction (not reset when caught) */ #define SIGTRAP 5 /* trace trap (not reset when caught) */ #define SIGABRT 6 /* abort() */ #if (defined(_POSIX_C_SOURCE) && !defined(_DARWIN_C_SOURCE)) #define SIGPOLL 7 /* pollable event ([XSR] generated, not supported) */ #else /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */ #define SIGIOT SIGABRT /* compatibility */ #define SIGEMT 7 /* EMT instruction */ #endif /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */ #define SIGFPE 8 /* floating point exception */ #define SIGKILL 9 /* kill (cannot be caught or ignored) */ #define SIGBUS 10 /* bus error */ #define SIGSEGV 11 /* segmentation violation */ #define SIGSYS 12 /* bad argument to system call */ #define SIGPIPE 13 /* write on a pipe with no one to read it */ #define SIGALRM 14 /* alarm clock */ #define SIGTERM 15 /* software termination signal from kill */ #define SIGURG 16 /* urgent condition on IO channel */ #define SIGSTOP 17 /* sendable stop signal not from tty */ #define SIGTSTP 18 /* stop signal from tty */ #define SIGCONT 19 /* continue a stopped process */ #define SIGCHLD 20 /* to parent on child stop or exit */ #define SIGTTIN 21 /* to readers pgrp upon background tty read */ #define SIGTTOU 22 /* like TTIN for output if (tp->t_local<OSTOP) */ #if (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)) #define SIGIO 23 /* input/output possible signal */ #endif #define SIGXCPU 24 /* exceeded CPU time limit */ #define SIGXFSZ 25 /* exceeded file size limit */ #define SIGVTALRM 26 /* virtual time alarm */ #define SIGPROF 27 /* profiling time alarm */ #if (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)) #define SIGWINCH 28 /* window size changes */ #define SIGINFO 29 /* information request */ #endif #define SIGUSR1 30 /* user defined signal 1 */ #define SIGUSR2 31 /* user defined signal 2 */
信号的传递
一个信号传递到目标进程有两个不同的步骤组成:
发送信号。内核通过更新目的进程上下文中的某个状态,发送一个信号给目的进程。发送信号可以有一下两种原因:
- 内核检测到一个系统事件,例如除零错误或者子进程终止;
- 一个进程调用了 Kill 函数,显示地要求内核发送一个信号给目的进程。
接受信号。当目的进程被内核强迫以某种方式对信号的发送做出反应时,它距接收了信号。进程可以忽略这个信号,终止或者执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号。
接收信号
当内核把进程 p 从内核模式切换到用户模式时,它会检查进程 p 的未被阻塞的待处理信号的集合。如果这个集合为空,那么内核讲控制传递到 p 的逻辑控制流中的下一条指令。然而,如果集合是非空的,那么内核选择集合中的某个信号 k(通常是最小的 k),并且强制 p 接收信号 k。收到这个信号会触发进程采取某种行为。一旦进程完成了这个行为,那么控制就传递回 p 的逻辑控制流中的下一条指令。每个信号类型都有一个与定义的默认行为:
- 进程终止
- 进程终止并转储内存
- 进程停止(挂起)直到被 SIGCONT 信号重启
- 进程忽略该信号
进程可以通过使用 signal 函数修改和信号相关联的默认行为。唯一的例外是 SIGSTOP 和 SIGKILL,它们的默认行为是不能修改的。
那么怎么修改呢?signal 函数可以通过下列三种方法之一来改变和信号 signum 相关联的行为:
- 如果 handler 是 SIG_IGN,那么忽略类型为 signum 的信号
- 如果 handler 是 SIG_DFL,那么类型为 signum 的信号行为恢复为默认行为
- 否则,handler 就是用户定义的函数的地址,这个函数被称为信号处理程序,只要进程接收到一个类型为 signum 的信号,就会调用这个程序。通过处理程序的地址传递到 signal 函数从而改变默认行为。这被称为信号处理程序(instlalling the handler)。调用信号处理程序被称为捕获信号。执行信号处理程序被称为处理信号。
当一个进程捕获来一个类型为 k 的信号时,会调用为信号 k 设置的处理程序,一个蒸熟参数被设置为 k。这个参数允许同一个处理函数捕获不同类型的信号。
至此,了解到的这些操作系统知识,已经足够支持我们对系统层的崩溃信号进行捕获了,接下就到了 Coding time。