1.信号的基本概念:
①信号是很短的消息
②标准信号:标准信号没有给参数、消息或是其他相随的信息留有空间
③通常使用一个数字来标识一个信号
④信号可以被发送到一个进程或一组进程。
2.信号的目的:
①让进程知道已经发生了一个特定的事件
②强迫进程执行它自己代码中的信号处理程序
a.很多应用程序提供自己的信号处理程序
b.系统也会定义一些缺省的信号处理程序
信号举例: “Ctrl+c”组合键
在Linux环境下编写下面的程序
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void handler(int sig)
{
printf("get a sig: %d\n",sig);
exit(1);
}
int main()
{
int i=1;
for(;i<31;i++){
signal(i,handler);
}
while(1);
return 0;
}
运行程序后,在命令行中在输入其它命令,这时会发现命令不能执行,按Ctrl+c后才可正常运行其它命令,
这是因为:
1.⽤户按下Ctrl-C,这个键盘输⼊产⽣⼀个硬件中断。(Ctrl-Z等)
2. 如果CPU当前正在执⾏这个进程的代码,则该进程的⽤户空间代码暂停执⾏,CPU从⽤户态 切换到内核态处理硬件中断。
3. 终端驱动程序将Ctrl-C解释成⼀个SIGINT信号,记在该进程的PCB中(也可以说发送了⼀ 个SIGINT信号给该进程)。
4. 当某个时刻要从内核返回到该进程的⽤户空间代码继续执⾏之前,⾸先处理PCB中记录的信号,发现有⼀个SIGINT信号待处理,⽽这个信号的默认处理动作是终⽌进程,所以直接终⽌进程⽽不再返回它的⽤户空间代码执⾏。
5. 注意,Ctrl-C产⽣的信号只能发给前台进程。 ⼀个命令 后⾯加个&可以放到后台运⾏,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。 Shell可以同时运⾏⼀个前台进程和任意多个后台进程,只有前台进程才能接到像Ctrl-C这种控 制键产⽣的信号。
将上面程序中的主函数稍作修改,变成下面的主函数:
int main()
{
int i=1;
for(;i<31;i++){
signal(i,handler);
}
printf("div zero!\n");
int a=10;
a/=0;
printf("div zero done!\n");
return 0;
}
执行这段代码,结果显示如下图:
这个信号被捕获,是8号信号
3.Linux中的信号
在命令行中输入kill -l会显示出如下信号
[lichao@localhost signal]$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
列表中,每个信号都有⼀个编号和⼀个宏定义名称,编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。
Linux信号参考
http://www.jb51.net/LINUXjishu/173601.html
4.信号的产生
①异常
当一个进程出现异常(比如试图执行一个非法指令,除0,浮点溢出等),内核通过向进程发送一个信号来通知进程异常的发生
②其他进程
一个进程可以通过kill或是sigsend系统调用向另一个进程或一个进出组发送信号。一个进程也可以向自身发送信号
③终端
某些键盘字符如ctrl+c等会向终端的前台进程发送信号
5.信号的应答方式
①显式的忽略这个信号
多数信号都可以使用这种方式进行处理。
②执行系统默认的缺省操作,可以是:
Terminate:进程被杀死
Dump:进程被杀死,且如果可能,创建包含进程上下文的可用于调试的core文件
Ignore:简单的忽略信号
Stop:进程被停止,状态置为TASK_STOPPED
Continue:如果进程被挂起,则状态置为TASK_RUNNING。否则忽略该信号
③捕获信号
为了执行用户希望的对某个事件的处理,可以由用户指定某个信号的处理函数。
1.阻塞信号
实际执⾏信号的处理动作称为信号递达(Delivery),信号从产⽣到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block )某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的⼀种处理动作。
每个信号都有两个标志位分别表⽰阻塞(block)和未决(pending),还有⼀个函数指针表⽰处理动作。信号产⽣时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。
下面是与信号阻塞相关的几个函数:
信号集操作函数:sigset_t类型
对于每种信号⽤⼀个bit表⽰“有效”或“⽆效”状态
头文件signal.h
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。注意,在使用sigset_t类型的变量之前,⼀定要调 ⽤sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以 在调⽤sigaddset和sigdelset在该信号集中添加或删除某种有效信号。这四个函数都是成功返回0,出错返回-1 。 sigismember是⼀个布尔函数,用于判断⼀个信号集的有效信号中是否包含某种 信号,若包含则返回1 ,不包含则返回0,出错返回-1 。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset));
sigprocmask()函数能够根据参数how来实现对信号集的操作,操作主要有三种:
①SIG_BLOCK 在进程当前阻塞信号集中添加set指向信号集中的信号
②SIG_UNBLOCK 如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞
③SIG_SETMASK 更新进程阻塞信号集为set指向的信号集
如果调⽤sigprocmask解除了对当前若⼲个未决信号的阻塞,则在sigprocmask返回前,⾄少将其中 ⼀个信号递达
int sigpending(sigset_t *set));
sigpending(sigset_t *set))获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。
int sigsuspend(const sigset_t *mask));
sigsuspend(const sigset_t *mask))用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。
代码:
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/types.h>
int showpending(sigset_t *pending)
{
int i=1;
for(;i<=31;i++){
if(sigismember(pending,i)){//判断指定信号是否在目标集合中
printf("1");
}else{
printf("0");
}
}
printf("\n");
}
void handler(int sig)
{
printf("get a sig: %d\n",sig);
}
int main()
{
sigset_t blockSet,oblockSet,pending;//定义信号集对象
sigemptyset(&blockSet);//清空初始化
sigemptyset(&oblockSet);
sigaddset(&blockSet,2);//(Ctrl-C)在该信号集中添加2号信号
signal(2,handler);
sigprocmask(SIG_SETMASK,&blockSet,&oblockSet);//设置阻塞信号集,阻塞SIGINT信号
int count=0;
while(1){
sigpending(&pending);//获取未决信号集
showpending(&pending);
sleep(1);
if(count++==10){
printf("recover proc block set!");
sigprocmask(SIG_SETMASK,&oblockSet,NULL);
}
}
return 0;
}
一开始打印的全0,二号信号被阻塞,按下Ctrl-C,二号信号被递达(忽略)后打印01000….,十秒后,一直打印全0;