注:目前只讨论前32个信号(1~31)
ps aux 可以查看进程状态
当信号停止之后,用 fg 1可以让进程继续运行;bg 1可以让它在后台运行
1.信号机制(下面五种情况触发信号)
man 7 signal 可查看信号机制
终端特殊按键:在终端中 ctl+c SiGINT(程序终止信号) ctl+z SIGSTP(停止信号) ctl+\ SIGQUT(退出信号)
硬件异常:
eg1:除0操作
答:会报错,浮点数例外的情况;会用SIGFPE信号
eg2:访问非法内存 (只读数据段进行写操作)
答:会报段错误,用SIGSEGV信号
kill函数或kill命令:向一个进程发送一个信号
函数原型 int kill(pid_t pid,int sig)
三种情况: pid>0 sig发送给ID为pid的进程
pid==0 sig发送给与发送进程同组的所有进程
pid<0 sig发送给组ID为|-pid|的进程,并且发送进程具有向其发送信号的权限
pid==-1 sig发送给发送进程有权限向他们发送信号的系统上的所有进程
sig==0时,用于检测,特的那个为pid进程是否存在,若不存在,返回-1
rase和abort函数:raise向自己发送某个信号,abort会终止和结束自己的信号
某种软件条件已发生
操作系统为每一个进程分配一个定时器alarm,当定时器到达时间的时候会产生信号,unsigned int alarm(unsigned int seconds)
作用:定时指定的秒数,时间到了之后会向指定进程发送SIGALRM信号
2.信号功能
3.进程处理信号的方式
SIG_IGN
SIG_DFL
a signal handling function
三种行为:
①默认处理动作 (term、core、ign、stop、cont)
②忽略
③捕捉(用户自定义信号处理函数)
信号集:
收到一个信号之后执行信号处理函数,在执行信号处理函数的过程中如果又来了一个相同的信号,那么这个信号将会被阻塞,直到信号处理函数执行完之后,再响应被阻塞的信号,注意如果信号被阻塞期间又收到了该信号,那么多个信号的处理会被合并为1次
比如正在执行SIGUSR1的信号处理函数,此时又收到了一个SIGUSR1信号,那么该信号将会被阻塞,直到信号处理函数执行完之后,再次执行该函数。如果在阻塞期间又收到了SIGUSER1信号,接下来只再调用一次SIGUSR1的信号处理函数
信号集就是用来记录当前收到了哪个信号,会把当前信号的标志位置成“正在处理”,如果此时再收到该信号,那么信号就阻塞等待
使用数据类型sigset_t表示信号集,在Linux中该类型是一个32位无符号整数,这是因为在Linux中定义了32种信号,每一个信号用32位无符号整型变量中的一位来标志,如果该位置为1,那么表示正在处理该信号,如果置为0表示可以处理该信号。注意:有的操作系统,信号个数多于32,此时就不能用32位整数表示一个信号集了。
Linux内核的进程控制块PCB是一个结构体,task_struct, 除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。
阻塞信号集(信号屏蔽字)
将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(处理发生在解除屏蔽后)。
未决信号集
信号产生,未决信号集中描述该信号的位立刻翻转为1,表示信号处于未决状态。当信号被处理对应位翻转回为0。这一时刻往往非常短暂。
信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。
阻塞信号集中为1时,表示阻塞,不能通过
前32个信号,不支持排队(多次同一个信号算一个),后32个信号支持排队
4.信号集处理函数
sigset_t为信号集,
int sigemptyset(sigset_t *set) //清空设置一个信号集,置0
int sigfillset(sigset_t *set) //把一个信号集全部置1
int sigaddset(sigset_t *set,int signo) //把信号集某一个信号位置1
int sigdelset(sigset_t *set,int signo) //把信号集某一个信号位置0
int sigismember(const sigset_t *set,int signo) //判断信号集某个信号是否置1
5.1 sigprocmask
注册函数,可以读取或更改进程的信号屏蔽字
#include<signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
返回值:若成功为0,出错为-1,set作为传入参数,oset作为传出参数
how参数:
5.2 sigpending读取当前进程的未决信号集,通过set参数传出
#include<signal.h>
int sigpending(sigset_t *set);
通过set参数传出,成功返回0,出错返回-1
注意:SIGKILL和SIGSTOP不允许被阻塞
6 信号捕捉设定
捕捉函数 sigaction
int sigaction(int signum,const struct sigaction *act, struct sigaction *oldact);
第一个参数为哪一个信号设置捕捉;第二个参数为信号设定动作;第三个为信号原有动作,如果不关心为null
struct sigaction{
void (*sa_handler)(int); //捕捉函数,早期的(函数指针)
void (*sa_sigaction)(int ,siginfo_t *,void *); //新诞生的捕捉函数(函数指针)
sigset_t sa_mask;//阻塞信号集中的信号(信号屏蔽字)
int sa_flags;//选择用第一还是第二个捕捉函数
void (*sa_restorer)(void);//过时了
};
希望只在主线程中处理信号:
用作在主调线程里控制信号掩码
int pthread_sigmask (int how,const sigset_t *newmask, sigset_t *oldmask)
How:
SIG_BLOCK: 结果集是当前集合参数集的并集
SIG_UNBLOCK: 结果集是当前集合参数集的差集
SIG_SETMASK: 结果集是由参数集指向的集
信号掩码:信号掩码是进程的属性,即阻塞的信号列表。信号掩码 在POSIX下,每个进程有一个信号掩码(signal mask)。简单地说,信号掩码是一个"位图",其中每一位都对应着一种信号。如果位图中的某一位为1,就表示在执行当前信号的处理程序期间相应的信号暂时被"屏蔽",使得在执行的过程中不会嵌套地响应那种信号。所谓"屏蔽",与将信号忽略是不同的,它只是将信号暂时"遮盖"一下,一旦屏蔽去掉,已到达的信号又继续得到处理。
示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
//信号处理函数
void sig_usr(int signo)
{
if(signo == SIGUSR1)
{
printf("收到了SIGUSR1信号,我休息10秒......!\n");
sleep(10);
printf("收到了SIGUSR1信号,我休息10秒完毕,苏醒了......!\n");
}
else if(signo == SIGUSR2)
{
printf("收到了SIGUSR2信号,我休息10秒......!\n");
sleep(10);
printf("收到了SIGUSR2信号,我休息10秒完毕,苏醒了......!\n");
}
else
{
printf("收到了未捕捉的信号%d!\n",signo);
}
}
int main(int argc, char *const *argv)
{
if(signal(SIGUSR1,sig_usr) == SIG_ERR) //系统函数,参数1:是个信号,参数2:是个函数指针,代表一个针对该信号的捕捉处理函数
{
printf("无法捕捉SIGUSR1信号!\n");
}
if(signal(SIGUSR2,sig_usr) == SIG_ERR)
{
printf("无法捕捉SIGUSR2信号!\n");
}
for(;;)
{
sleep(1); //休息1秒
printf("休息1秒~~~~!\n");
}
printf("再见!\n");
return 0;
}
其中有两个信号自定义函数:使用kill发送USR1、USR2会分别反应:
(1)执行信号处理函数被卡住了10秒,这个时候因为流程回不到main(),所以main中的语句无法得到执行;
(2)在触发SIGUSR1信号并因此sleep了10秒种期间,就算你多次触发SIGUSR1信号,也不会重新执行SIGUSR1信号对应的信号处理函数,而是会等待上一个SIGUSR1信号处理函数执行完毕才 第二次执行SIGUSR1信号处理函数;
换句话说:在信号处理函数被调用时,操作系统建立的新信号屏蔽字(sigprocmask()),自动包括了正在被递送的信号,因此,保证了在处理一个给定信号的时候,如果这个信号再次发生,那么它会阻塞到对前一个信号处理结束为止;
(3)不管你发送了多少次kill -usr1信号,在该信号处理函数执行期间,后续所有的SIGUSR1信号统统被归结为一次。比如当前正在执行SIGUSR1信号的处理程序但没有执行完毕,这个时候,你又发送来了5次SIGUSR1信号,那么当SIGUSR1信号处理程序执行完毕(解除阻塞),SIGUSR1信号的处理程序也只会被调用一次(而不会分别调用5次SIGUSR1信号的处理程序)。
//kill -usr1,kill -usr2
(1)执行usr1信号处理程序,但是没执行完时,是可以继续进入到usr2信号处理程序里边去执行的,这个时候,相当于usr2信号处理程序没执行完毕,usr1信号处理程序也没执行完毕;此时再发送usr1和usr2都不会有任何响应;
(2)既然是在执行usr1信号处理程序执行的时候来了usr2信号,导致又去执行了usr2信号处理程序,这就意味着,只有usr2信号处理程序执行完毕,才会返回到usr1信号处理程序,只有usr1信号处理程序执行完毕了,才会最终返回到main函数主流程中去继续执行;