【Linux】Ⅴ进程信号
作者:互联网
同步与异步
- 同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)。
- 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
- 换句话说,就是由调用者主动等待这个调用的结果。
- 而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
阻塞与非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
信号的概念
信号是一个软件中断。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。
信号的种类:一共有62种
-
1~31:非可靠信号,信号有可能会丢失。
-
34~64:可靠信号,信号不会丢失。
Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,信号值小于SIGRTMIN的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是信号可能丢失。
随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。
信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在 支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送 函数kill()。
信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
信号的产生
硬件产生
- Ctrl+c:SIGINT(2号信号),发送给前台进程
如何把一个进程放到后台去运行,在启动命令之后加&符号
fg:就将刚刚放到后台的进程,在放到前台来运行
- Ctrl+z:SIGTSTP(20号信号)除非有特定的场景,否则不要用
- Ctrl+|: SIGQUIT(3号信号)
软件产生
按照原因分类:
- 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。
- 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。
- 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。
- 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调用。
- 在用户态下的进程发出的信号。如进程调用系统调用kill向其他进程发送信号。
- 与终端交互相关的信号。如用户关闭一个终端,或按下break键等情况。
- 跟踪进程执行的信号。
相关函数和命令
kill函数
#include <signal.h>
int kill(pidt pid,int sig);
//pid:接受该信号的进程的pid
//sig:要发送的具体信号值
abort函数
谁调用该函数,谁收到6号信号封装了的ki11
#include <stdlib.h>
void abort(void);
//相当于kill(getpid(),6);
6号信号(SIGABRT)==>(double free)
解引用空指针+内存访问越界==》进程收到11号信号(SIGSEGV)
kill命令
kill -l
//列出当前所有可用信号,可见上图
kill -[信号名称/编号] 对应进程的pid
eg: kill -1 2246 对pid为2246的进程发出暂停信号
kill -SIGKILL 2243 杀死进程2243
信号的注册与注销——位图+signqueue队列
信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。如果信号发送给一个正在睡眠的进程,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。如果发送给一个处于可运行状态的进程,则只置相应的域即可。
注意:当前操作系统中没有0号信号
非可靠信号的注册——在sigqueue中最多只占用一个sigqueue节点
当进程收到一个非可靠信号
第一件事情:将非可靠信号对应的比特位更改为1;
第二件事情:添加sigqueue节点到sigqueue队列当中。但是!!!如果在添加sigqueue节点的时候,队列当中已然有了该信号的sigqueue节点,则不添加。
可靠信号的注册——可能在sigqueue中占用多个sigqueue结点
如果进程收到一个可靠信号
第一件事情:在sig位图当中更改该信号对应的比特位为1;
第二件事情:不论之前sigqueue队列当中是否存在该信号的sigqueue节点,都再次添加sigqueue节点到sigqueue队列当中去。
非可靠信号的注销
1.将该信号的sigqueue节点从sigqueue队列当中进行出队操作
2.信号在sig位图当中对应的比特位从1置位0
可靠信号的注销
1.将该信号的sigqueue节点从sigqueue队列当中进行出队操作
2.需要在判断sigqueue队列当中是还有相同的sigqueue节点
2.1没有相同的节点
信号在sig位图当中对应的比特位从1置位0
2.2还存在相同的节点
不会更改sig位图当中对应的比特位(保持为1)
信号的处理
1.SIG_DFL:默认处理方式
2.SIG_IGN:忽略处理
SIGCHLD信号——僵尸进程的产生实际上是因为子进程在退出的时候,给父进程发送一个SIGSHILD信号,但是父进程在收到这个信号之后,但是父进程对SIGSHILD信号默认是忽略处理,所以才导致子进程的退出资源未能得以回收,才变成了僵尸进程。
不能对子进程发送9号信号(因为不能杀一个已经死去的人)
3.自定义信号处理方式
①signal函数
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum,sighandler_t handler);
//该函数可以更把signum信号的处理动作更换成 handler所指向的函数
//也就是说函数收到signum信号的时候,不会去执行signum信号,
//而是会执行handle所指向的函数
man手册说明:
signal()的行为在UNIX版本之间有所不同,并且在历史上在Linux的不同版本中也有所不同。
避免使用它:改为使用sigaction(2)。请参阅下面的可移植性。
signal()将信号符号的设置为处理程序,该处理程序可以是SIG_IGN,SIG_DFL或程序员定义的函数的地址(“信号处理程序”)。
如果信号值传递给该进程,则发生以下情况之一:
*如果设置为SIG_IGN,则忽略该信号。
*如果设置为SIG_DFL,则发生与信号关联的默认操作(请参见signal(7))。
*如果设置为函数,则首先将处置重置为SIG_DFL,或者阻止信号(请参见下面的可移植性),然后使用参数signum调用处理程序。如果调用处理程序导致信号被阻塞,则从处理程序返回后,信号将被解除阻塞。
信号SIGKILL和SIGSTOP不能够被捕获或忽略。
第一个参数指定信号的值,第二个参数是一个函数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。
如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigcallback(int signo)
{
printf("signo : %d\n", signo);
}
int main()
{
signal(2, sigcallback);
signal(20, sigcallback);
while(1)
{
printf("linux so easy\n");
sleep(1);
}
return 0;
}
②sigaction函数
#include <signal.h>
int sigaction(
int signum, const struct sigaction *act,struct sigaction *oldact);
man手册说明:
sigaction()系统调用用于更改进程在收到特定信号后采取的操作。 (有关信号的概述,请参见signal(7))
signum指定信号,并且可以是除SIGKILL和SIGSTOP之外的任何有效信号。
如果act为非NULL,则从act安装信号信号的新动作。 如果oldact为非NULL,则先前的操作将保存在oldact中。
siginfo_t*是指向siginfo_t结构的指针,结构中包含信号携带的数据值。
signal函数底层也是调用sigaction函数。
在使用 struct sigaciton之前,要先进行初始化——用sigempty函数。
int sigemptyset(sigset_t*set);/将位图的所有比特位设置为0
//第一次按下ctrl+|之后执行sigcallback
//第二次按下ctrl+|之后执行三号信号,程序退出
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigcallback(int signo)
{
printf("signo : %d\n", signo);
}
int main()
{
//act --》 入参
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = sigcallback;
//oldact --》 出参
struct sigaction oldact;
sigaction(3, &act, &oldact);
getchar();
sigaction(3, &oldact, NULL);//复原
while(1)
{
printf("linux so easy\n");
sleep(1);
}
return 0;
}
信号的捕捉
什么时候进入到内核空间:调用系统调用函数的时候,或者调用库函数的时候。(库函数底层大多数都是封装系统调用函数的)
信号的阻塞
信号的阻塞,并不会干扰信号的注册!信号该注册还是注册的,只不过当前的进程不能立即处理了
2.1 当我们将block位图当中对应信号的bite为置为1,表示当前进程阻塞该信号
2.2 当进程收到一个该信号的时候,进程还是一如既往的对该信号进行注册
2.3 当进程进入到内核空间,准备返回用户空间的时候,调用do_signal函数,不会立即去处理该信号
3. 这里面的不会立即处理,一定不是之后不处理(解除阻塞的时候就会对其进行处理)。
sigprocmask函数——设定对信号屏蔽集内的信号的处理方式(阻塞或不阻塞)
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- how:告诉sigprocmask函数,应该做什么操作
- SIG_BLOCK:设置某个信号为阻塞
- SIG_UNBLOCK:解除对某个信号的阻塞
- SIG_SETMASK:替换阻塞位图set:用来设置阻塞位图
- SIG_BLOCK:设置某个信号为阻塞
block(new)=block(old)| set (按位或)
0010 = 0000 | 0010
- SIG_UNBLOCK:解除对某个信号的阻塞
block(new)=block(old)&(~set)(取反之后按位与)
0000 = 0010 & (~0010)=0010 & 1101
- SIG_SETMASK:替换阻塞位图
block(new)=seto1dset:原来的阻塞位图
- oldset:原来的阻塞位图
注意:9号信号不能被阻塞,也不可以对9号信号进行自定义函数
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sigcallback(int signo)
{
printf("signo : %d\n", signo);
}
int main()
{
signal(2, sigcallback);
signal(40, sigcallback);
sigset_t set;
sigfillset(&set);
//将参数set信号集初始化,然后把所有的信号加入到此信号集里即将所有的信号标志
//位置为1,阻塞所有的信号
sigset_t oldset;//保存之前的信号集
sigprocmask(SIG_SETMASK, &set, &oldset);
getchar();
sigprocmask(SIG_SETMASK, &oldset, NULL);//信号集复原,解除上面的阻塞
while(1)
{
printf("linux so easy\n");
sleep(1);
}
return 0;
}
标签:sigqueue,调用,signal,阻塞,信号,Linux,进程 来源: https://blog.csdn.net/weixin_43737259/article/details/115757228