Linux异常与信号处理
1.1异常基本概念
在Linux系统中,异常通常指的是打断CPU正常执行流程的事件,这些事件可以是程序错误、硬件中断或其他需要立即处理的情况。
异常一般是同步发生的(中断是异步),这意味着它们是由当前正在执行的指令直接导致的。异常包括但不限于以下几种:
中断(interrupt):通常是异步发生的,即与当前正在执行的指令无关,它是由外部设备(如键盘、鼠标、硬盘等)发出的信号所引起的。
陷阱(trap):提供用户程序和操作系统之间的接口,例如系统调用。
故障(fault):可能由程序尝试访问不存在的内存地址引起,这类异常有可能被修复,并且如果修复成功,则会重新执行引起故障的指令。
终止(abort):这是一种严重的错误,通常不会被恢复,而是直接导致程序终止执行。
异常模块实现功能:在系统平台发生异常时,对异常进行截获,保存异常现场信息,定位异常的发生点,对异常进程的堆栈进行回溯分析以获取异常时的函数调用关系,提供应用层函数调用接口以自定义开发的异常处理策略。
1.2信号
信号是一种进程间通信的机制,信号都有一个默认的处理行为,对应于某种系统事件。信号触发时,信号处理函数和进程正常的执行流程同时存在,这会给编程带来隐患,如果信号处理函数中调用了不可重入函数的话。信号同其他进程间通信技术(管道、共享内存)相比,传递的信息还是有限的,由于信息较少所以也方便管理,一般在系统管理中使用,比如终止或者恢复进程等。信号的接收不是有用户进程来完成,需要内核来代理。当用户进程P2向另一个进程P1发送信号后,内核接收到信号,将信号放置P1进程的信号队里中。
当P1进程进入内核态时,会检查信号队列,并调取相应的信号函数进行处理。
信号的核心作用:
通知异常事件:例如除零错误(SIGFPE)、非法内存访问(SIGSEGV)、用户输入中断(SIGINT)等。
进程间通信:进程可以通过 kill() 函数主动发送信号(如 SIGKILL 终止其他进程)。
异步事件处理:信号允许进程在正常执行流程中被中断,以处理紧急或关键事件。
进程可以对信号采取以下处理方式:
默认动作:由内核定义,如终止(SIGTERM)、忽略(SIGCHLD)或产生核心转储(SIGSEGV)。
自定义处理函数(Signal Handler):通过 signal() 或 sigaction() 注册处理函数,实现特定逻辑。
忽略信号:显式忽略某些信号(如 SIGINT),但某些信号(如 SIGKILL、SIGSTOP)不可忽略。
常见信号及对应发生场景:
#define SIGHUP 1 //- 该信号让进程立即关闭.然后重新读取配置文件之后重启
#define SIGINT 2 //- 程序中止信号,用于中止前台进程。相当于输出 Ctrl+C 快捷键
#define SIGQUIT 3 //- 键盘的退出键被按下 ESC
#define SIGILL 4 //- CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件。
#define SIGTRAP 5 //- 该信号由断点指令或其他trap指令产生
#define SIGABRT 6 //- 由abort(3)发出的退出指令
#define SIGBUS 7 //- 非法地址, 包括内存地址对齐出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。
#define SIGFPE 8 //- 浮点异常,不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误
#define SIGKILL 9 //- 无条件终止进程。本信号不能被忽略、处理和阻塞。默认动作为终止进程。
#define SIGUSR1 10 //- SIGUSR1:用户定义的信号,即程序可以在程序中定义并使用该信号。默认动作为终止进程。
#define SIGSEGV 11 //- 无效的内存引用,试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。访问空指针,野指针基本都产生这个信号,也是最常见的信号
#define SIGUSR2 12 //- 这是另外一个用户定义信号,程序员可以在程序中定义并使用该信号。
#define SIGPIPE 13 //- 写管道,但是管道的读端已经终止,会产生此信号
#define SIGALRM 14 //- 由alarm(2)发出的信号
#define SIGTERM 15 //- 由kill(1)命令发送的终止信号
#define SIGSTKFLT 16 //- 堆栈错误
#define SIGCHLD 17 //- 子进程结束,将CHLD信号发送给父进程
#define SIGCONT 18 //- 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作.
#define SIGSTOP 19 //- 该信号可以暂停前台进程,相当于输入 Ctrl+Z 快捷键。本信号不能被阻断
#define SIGTSTP 20 //- 停止进程的动作,但该信号可以被处理和忽略。按下组合键时发出该信号。默认动作为暂停进程。
#define SIGTTIN 21 //- 当后台进程要从用户终端读数据时,该终端中的所有进程会收到SIGTTIN信号。默认动作为暂停进程。
#define SIGTTOU 22 //- 该信号类似于SIGTIN,在后台进程要向终端输出数据时产生。默认动作为暂停进程。
#define SIGURG 23 //- 套接字(socket)上有紧急数据时,向当前正在运行的进程发出此信号,报告有紧急数据到达。默认动作为忽略该信号。
#define SIGXCPU 24 //- 进程执行时间超过了分配给该进程的CPU时间,系统产生该信号并发送给该进程。默认动作为终止进程。
#define SIGXFSZ 25 //- 进程超过了其软文件长度的限制
信号处理流程:
block表:位图结构,比特位的位置表示哪个信号,比特位的内容表示是否对应信号被阻塞。
pending表:位图结构,比特位的位置表示哪个信号,比特位的内容表示是否收到信号。
handler表:函数指针数组:数组的下标表示信号的编号,数组特定的下标的内容表示该信号的递达动作,信号怎么处理。当处理信号的时候,会先进行(int)handler[signal]==0的判断,如果成立的话就执行的是默认动作;如果不是,再用(int)handler[signal]==1的判断,如果成立就执行忽略动作;如果还不是,就执行的是handlersignal;
完整流程:os发送信号,pending位置写入,处理信号的时候先到pending的位图中找1,找到了再查看block位图中对应信号位置是否为1,若为1,则不进行当前位信号的后续处理,为0才去handler中执行后续处理操作。
实际执行信号的处理动作称为:信号递达;信号到产生到递达之间的状态:信号未决。进程可以选择阻塞某个信号(block),被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
注意:阻塞和忽略不一样,只要信号被阻塞就不会递达,而忽略是递达之后可选的一种处理动作。
2.黑匣子是利用单板的保留内存在单板不掉电的情况下内容不会丢失的特性,将单板运行过程中的一些信息(包括状态数据,异常数据等)记录下来,存储到flash中用于日常的问题分析。黑匣子初始化后开始记录新的黑匣子数据,黑匣子记录分为以下几类:
重启类记录。只需要记录一条信息,在系统重启、异常的时候添加到黑匣子中。
过程类记录。不定时记录,一般有若干条记录,它由一定事件触发后,记录一些相关的信息。比如当进程创建时,链路中断时,记录一些信息到黑匣子,无拍照函数。
状态类记录。需要每隔一定时间获取其值的记录,一般有若干条记录,由循环记录记录最近的一定数量的记录条数,它由拍照函数获取最新的记录值。这类记录包括一些重要的需要实时监控的信息:CPU状态等。