【Linux】—— Linux进程信号2

进程信号2

我们在 Linux进程信号1中学习了什么是进程信号,信号如何产生等,今天我们继续学习一些关于进程信号相关的知识

阻塞信号

信号相关的其他常见概念

  • 实际执行信号的处理动作称为信号递达(Delivery)
  • 信号从产生到递达之间的状态,称为信号未决(Pending)
  • 进程可以选择**阻塞 (Block )**某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
  • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

信号在内核中的表示

内核表示信号

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler
  • 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。

信号集操作函数

  • 在下面我们进行信号捕捉的时候我们会使用一个函数sigemptyset,这是信号集做函数中的一个,我们来看看具体有哪些信号集操作函数。
    信号集操作函数
  • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。
  • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。
  • 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。
  • 初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。

捕捉信号

我们前面一直提信号的捕捉或者递达这一概念,那么在操作系统中到底是什么时候进行信号的捕捉呢?我们依旧使用一张图来表达我们的流程:

捕捉信号

  • 图看不懂没关系,现在先请记住一句话:信号是从内核区返回用户区时被检测处理
  • 我们都知道操作系统是计算机中权限最高的管理者,操作系统不相信任何人,所以处理信号这种行为就是执行内核区的代码
  • 有的同学会问什么是执行内核区的代码?操作系统给我提供的系统调用api就是执行内核区代码,而我们执行自己写的代码就是执行用户区的代码。之所以需要在内核区中捕获处理信号是因为,操作系统在捕获信号时需要检测未决信号集合,屏蔽信号集等,并需要在处理信号后将他们由1置为0,这是用户无法做到的,我们将上图再高度抽象化一次。
    捕捉信号

内核如何实现信号的捕捉

  • 我们在谈发送和捕捉信号之前必须了解操作系统处理信号的办法,我们之前也提到过操作系统处理信号有三种方法,执行默认操作,执行自定义操作,忽略

  • 现在我们来谈谈什么叫做信号的安装:在目的进程安装该信号,即设置如果目标进程捕获该信号时执行的操作代码。Linux采用signal和sigaction系统调用来完成。因为信号是异步事件的典型应用,产生信号对进程而言是随机出现的,因此,进程不能预先知道信号会不会发送到当前的进程,也不能预知信号什么时候发送到当前进程。因此只能在信号到来前 告诉内核在此信号发生时,请执行下列操作,这也就是所谓的信号安装。

  • signal 函数
    signal

此函数有两个参数,第一个参数sig为要设置的信号,第二个参数为接收到此信号后你所要执行的函数的函数指针,第二个参数还给我们了三个宏的选择

1.SIG_ERR   //返回错误
2.SIG_DFL   //执行信号默认操作
3.SIG_IGN   //忽略信号

如果执行成功,此函数返回上一次对此信号的设置,如果设置多次,最终者为最近设置的一次,如果设置失败,将返回SIG_ERR的错误。话不多说,我们一起来用用这个函数。
signal
代码非常简单,相信你仔细看一定看的懂,handler的参数就是我们捕获到的信号码,这里要说的是,2号信号实际上就是我们按ctrl + c 给进程发送的信号,原来默认会让进程终止,现在我们让他执行我们自定义的行为打印handler函数中的句子。(无法终止进程的同学按ctrl + z即可)

  • 运行结果
    在这里插入图片描述
    如果你使用while循环将31个信号全部捕获,那么你会发现无论使用键盘上的操作也无法结束进程,这里就提到了我们之前说过的9号信号,这个信号十分强势,不受自定义行为的影响,所以你只需要在另一个终端下找到当前进程的pid发送9号信号即可。同样不受自定义行为控制的还有18和19号信号,一个让进程暂停,另一个则唤醒进程,他们是相对的俩个信号。

signal信号安装函数很简单,他只能提供简单的信号安装操作,所以逐步被淘汰,因此Linux提供了功能更强大的sigaction函数

  • sigaction函数
    sigaction
    此函数的第一个参数为要安装的信号,第二三个参数都为信号结构体sigaction(用于描述要采取的操作及相关信息)变量。第二个参数用来指定欲设置的信号处理方式,第三个参数将存储执行此函数前针对此信号的安装信息,说白了就是一个备份。
    现在我们来看看struct sigaction中有什么东西:
    sigaction
struct sigaction {

	void (*sa_handler)(int);//类似于调用signal函数
	
	void (*sa_sigaction)(int, siginfo_t *, void *);//信号捕获函数,可以获取其他信息
	
	sigset_t sa_mask;//执行信号捕获函数期间要屏蔽的其他信号集合
	
	int sa_flags;//影响信号行为的特殊标志
	
	void (*sa_restorer)(void);//没有使用,作为了解

}
  • 结构体中sa_mask是一个信号集合(位图),用于标识在执行信号捕获函数时,添加到进程屏蔽信号集合中的信号集。但是不会屏蔽9,18,19号信号。

  • 上面的sa_handler和sa_sigaction函数指针只需要二取一即可,如果使用前者那么行为和signal函数相同,而后者可以获取更多的信息,这看起来像时signal的升级版。

  • 看过了sigaction函数,我们接下来看看这个函数该怎么使用
    sigaction函数的使用

  • 写到这我们执行上述程序会发现什么现象都不会出现,这是因为我们没有定义handler函数的行为,我们现在来定一下该函数的行为,这里就需要使用sa_signaction结构体的第二个成员了,我们来具体看一下这个成员中有一些什么
    siginfo_t

对了,别忘了我们要调用sa_sigaction时与他配合的sa_flags,这里我们来看几个常用的选项

  • SA_NOCLDSTOP:子进程退出时不生成SIGCHLD信号给父进程
  • SA_RESTART:影响可中断函数的行为,即指定的可中断函数会调用失败,并且设置erron全局变量
  • SA_SIGINFO:如果未能捕捉到该信号,那么函数必须调用sa_handler成员来捕捉信号,且不得修改sa_sigaction成员,相反的情况是只能调用sa_sigaction成员而不可修改sa_handler成员

现在了解了更多,不妨这次我们使用sa_sigaction成员试试看:
sigaction的使用

  • 运行结果,ctrl + c向进程发送信号(ctrl + \)可退出该进程
    sigaction

想必讲到这大家应该也基本上了解了signal和sigaction两个函数的使用了,其实不难理解。

可重入函数

  • 可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;
  • 而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

我们刚刚在信号捕捉中讲到的signal函数吗,它就是一个可重入函数,也就代表他是一个不可靠的信号函数。

  • 当调用sigprocmask函数解除信号的屏蔽后,系统会按顺序检测未决信号,但是这个顺序不是信号到来的顺序,而是信号值的顺序。
  • 当检测到1号信号发现他需要被处理时,进入处理函数,因此内核为这个函数在用户创建了函数栈,而此时操作系统发现2号信号也需要被处理,所以打断一号信号的处理跑去处理2号信号,处理完2号信号后发现1号信号仍然未决未注销,因此再次执行处理函数,执行完毕后注销,回到用户空间,按正常的函数情况执行,发现有函数栈没有执行,因此再执行一次…

因此这也就是为什么signal函数逐渐被淘汰的原因,而sigaction则不会出现这种情况。

不可重入函数
  • 一般的,调用了malloc或者free的函数是不可被重入的,因为malloc是通过全局链表来管理堆的
  • 调用的标准I/O库函数的也是不可重入函数,因为标准I/O库中很多实现都是以不可重入的方式使用全局数据结构
发布了167 篇原创文章 · 获赞 175 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/chenxiyuehh/article/details/95585334
今日推荐