百金买骏马,千金买美人,万金买爵禄,何处买青春?
目录
信号的概念
信号的种类:
kill -l 命令可以查看信号列表
man 7 signal 查看信号详细内容
信号的产生
补充知识 Core Dump(转储内存)
补充知识:与信号相关的数据结构
对于不可靠信号
接收信号
信号的处理:
阻塞信号集:
未决信号集:
信号的捕获:
这里为了帮助理解,展了一段存在bug的代码:
运行结果:
结语
参考的文章
参考的书籍
信号的概念
在计算机科学中,信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。(摘自百度百科)
特别强调对 异步 和 中断 理解:因为信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。
信号的种类:
kill -l 命令可以查看信号列表
man 7 signal 查看信号详细内容
- 每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定 义 #define SIGINT 2
- Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,因此,把那些建立在早期机制上的信号叫做"不可靠信号",信号值小于SIGRTMIN(在我的机器里SIGRTMIN=34,SIGRTMAX=64)的信号都是不可靠信号。这就是"不可靠信号"的来源。
- 关于可靠信号和不可靠信号
信号的产生
- 按键产生,如:Ctrl+c、Ctrl+z、Ctrl+\
- 系统调用产生,如:kill()、raise()、abort()
- 软件条件产生,如:定时器alarm()、pause()
- 硬件异常产生,如:非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误)
- 命令产生,如:kill命令
上面的一些函数或命令都对百度百科进行了超链接,如果还有疑问,可以参考 《unix 环境高级编程[第10章]》
补充知识 Core Dump(转储内存)
- wait()和waitpid(),都有一个status参数,该参数是一个输出型参数,由操作系统填充。
- 如果传递NULL,表示不关心子进程的退出状态信息。
- 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。 status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图:
- 关于wait()和waitpid()所返回相关的宏
- linux中core dump开启使用教程
我们可以通过位运算,也可以通过宏来得到我们需要的信息,具体代码如下
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(void)
{
pid_t pid;
if ((pid = fork()) == -1)
perror("fork"), exit(1);
if (pid == 0) //child
{
sleep(4);
int a=13/0; //故意写错,让程序异常退出
exit(0xFF);
}
else
{
int st;
int ret = wait(&st);
// if(ret > 0 &&WIFEXITED(st) ) WIFEXITED(st)进程正常退出为真,和下面等价
if (ret > 0 && (st & 0X7F) == 0)
{ // 正常退出
printf("正常退出\n");
printf("宏:st:%p child exit code:%d sig code : %d \n",st,WEXITSTATUS(st),WTERMSIG(st));
printf("移位运算:st:%p child exit code:%d sig code : %d\n",st,(st >> 8) & 0XFF,st & 0X7F);
printf("core文件是否产生:%d,core dump 标志是:%d\n",WCOREDUMP(st),st & 0X80);
}
else if (ret > 0)
{ // 异常退出
printf("异常常退出\n");
printf("宏:st:%p child exit code:%d sig code : %d \n",st,WEXITSTATUS(st),WTERMSIG(st));
printf("移位运算:st:%p child exit code:%d sig code : %d\n",st,(st >> 8) & 0XFF,st & 0X7F);
printf("core文件是否产生:%d,core dump 标志是:%d\n",WCOREDUMP(st),st & 0X80);
//core会输出128,因为它刚好在第8位 ,即0X80
}
}
}
运行结果:
补充知识:与信号相关的数据结构
具体一点是这样子(图片来源)
然后我们再画简单点
对于不可靠信号
- 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
- SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
- SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。
接收信号
信号的处理:
- 执行默认动作
- 忽略(丢弃)
- 捕捉(调用户处理函数)
阻塞信号集:
- 将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(解除屏蔽后)。
未决信号集:
- 信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转回为0。这一时刻往往非常短暂。
- 信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。
介绍了这么多知识,现在我们来对信号再进行一个捕获
信号的捕获:
- 如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码 是在用户空间的,处理过程比较复杂,举例如下: 用户程序注册了SIGQUIT信号的处理函数sighandler。当前正在执行 main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号 SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler 和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返 回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复 main函数的上下文继续执行了。
这里为了帮助理解,展了一段存在bug的代码:
#include <stdio.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <signal.h> #include <unistd.h> void handler1(int sig){ int olderrno=errno; pid_t child=waitpid(-1,NULL,0); if(child<0) fprintf(stderr," waitpid error"); printf(" Handler reaped child %d\n",(int)child); sleep(1); printf("%s\n",strerror(olderrno)); } int main(){ /*第一个参数 是信号名 SIGHLD 在一个进程终止或者停止时,将SIGCHLD信号发送给其父进程,按系统默认将忽略此信号, 如果父进程希望被告知其子系统的这种状态,则应捕捉此信号。 第二参数是信号处理函数 其中 常量SIG_IGN代表忽略 SIG_DFL代表 按系统默认动作 */ //返回值 若成功,返回以前的信号处理配置; 若出错,返回 SIG_ERR if(signal(SIGCHLD,handler1)==SIG_ERR) //对信号进行捕获 fprintf(stderr,"signal error"); //创建子进程 int i=0; for(i=0;i<3;i++){ if(fork()==0){ //child printf("Hello i am child %d\n",(int)getpid()); exit(0); } } //父进程运行 printf("Hi i am parent %d\n",(int)getpid()); while(1) ; exit(0); }
运行结果:
这里我们注意到尽管发送了3个SIGCHLD信号给父进程,但其中只有两个信号被成功接收了,因此父进程只回收了两个子进程.而子进程31288则成为了一个僵尸进程.
那么是哪里出错了呢?这段代码来源《深入理解计算机系统(第8章 图8-36)》
结语
本文也仅仅是对信号的一个浅谈,意在先树立一个大的框架,希望对读者能有所帮助,作者本人也在一路学习的过程中,很多细节由于自身经验和时间的原因,并没办法给读者细细道来,但是本文所参考的文章和书籍均会在文末放上链接,当然我也很欢迎读者如果有问题可以私信于我或在底下评论,我也会很乐意分享我的见解.
这些是我们没有细细讨论的内容,其实每一个函数都很有意思,这边再次推荐一下这本好书《UNIX环境高级编程》,当然还有《深入理解计算机系统》
参考的文章
Linux应用与网络编程 - 随笔分类(第2页) - 浅墨浓香
Linux - 信号 | C++ 全栈知识体系
可靠信号与不可靠信号 - wsw_seu - 博客园
linux 信号处理 六(全) - 走看看
参考的书籍
《深入理解计算机系统》
《UNIX环境高级编程》