4.进程信号量

  linux中的信号

    Ctrl-C特点

    信号概念

    信号本质

    信号处理常见方式

  linux中的信号的两种分类方式

    可靠信号与不可靠信号

    实时信号与非实时信号

  产生信号

    1.通过终端按键产生信号(硬件)

    2.硬件异常产生信号

    3.调用系统函数kill向进程发信号

    4.由软件条件产生信号

  信号捕捉

    siganl函数进行信号捕捉

    模拟一下野指针异常

  总结

    信号处理的三种方式

    信号处理流程

    信号产生到处理的状态

    信号在内核中的表示

    内核实现信号捕捉



linux中的信号

  1. 用户在Linux命令行输入命令,在Shell下启动一个前台进程。
  2. 用户按下Ctrl-C, 产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程
  3. 前台进程因为收到信号,进而引起进程退出



Ctrl-C特点
  1. Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个& 可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。
  2. Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像Ctrl-C 这种控制键产生的信号。
  3. 前台进程在运行过程中用户随时可能按下Ctrl-C而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的。



信号概念

信号是进程之间事件异步通知的一种方式,属于软中断



信号本质

软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。

查看系统定义的信号量
kill -l
  • 每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,宏对应的值就是编号的值
  • 编号34以上的是实时信号
查看信号各自产生条件,默认的处理动作
man 7 signal

信号处理常见方式
  1. 忽略此信号。
  2. 执行该信号的默认处理动作。
  3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。



linux中的信号的两种分类方式



可靠信号与不可靠信号
不可靠信号

Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,信号值小于SIGRTMIN(34)的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是信号可能丢失。

可靠信号
产生

有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。

兼容性

信号值位于SIGRTMIN(34)和SIGRTMAX(64)之间的信号都是可靠信号

Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。

信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。

对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数,而经过signal安装的信号不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。



实时信号与非实时信号

早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),将来可能进一步增加,这需要得到内核的支持。前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。

非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。



产生信号



1.通过终端按键产生信号(硬件)

SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump

Ctrl + c --> 2)SIGINT(终止/中断) “INT”—Interrupt
Ctrl + z --> 20)SIGTSTP(暂停/停止) “T”—Terminal 终端
Ctrl + \ --> 3)SIGQUIT(退出)



2.硬件异常产生信号

硬件异常被硬件以某种方式检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。



3.调用系统函数kill向进程发信号
写一个死循环程序
#include <stdio.h>
int main(){
    while(1){
        sleep(1);
        printf("immortal...\n");
    }
    return 0;
}

保存为endless.c

编译(make方式也可以)

gcc endless.c -o endless

复制SSH隧道在另一个窗口查看死循环进程的pid(在xshell中)

ps -ef|head -1;ps -ef | grep endless

记住Pid,然后使用11号信号SIGSEGV终止进程

kill -SIGSEGV pid  //等效于kill -11 pid

在这里插入图片描述

在这里插入图片描述

屏蔽死循环的输出
#include <stdio.h>
int main(){
    while(1){
        sleep(1);
        //printf("immortal...\n");
    }
    return 0;
}

./endless运行在后台,查看pid并使用11号信号SIGSEGV终止进程

在这里插入图片描述

  • kill -宏或者宏编号 pid 命令都是等价的

在这里插入图片描述

  • kill -SIGSEGV pid之后再回车才显示Segmentation fault ,是因为在pid进程终止掉之前已经回到了Shell提示符等待用户输入下一条命令Shell不希望Segmentation fault信息和用 户的输入交错在一起,所以等用户输入命令之后才显示。

  • 以往遇到的段错误都是由非法内存访问产生的,而这个程序本身没错,给它发SIGSEGV也能产生段错误

  • kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号(自己给自己发信号)。两个函数都是成功返回0,错误返回-1。

  • abort函数使当前进程接收到信号而异常终止。和exit函数一样,abort函数总是会成功的,所以没有返回值。



4.由软件条件产生信号

常见的产生信号的系统函数有:kill, raise, alarm和setitimer以及sigqueue函数

SIGPIPE是一种由软件条件产生的信号,客户端程序向服务器端程序发送了消息,然后关闭客户端,服务器端返回消息的时候就会收到内核给的SIGPIPE信号。

alarm函数与SIGALRM信号

alarm的返回值是0或者是以前设定的闹钟时间还余下的秒数

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。

#include <stdio.h>
#include <unistd.h>
int main(){
    int count = 0;
    alarm(1); //1秒钟之后,给当前进程发送SIGALRM信号
    for(;1;count++){
        printf("count = %d\n", count);
    }
    return 0;
}

这个程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终止



信号捕捉



siganl函数进行信号捕捉
#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
	printf("catch a sig : %d\n", sig);
}
int main()
{
	signal(2, handler);
		while (1);
	return 0;
}

在这里插入图片描述

模拟一下野指针异常
#include <stdio.h>
#include <signal.h>
void handler(int sig)
{
	printf("catch a sig : %d\n", sig);
}
int main()
{
	signal(SIGSEGV, handler);
	sleep(1);
	int* p = NULL;
	*p = 100;
	while (1);
	return 0;
}

上述代码会无限循环捕捉信号量: 11) SIGSEGV

在这里插入图片描述

在C/C++当中除零,内存越界等异常,在系统层面上,是被当成信号处理的



总结

  • OS是进程的管理者,因此产生的所有信号,最终都要有OS来进行执行

  • 信号的处理不是立即处理,在合适的时候,因为信号是异步的

  • 信号如果不是被立即处理,那么信号需要暂时被进程记录下来,记录在进程地址空间的一个位图中(无符号整形)

  • OS向进程发送信号,实际上就是修改信号发往的目标进程PCB位图的对应记录信号的比特位



信号处理的三种方式

第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。

第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。

第三种方法是,进程通过系统调用signal来指定进程对某个信号的处理行为。对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。



信号处理流程
1.信号产生(见上文4个产生信号的例子),总结一下:

硬件来源(比如我们按下了键盘或者其它硬件故障);

软件来源:最常用发送信号的系统函数是kill, raise, alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。

–>

2.信号在进程中注册

–>

3.信号的执行和注销



信号产生到处理的状态
  1. 信号从产生到递达之间的状态,称为信号未决(Pending)。
  2. 进程可以选择阻塞 (Block )某个信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
  3. 实际执行信号的处理动作称为信号递达(Delivery)
  4. 忽略是在递达之后可选的一种处理动作。



信号在内核中的表示

在这里插入图片描述

  • 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。

  • SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

  • SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号。



内核实现信号捕捉

在这里插入图片描述

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler。当前正在执行main函数,这时发生中断或异常切换到内核态。在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值