Unix信号处理

devtools/2024/10/25 15:29:20/

1.引言
信号是软件中断。很多比较重要的应用程序都需要处理信号。信号提供了一种处理异步事件的方法:终端用户键入中断键,则会通过信号机构停止一个程序。

2.信号的概念
首先,每个信号都有一个名字。这些名字都以三个字符SIG开头。例如,SIGABRT是夭折信号,当进程调用abort函数时产生这种信号。SIGALRM是闹钟信号,当由alarm函数设置的时间已经超过后产生此信号。
在头文件signal.h中,这些信号都被定义为正整数。没有一个信号其编号为0.因为编号0在系统调用kill函数中会有特殊的处理,系统调用kill 用来向进程传递信号。
很多条件可以产生一个信号。

(1)当用户按某些终端键时,产生信号。在终端按delete键通常产生中断信号(SIGINT)。这是一个停止一个已经失去控制程序的方法。
(2)硬件异常产生信号:除数为0,无效的存储访问等等。这些条件通常由硬件检测到,并将其通知内核。然后内核为条件发生时正在运行的进程产生适当的信号。例如,对执行一个无效存储访问的进程产生SIGSEGV。
(3)进程可以通过kill函数将信号发送给另一个进程或者进程组。自然,有些限制:接受信号进程和发送信号进程的所有者必须相同。或发送信号进程的所有者必须是超级用户。
(4)当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。例如在网络连接上传来非规定波特率的数据,会发送SIGURG信号,在管道的读进程终止后,写进程继续写管道,会产生SIGPIPE信号,闹钟时间到会产生SIGALRM信号

进程可以在信号发生时,执行下列操作:

(1)忽略此信号。大多数信号都可以这么处理,但SIGKILL和SIGSTOP两种信号不能被忽略,因为这两个信号向超级用户提供一种使进程终止或者停止的可靠方法。
(2)捕捉信号。为了做到这一点,要提前把自己的函数注册到该信号相关的回调函数里,当发生信号时,系统会调用已经注册的回调函数执行。
(3)执行默认动作。大多数信号的默认动作是终止进程

Unix信号说明:

(1)SIGABRT:调用abort函数时产生此信号,进程异常终止
(2)SIGALRM:超过用alarm函数设置的时间时产生此信号。若settitimer函数设置的间隔时间已经超时,那么也产生此信号
(3)SIGBUS:指示一个实现定义的硬件故障
(4)SIGCHLD:在一个进程终止或停止时,SIGCHLD信号被送给其父进程。按系统默认,将忽略此信号。如果父进程希望了解其子进程的这种状态改变,则应该捕捉此信号。信号捕捉函数中通常要调用wait函数以取得子进程ID和其终止状态。
(5)SIGCONT:此作业控制信号送给需要继续运行的处于停止状态的进程。如果接受到此信号进程处于停止状态,则系统默认动作是使得该进程继续运行,否则默认动作是忽略此信号。
(6)SIGEMT:指示一个实现定义的硬件故障。 (7)SIGFPE:此信号表示一个算术运算异常,例如除以0,浮点溢出等。
(8)SIGHUP:如果终端界面检测到一个连接断开,则将此信号送给与该终端相关的控制进程。此信号被送给session结构中s_leader字段所指向的进程。仅当终端的CLOCAL标志没有设置时,在上述条件下才产生此信号。如果所连接的终端是本地的,才设置该终端的CLOCAL标志。它告诉终端驱动程序忽略所有调制解调器的状态行。注意,接到此信号的对话期首进程可能在后台。如果对话期前进程终止,也产生此信号,在这种情况总是传递给前台进程组。
通常用此信号通知精灵进程以再读他们的配置文件。选用SIGHUP的理由是,因为一个精灵进程不会有一个控制终端。
(9)SIGILL:此信号指示进程已经执行一条非法硬件指令
(10)SIGINFO:这是一种4.3+BSD信号,当用户按状态键(一般是Ctrl-T),终端驱动进程产生此信号并送至前台进程组中的每一个进程,此信号通常造成在终端上显示前台进程组各进程的状态信息。
(11)SIGINT:当用户按中断键(delete或者Ctrl-C)时,终端驱动程序产生此信号并送至前台进程组中的每一个进程。当一个进程在运行时失控,特别是它正在屏幕上产生大量不需要的输出时,常用此信号终止它。
(12)SIGIO:此信号指示一个异步IO事件。 (13)SIGIOT:这指示一个实现定义的硬件故障
(14)SIGKILL:这是两个不能被捕捉或者忽略的信号的其中一个。它向系统管理员提供了一种可以杀死任一进程的可靠方法。
(15)SIGPIPE:如果在读进程已经终止时写管道,则产生此信号。若进程写该套接口也产生此信号。
(16)SIGPOLL:这是一种SVR4信号,当在一个可轮询设备上发生一特定事件时产生此信号。
(17)SIGPROF:当setitimer函数设置的间隔时间已经超过时产生此信号。
(18)SIGPWR:这是一种SVR4信号,它依赖于系统。它主要用于具有不间断电源的系统上。如果电源失效,则UPS起作用,而且通常软件会接到通知。在这种情况下,系统依靠蓄电池电源继续运行,所以无需做任何处理。但是如果蓄电池也将不能支持工作,则软件通常会再次接到通知,此时,它在15-30秒内使系统各部分都停止运行。此时应当传递SIGPWR信号。在大多数系统中接到蓄电池电压过低的进程将信号SIGPWR发送给init进程,然后由init处理停机操作。
(19)SIGQUIT:当用户在终端上按退出键(一般Ctrl-\)时,产生此信号,并送至前台进程组中所有进程。此信号不仅终止前台进程组,同时产生一个core文件。
(20)SIGSEGV:指示进程进行了一次无效的存储访问
(21)SIGSTOP:这是一个作业控制信号,它停止一个进程。它类似于交互停止信号(SIGTSTP),但是SIGSTOP不能被捕捉或忽略。
(22)SIGSYS:指示一个无效的系统调用。由于某种未知原因,进程执行了一条系统调用指令,但是其指示系统调用类型的参数是无效的。
(23)SIGTERM:这是由kill命令发送的系统默认终止信号。 (24)SIGTRAP:指示一个实现定义的硬件故障
(25)SIGTSTP:交互停止信号,当用户在终端上按挂起键(Ctrl-Z)时,终端驱动产生此信号。
(26)SIGTTIN:当一个后台进程组进程试图读其控制终端时,终端驱动程序产生此信号。在下列情形下不产生此信号,此时读操作返回出错,errno设置EIO:读进程忽略或阻塞此信号,或者读进程所属的进程组是孤儿进程组。
(27)SIGTTOU:当一个后台进程组进程试图写其控制终端时产生此信号。一个进程可以选择为允许后台进程写控制终端。
(28)SIGURG:此信号通知进程已经发生了一个紧急情况。在网络连接上,接到非规定波特率的数据时,此信号可选择地产生。
(29)SIGUSR1:这是一个用户定义的信号,可用于应用程序 (30)SIGUSR2:这是一个用户定义的信号,可用于应用程序。
(31)SIGVTALRM:当一个由setitimer函数设置的虚拟间隔时间已经超过时产生此信号。
(32)SIGWINCH:如果一个进程用ioctl的设置-窗口-大小命令更改了窗口的大小,则内核将SIGWINCH信号送至前台进程组。
(33)SIGXCPU:如果进程超过了其软CPU时间限制,则产生此信号 (34)SIGXFSZ:如果进程超过了其软文件长度限制

3.signal函数

#include<signal.h>
void (*signal(int signo, void (func)(int))) (int);
返回:成功则把func设置为signo的处理函数,错误则返回SIG_ERR

signo参数是信号名。func的值可以是(a)SIG_IGN,(b)SIG_DFL(c)当接到此信号后要调用的函数的地址。如果指定了SIG_IGN,则向内核表示忽略此信号(SIGKILL和SIGSTOP不能忽略)。如果指定了SIG_DFL,则表示接到此信号后的动作是系统默认动作。当指定函数地址时,我们称此为捕捉此信号。我们称此函数为信号处理程序或者信号捕捉函数。
signal函数的原型说明此函数要求两个参数,返回一个函数指针,而该指针所指向的函数无返回值。第一个参数signo是一个整形,第二个参数时函数指针,它所指向的函数需要一个整形参数,无返回值。

程序启动:
当执行一个程序时,所有信号的状态都是系统默认或者忽略。通常所有信号都被设置为系统默认动作,除非调用exec的进程忽略该信号。比较特殊的是,exec函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不变(一个进程原先要捕捉的信号,当其执行一个新程序后,就自然不能再被捕捉了,因为信号捕捉函数的地址很可能在所执行的新程序文件中已无意义)。
进程创建:
当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为子进程在开始时复制了父进程存储图像,所以信号捕捉函数的地址在子进程中是有意义的。

4.不可靠信号
早期的Unix版本中(例如v7),信号是不可靠的。不可靠在这里指的是,信号可能会被丢失。那时,进程对信号的控制能力也很低,它能捕捉信号或忽略它,但有些很需要的功能它却并不具备。例如,有时用户希望通知内核阻塞信号,在其发生时记住它,然后进程做好了准备时再通知它。这种阻塞能力当时并不具备。

5.中断的系统调用
早期Unix系统的一个特性是:如果在进程执行一个低俗系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其errno设置EINTR。这样处理的理由是:因为一个信号发生了,进程捕捉到了它,这意味着已经发生了某种事情,所以是个好机会应当唤醒阻塞的系统调用。
为了支持这种特性,将系统调用分成两类:低速系统调用和其他系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用,他们包括:

(1)在读某些类型的文件时,如果数据不存在则可能使调用永远阻塞(管道,终端设备以及网络设备)
(2)写某些类型的文件时,如果不能立即接受这些数据,则也可能会使调用者永远阻塞
(3)打开文件,在某种条件发生之前也可能会使调用者阻塞(例如,打开终端设备,它要等到直到所连接的调制解调器回答了电话)
(4)pause和wait(进程睡眠直到捕捉信号)
(5)某些ioctl操作
(6)进程间的某些通信函数

6.kill函数和raise函数
kill函数将信号发送给进程或进程组。raise函数则将允许进程向自身发送信号。

#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);

kill的pid参数有四种不同情况:pid>0表示进程ID为pid的进程。pid0表示将信号发送给其进程组ID。pid<0表示将信号发送给其进程组ID等于pid绝对值的进程,pid-1未定义。

7.alarm 和 pause函数
使用alarm函数可以设置一个时间值,在将来的某个时刻该时间值会被超过。当所设置的时间值被超过后,产生SIGALRM信号。默认动作终止该进程。

#include<unistd.h>
unsigned int alarm(unsigned int seconds)

pause函数是进程挂起直到捕捉到一个信号

8.信号集
我们需要有一个能表示多个信号–信号集的数据类型。将在sigprocmask这样的函数中使用这种数据类型,以告诉内核不允许发生该信号集中的信号。如前所述,信号种类数目可能超过一个整形量所包含的位数,所以一般而言,不能用整形数的一位表示一种信号。POSIX.1定义数据类型sigset_t以包含一个信号集,并且定义了下列五个处理信号级的函数

#include<signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

函数sigemptyset初始化由set指向的信号集,使排除器中所有信号。函数sigfillset初始化由set指向的信号集,使其包含所有信号。所有应用程序在使用信号集前,要对该信号集调用sigemptyset或者sigfillset一次。这是因为C编译程序将不再赋初值的外部和静态变量都初始化为0。
初始化后的信号集,可以在该信号集中增删信号。函数sigaddset将一个信号添加到现存集中,sigdelset则从信号集中删除一个信号。

9.sigprocmask函数
一个进程的信号屏蔽规定了当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或者更改进程的信号屏蔽字。

#include<signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

首先,oset是非空指针,进程的当前信号屏蔽字通过oset返回。其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
how可选用的值:

SIG_BLOCK:该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。
SIG_UNBLOCK:该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的交集。set包含了我们希望接触阻塞的信号。
SIG_SETMASK:该进程新的信号屏蔽字是set指向的值

如果set是个空集,则不改变该进程的信号屏蔽字,how的值也无意义。

10.sigpending函数
sigpending返回对于调用进程被阻塞不能递送和当前未决的信号集。该信号集通过set参数返回。

#include<signal.h>
int sigpending(sigset_t *set);

12.sigaction函数
sigaction函数的功能是检查或修改与指定信号相关联的处理动作。此函数取代了UNIX早期版本使用的signal函数。
#include <signal.h>
int sigaction(int signo, const struct sigaction * act, struct sigaction * oact);
其中,参数signo是要检测或修改具体动作的信号的编号数。若act指针非空,则要修改其动作。如果oact指针非空,则系统返回该信号的原先动作。

struct sigaction{void (*sa_handler) ();sigset_t sa_mask;int sa_flags;
}

当更改信号动作时,如果sa_handler指向一个信号捕捉函数(不是常数SIG_IGN或者SIG_DFL),则sa_mask字段说明了一个信号集,在调用信号捕捉函数之前,该信号集要加到进程的信号屏蔽集。仅当从信号捕捉函数返回时再将进程的信号屏蔽字恢复为原始值。这样,在调用信号处理程序时就能阻塞某些信号。在信号处理程序被调用时,系统建立的新信号屏蔽字会自动包括正在被递送的信号。因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。若同一种信号多次发生,通常不进行排队处理,其信号处理函数通常只会被调用一次。
一旦对给定的信号设置了一个动作,那么在用sigaction改变它之前,该设置就一直有效。
act结构的sa_falgs字段包含了对信号进行处理的各个选择项。如下:

SA_NOCLDSTOP:若signo是SIGCHLD,当一子进程停止时(作业控制), 不产生此信号。当一子进程终止时,仍旧产生此信号。
SA_RESTART:由此信号中断的系统调用自动再启动
SA_ONSTACK:若用siglatstack已说明了一替换栈,则此信号递送给替换栈上的进程
SA_NOCLDWAIT:若signo时SIGCHLD,则当调用进程的子进程终止时,不创建僵死进程。若调用进程在后面调用wait,则阻塞到它所有子进程都终止。此时返回-1,errno设置为ECHILD
SA_NODEFER:当捕捉到此信号时,在执行其信号捕捉函数时,系统不自动阻塞此信号。注意,此种类型的操作对应于早期的不可靠信号
SA_RESETHAND:对此信号的处理方式在此信号捕捉函数的入口处设置为SIG_DFL。注意此种类型的信号对应于早期的不可靠信号
SA_SIGINFO:此选项对信号处理程序提供了附加信息。

13.sigsetjmp和siglongjmp函数
信号处理程序中调用longjmp函数以返回到程序的主循环中,而不是从该处理程序返回。
调用longjmp时有一个问题。当捕捉到一个信号时,进入信号捕捉函数,此时当前的信号被自动加到进程的信号屏蔽字中。者阻止了后来产生的这种信号中断此信号处理程序。为了允许屏蔽和非屏蔽两种形式并存,POSIX.1定义了两个新韩淑sigsetjmp和siglongjmp。

#include<setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask);
void siglongjmp(sigjmp_buf env, int val);

如果savemask非0,则sigsetjmp在env保存进程的当前信号屏蔽字。调用siglongjmp时,如果带非0的savemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。

14.sigsuspend函数
更改进程的信号屏蔽字可以阻塞或解除阻塞所选择的信号。使用这种技术可以保护不希望由信号中断的代码临界区。如果希望对一个信号接触阻塞,然后pause以等待以前被阻塞的信号发生,则又将如何呢?如果在解除对SIGINT的阻塞和pause之间发生了SIGINT信号,则此信号被丢失。这是早期的不可靠信号机制的一个问题,为了纠正此问题,需要一个原子操作实现恢复信号屏蔽字,然后使其进程睡眠,这种功能由sigsuspend函数所提供的。

#include<signal.h>
int sigsuspend(const sigset_t sigmask);
返回:成功无返回值,失败返回-1,errno设置为EINTR

进程的信号屏蔽字设置由sigmask指向的值。在捕捉到要给信号或发生了一个会终止该进程的信号之前,该进程也被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且该进程的信号屏蔽字设置为调用sigsuspend之前的值。

15.作业控制信号
六个POSIX.1认为是与作业控制有关的信号。

SIGCHLD:子进程已停止或终止
SIGCONT:如果进程已停止,则使其继续运行
SIGSTOP:停止信号
SIGTSTP:交互停止信号
SIGTTIN:后台进程组的成员读控制终端
SIGTTOU:后台进程组的成员读写控制终端

大多数应用程序并不处理这些信号。而是交互式shell通常做处理这些信号的所有工作。当键入挂起字符时,SIGTSTP被传送至后台进程组的所有进程。当通知shell在前台或后台恢复一个作业时,shell向作业中的所有进程发送SIGCONT信号。与之类似的有,如果向一个进程递送了SIGTTIN和SIGTTOU信号,则进程停止,作业控制shell了解后通知我们。
一个例外是管理终端的进程。当用户要挂起它,他需要了解到这一点,这样就能将终端恢复到vi启动时的情况。另外,在前台恢复时,太需要将终端状态设置回当前状态,并需要重新绘制屏幕。
在作业控制信号间有某种相互作用。当对一个进程产生4种停止信号,SIGTSTP,SIGSTOP,SIGTTIN,SIGTTOU中的任意一种时,对该进程的任意未决的SIGCONT信号就被丢弃。当对一个进程产生SIGCONT信号时,对同一个进程的任一未决的停止信号都被丢弃。


http://www.ppmy.cn/devtools/128708.html

相关文章

Aliyun PAI 上运行 Janus 模型

Deepseek 发布了 Janus 1.3B 多模型小模型&#xff0c;本文将使用 Aliyun 的 PAI 环境测试该模型&#xff0c;看看模型的效果如何&#xff1a; 登录 DSW 登录&#xff0c;并启动环境&#xff0c;Aliyun 首次给三个月免费额度&#xff0c;5000CU。 下载代码并安装 !git clone…

Java函数式编程

一、初识函数对象化 如果一个接口中只有一个抽象方法&#xff0c;且抽象方法的参数和返回类型与lambda表达式的参数和返回结果一致&#xff0c;那么就可以将接口类型作为lambda表达式的函数对象类型 interface Lambda {int calculate(int a, int b); }Lambda add (a, b) -&g…

开发运维警示录-20241024

开发警示录 1、作为开发&#xff0c;不要私自修改业务人员给的SQL语句&#xff0c;虽然个人感觉SQL很冗余&#xff0c;效率低等。 2、开发前&#xff0c;要明确需求&#xff0c;必要时通过图和文字形成文档与需求方确认、留痕。 3、开发复杂的业务逻辑代码前&#xff0c;先疏通…

Trimble三维激光扫描开启工业元宇宙的安全“智造”之路-沪敖3D

以下文章来源于天宝Trimble Field Systems &#xff0c;作者小甜宝 工业制造是一个固有危险性的行业&#xff0c;人身伤害、物理损坏和长时间的维修都可能导致项目停滞。因此&#xff0c;确保安全不仅仅对工作人员重要&#xff0c;更是保证项目顺利进行关键。 在自动化程度提…

sharpkeys-键盘部分按键不好用,用其它不常用按键代替

sharpkeys-键盘部分按键不好用&#xff0c;用其它不常用按键代替 文章目录

Json管理器的使用

解释 JsonMgr 是一个用于管理 JSON 数据的工具类&#xff0c;负责将数据对象序列化为 JSON 格式并存储到硬盘中&#xff0c;同时支持从硬盘读取 JSON 文件并反序列化为对象。它支持两种不同的 JSON 序列化方式&#xff1a;Unity 的内置 JsonUtility 和第三方库 LitJson。 核心…

腐蚀膨胀预处理

腐蚀&#xff1a;通过减少前景对象&#xff08;例如白色字符&#xff09;的边缘&#xff0c;腐蚀可以用来减小或消除细小的干扰线。如果干扰线较细&#xff0c;腐蚀可以有效地“消除”这些线条&#xff0c;同时保留较粗的字符。 膨胀&#xff1a;在腐蚀之后&#xff0c;膨胀可…

如何按照最左原则和B+树设计的联合索引

在数据库的联合索引中&#xff0c;最左原则&#xff08;Leftmost Prefix Rule&#xff09;指的是&#xff1a;当查询使用联合索引时&#xff0c;查询必须从索引的最左侧列开始才能有效利用索引。这是因为联合索引按列的顺序进行存储&#xff0c;如果跳过最左列&#xff0c;查询…