一.信号的概念
信号是信息的载体,Linux/UNIX 环境下,古老、经典的通信方式,现下依然是主要的通信手段。
Unix早期版本就提供了信号机制,但不可靠,信号可能丢失。Berkeley和AT&T都对信号模型做了更改,增加了可靠信号机制。但彼此不兼容。POSIX.1对可靠信号例程进行了标准化。
A给B发送信号,B收到信号前在执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完之后再继续。与中断类似,但信号是软件层面上实现的中断,早期常被称为“软中断”。
二.信号的特质:
1.简单,不能携带大量信息,必须满足某条件才可发送。
2.信号是通过软件层面上的实现,其实现手段导致信号有很强的延时性。
3.每个进程收到的所有信号,都是由内核负责发送的,内核处理。
三 .信号相关的事件和状态
产生信号:
按键产生:如Ctrltc、Ctrltz、Ctrl+\v1.
系统调用产生:如kill、raise、aborte2
软件条件产生:如定时器alarm
硬件异常产生:如:非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误)
命令产生:如:kil命令
递达:信号递送并且到达进程
未决:产生和递达之间的状态。主要由于“阻塞(屏蔽)”导致该状态。下文讲到的未决信号集是导致该状态的主要原因,会使某个信号处于被屏蔽状态,从而无法递达
递达后,信号的处理方式:
1.执行默认动作。
2.忽略(丢弃)
3.捕捉(调用户处理函数)
进程控制块PCB,除了包含进程Pid,进程状态,进程工作目录,用户 id,组id,
文件描述符表等,还应该包含了信号相关的信息 : 主要指阻塞信号集和未决信号集
四.阻塞信号集和未决信号集
阻塞信号集,也称信号屏蔽字,能够屏蔽信号。未决信号集则保存信号的状态,0表示未产生,1表示处于未决。
这两个信号集本质上都是位图,以0和1来表示信号的状态。
阻塞信号集:
将信号加入该集合后,对他们设置屏蔽,当屏蔽信号后,再收到该信号时,该信号的处理将推后,使其处于未决状态。
未决信号集:
1.信号产生,未决信号集中描述该信号的位由0立刻翻转为1,使其处于未决状态。当信号被处理后,对应位翻转回为0。这一时刻往往非常短暂(信号一旦翻转为1,就会被内核立即处理)。
2.信号产生后由于被阻塞(加入阻塞信号集)不能递达。在屏蔽解除前,信号一直处于未决状态。

五.信号四要素和常用信号
四要素:

默认处理动作

常用信号
1)★SIGHUP★:当用户退出 shell 时,由该shell 启动的所有进程将收到这个信号,默认动作为终止进程
2)★SIGINT★:当用户按下了<Ctr+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动。作为终止正在运行的进程。“
3)★SIGQUIT★:当用户按下<ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号。默认动作为终止进程。
4)SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生 core 文件。
5)SIGTRAP:该信号由断点指令或其他trap指令产生。默认动作为终止进程并产生core 文件。#
6)SIGABRT:调用 abort函数时产生该信号。默认动作为终止进程并产生core 文件。
7)★SIGBUS★(总线错误):非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core 文件。
8)★SIGFPE★:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。
9)★SIGKILL★:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法,kill - 9 就是由于信号9不能被忽略,所以使用信号9进行终止。
10)★SIGUSR1★:用户定义的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
11)★SIGSEGV★(段错误信号):指示进程进行了无效内存访问。默认动作为终止进程并产生core 文件。
12)★SIGUSR2★:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
13)★SIGPIPE★:pipe 向一个没有读端的管道写数据。默认动作为终止进程。
14)★SIGALRM★:定时器超时,超时的时间 由系统调用 alarm 设置。默认动作为终止进程。
15)★SIGTERM★:与SIGKILL类似,是程序结束信号,不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。shell 命令Kill时,产生这个信号。默认动作为终止进程。"
16)SIGSTKFLT:Linux早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。
17)★SIGCHLD★:”子进程状态发生变化时,父进程会收到这个信号。默认动作为忽略这个信号。
18)SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。
19)★SIGSTOP★:(CRTL+Z)停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。
20)SIGTSTP:停止终端交互进程的运行。按下<ctrl+z>组合键时发出这个信号。默认动作为暂停进程。
21)SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。。
22) SIGTTOU:该信号类似于 SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。"
23)SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外据到达,默认动作为忽略该信号。
24)SIGXCPU:进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程。默认动作为终止进程。
六.关于信号的系统调用和库函数
1.kill
kill用于向进程发送信号。

pid :
大于0:发送信号给指定进程
=0:发送信号给与该进程处于同一进程组的进程。
<-1:取绝对值,杀死该绝对值所对应的进程组的所有组员。
= -1:杀死所有该进程有权限传递信号的进程。
sig:
传递的信号宏
2.alarm
用于定时,在倒计时结束后,会发送SIGALRM信号给当前进程,默认终止进程。
unsigned int alarm(unsigned int seconds)
返回值:上次定时剩余时间。
time命令:查看程序执行时间。实际时间=用户时间+内核时间+等待时间,其中等待时间占用大,主要是IO操作引起的。优化时可以从IO入手。

3.setitimer/getitimer
设置定时器(闹钟)。可代替alarm函数。可以实现周期定时,精确到微秒
int setitimer(int which, const struct itimerval *new_ value, struct itimerval *old_value);
返回值
成功:0;失败:-1,设置errno
参数:
which:
指定定时方式。
①自然定时:ITIMERREAL ,发送信号SIGALRM-------计算自然时间
② 虚拟空间计时: ITIMER_VIRTUAL,发送SIGVTALRM-------计算进程占用cpu的时间
③)运行时计时(用户+内核):ITIMERPROF,发送SIGPROF ------计算占用cpu及执行系统调用的时间
new_value和old_value:
这两个参数都是itimerval结构体,一个itimerval结构体内部包含两个timeval结构体
第一个结构体new_value是一个传入参数,代表定时,其中lt_value代表定时秒数,it_interva代表周期时间。
第二个old_value是一个传出参数,上次定时剩余时间。



4.信号集设定
系统内核不允许直接对未决信号集做处理,但是可以对阻塞信号集做处理,从而影响未决信号集。
自定义信号集set
可以操作一个新的位图set,(set定义: sigset_t set),然后将set与阻塞信号集进行位操作,或直接替换,从而改变阻塞信号集。
下图为改变set集合的函数一览
sigismember(sigset_set *set ,int signum) ,此函数返回值:
若该信号在set中表现为1(即屏蔽/未决),返回为1
若信号在set中表现为0,返回0
设置屏蔽与解除屏蔽函数
1. sigprocmask
原型: int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
参数:
how :①SIG_BLOCK --设置阻塞(位或) ②SIG_UNBLOCK:取消阻塞(set取反,再位与) ③SIG_SETMASK:用自定义set替换mask(阻塞信号集),相当于覆盖。
set:自定义set
oldset:旧的 mask。
2.sigpending
原型 int sigpending (sigset_t * set)
作用:将自定义set作为传出参数,返回该进程目前的未决信号集状态。
案例:

七.信号捕捉-------signal和sigaction系统调用
信号递达后可以不被处理,进程可以捕捉信号,并做出对于动作。
1.signal

其中,signum表示信号编号(或者叫信号宏),handler传递的是函数,表示要进行的动作。

sighandler_t类型是函数指针,指向一个返回值为void,参数为int的函数
案例:
signal传递sigint信号(CTRL+C产生),将sig_cath作为函数指针传入(函数名即该函数在代码区的入口地址,所以也可以说函数名就是这个函数的函数指针)

结果:

2.sigaction(常用,重点)
定义
①signum:捕捉信号
②act:传入参数,是一个sigaction结构体,设置信号捕捉的参数。
③oldact:传出参数,是一个sigaction结构体,返回旧的信号捕捉参数。
sigaction结构体内容:
其中参数中,最常用的是sa_handler,sa_mask,sa_flags
①sa_handler即信号被捕捉后所执行的函数,它是一个函数指针。
②sa_mask是作用在函数执行期间的屏蔽集,只在这个期间内有效,它的目的是:如果在捕捉函数执行期间,有其它的信号产生,函数执行会受影响(例如终止进程,暂停进程)。此时就可以使用sa_mask设置某些信号的屏蔽。
③是设置参数,默认传0:当它为0时,会默认将sa_mask中捕捉信号设置为屏蔽,这是因为如果函数在执行中,此捕捉信号再次产生,那么函数会再次被捕捉,会递归地执行捕捉函数,这明显是一个错误。所以sa_falgs默认将其设置为屏蔽,使函数在执行过程中不再出现此错误。
在使用sigaction时,对于act结构体,一般将sa_mask使用sigemptyset()设置为0,再为sa_falg传0,使其阻塞捕捉函数。
案例:
特性:

信号捕捉流程:
内核实现信号的捕捉,是在软件层面上实现的
1.当程序因某种原因需要进入到内核时,是信号捕捉的开始
2.在内核处理过程中,查看进程的未决信号集,查找未处理的信号
3.若有未处理的信号,查询是否被捕捉,若捕捉,则中断当前程序,转而执行信号捕捉函数。
4.信号捕捉函数处理完成后,再次返回内核。
5.程序从被中断的位置继续执行。
★借助信号捕捉回收子进程★

对于这个案例,有以下几个重点:
一.根据信号捕捉的特性,当信号捕捉函数执行时,本信号默认被屏蔽。
所以在信号捕捉函数catch_child当中,若每次捕捉信号都只回收一个子进程,那么若有多个子进程同时结束时,后发送的信号会被屏蔽,从而不被处理,成为僵尸进程。所以在一次捕捉函数调用中,使用循环来回收一个或多个子进程,避免僵尸进程出现。
二. 在父进程的逻辑中,为SIGCHLD信号设置了捕捉,但由于fork子进程在设置之前,那么有可能在这个信号设置之前就已经有进程结束,造成僵尸进程出现,为了解决这种风险,1.使用sleep,使子进程晚执行。2.在fork之前就对SIGCHLD进行屏蔽,使信号无法递达。待到sigaction执行完毕时,再解除阻塞,这样就可以回收早于它结束的子进程。
1564

被折叠的 条评论
为什么被折叠?



