Linux 信号(signal)
目录
1 信号的本质
2 信号列表
3 信号发送时机
3.1 内核自动给进程发送信号
3.2 进程给进程发送信号
4 信号处理时机
5 统一事件源
1 信号的本质
软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。
收到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:
- 类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。
- 忽略某个信号,对该信号不做任何处理,就象未发生过一样。
- 对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。
2 信号列表
自己的UNIX操作系统支持多少种信号,可以在命令行中通过kill -l指令查看。一般来说MAC支持31种信号,Linux支持64种信号。不同平台支持的信号都差不多,因为毕竟都是按照POSIX标准来的。
SIGHUP | 1 | A | 终端挂起或者控制进程终止 |
SIGINT 2 | 2 | A | 键盘中断(如break键被按下) |
SIGQUIT | 3 | C | 键盘的退出键被按下 |
SIGILL | 4 | C | 非法指令 |
SIGABRT | 6 | C | 由abort(3)发出的退出指令 |
SIGFPE | 8 | C | 浮点异常 |
SIGKILL | 9 | AEF | Kill信号 |
SIGSEGV | 11 | C | 无效的内存引用 |
SIGPIPE | 13 | A | 往一个写端关闭de socket中连续写入数据 |
SIGALRM | 14 | A | 由alarm(2)发出的信号 |
SIGTERM | 15 | A | 终止信号 |
SIGUSR1 | 30,10,16 | A | 用户自定义信号1 |
SIGUSR2 | 31,12,17 | A | 用户自定义信号2 |
SIGCHLD | 20,17,18 | B | 子进程结束信号 |
SIGCONT | 19,18,25 | 进程继续(曾被停止的进程) | |
SIGSTOP | 7,19,23 | DEF | 终止进程 |
SIGTSTP | 18,20,24 | D | 控制终端(tty)上按下停止键 |
SIGTTIN | 21,21,26 | D | 后台进程企图从控制终端读 |
SIGTTOU | 22,22,27 | D | 后台进程企图从控制终端写 |
处理动作一项中的字母含义如下:
- A 缺省的动作是终止进程
- B 缺省的动作是忽略此信号,将该信号丢弃,不做处理
- C 缺省的动作是终止进程并进行内核映像转储(dump core),内核映像转储是指将进程数据在内存的映像和进程在内核结构中的部分内容以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员提供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并且可以调试他们的程序。
- D 缺省的动作是停止进程,进入停止状况以后还能重新进行下去,一般是在调试的过程中(例如ptrace系统调用)
- E 信号不能被捕获
- F 信号不能被忽略
3 信号发送时机
信号发送时机主要有两种,一种是内核自动给进程发送信号。另一种是进程主动给进程发送信号,此时可以是当前进程给当前进程发信号,也可以是进程A给进程B发信号。下面分别解释:
3.1 内核自动给进程发送信号
这里单独列出内核给进程发送信号其实有点牵强,因为内核本身就属于进程地址空间的一部分,只不过这部分地址空间是所有进程共享的。这里只讲一个信号,SIGALARM。
此信号和alarm系统调用有关,alarm()系统调用是给调用进程设置一个告警时间值,到达那个告警时间值内核自动给进程发一个SIGALARM信号,其实现过程是这样的:进程调用alarm()系统调用会传一个时间参数(以秒为单位),该系统调用会在调用进程的task_struct.alarm字段上加上指定的秒然后退出系统调用。内核调度程序schedule()每次执行的时候会遍历一遍进程数组列表里面的所有进程,只要发现有进程当前时间的值已经大于task_struct.alarm的值,就给该进程发一个SIGALARM信号,并重置该进程的alarm=0。我们姑且认为这种信号发送机制为内核自动给进程发送的。
3.2 进程给进程发送信号
进程给进程发送信号分为进程给自己发信号,进程给其他进程发信号两种。大部分应用场景都是进程给其他进程发信号。
不管如何进程给进程发信号都要通过系统调用kill(pid , sig)来实现,注意这里kill不仅仅代表杀死进程的意思,虽然大多数信号都是杀死进程。这里有一个限制,发送进程的euid必须和接受进程的euid相同,或者发送进程具有超级用户权限。该函数的参数说明如下(pid标志接收信号的进程,sig标志要发送的信号):
- pid > 0 , pid代表进程号,即给某单个进程发信号,该单个进程由pid来唯一标志。
- pid = 0 , 信号被发送给当前进程的进程组中的所有进程,这里的一个隐含条件是发送信号的进程必须是进程组的组长。
- pid = -1 , 信号被发送给除0号进程进程外的所有进程。
- pid < -1 , 信号被发送给进程组中的所有进程(进程组号=-pid)。
如果是进程自己给进程自己发信号,则一般是在进程执行程序中调用kill(pid,sig)。
如果是进程自己给其他进程发信号,可选择的方式就很多了,可以在进程代码执行过程中发送,也可以用命令行发送。
用命令行发送信号的格式一般是 kill -sig pid。其实用命令行发送信号本质也是进程给进程发信号。
4 信号处理时机
信号在被进程从内核态转到用户态的时候执行。常见的执行态切换有 系统调用返回, 时钟中断返回。这两种本质上都是中断处理返回。Linux当中系统调用的返回值放在eax寄存器中,接着内核代码判断进程状态,如果进程状态不是0,则去执行调度程序。如果进程时间片到期,则也去执行调度程序。
接着开始检查当前进程的task_struct.signal & ~task_struct.blocked , 如果有收到未被屏蔽的信号,则按照信号从低位到高位的方式依次调用do_signal信号处理函数。
从这里可以看出,信号的处理过程是异步的,并不是说给进程发了信号,进程就立刻马上执行完当前指令就去执行信号处理程序。而是选择在从内核态返回到用户态的时候检查处理。一个进程在执行过程中可能没有系统调用(第一次创建fork(),最后一次退出exit()除外),或者系统调用的频率非常低,所以我们发的信号有可能很长时间得不到处理。但是时钟中断发生的频率非常频繁,并且是匀速发生的,所以在这里从时钟中断的角度来说的话,可以认为信号的处理是准实时进行的。
5 统一事件源
当信号发生的时候进入信号处理函数,信号处理函数只是简单的发送到管道,IO复用程序会监听管道的另一头如果有数据到来就进入主循环内获取信号值,根据不同的信号值来处理对应的业务逻辑。
代码实现:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <pthread.h>
#include <signal.h>#define MAX_EVENT_NUMBER 1024static int pipefd[2];int setnonblocking(int fd)
{int old_option = fcntl(fd, F_GETFL);int net_option = old_option | O_NONBLOCK;fcntl(fd, F_SETFL, new_option);return old_option;
}void addfd(int epollfd, int fd)
{struct epoll_event event;event.data.fd = fd;event.events = EPOLLIN | EPOLLET;epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);setnonblocking(fd);
}/*信号处理函数,通过管道通知主程序*/
void sig_handler(int sig)
{int save_errno = errno;int msg = sig;send(pipefd[1],(char*)&msg, 1, 0);errno = save_errno;
}void addsig(int sig)
{struct sigaction sa;memset(&sa, 0, sizeof(sa));sa.sa_handler = sig_handler;sa.sa_flags |= SA_RESTART;sigfillset(&sa.sa_mask);assert(sigaction(sig, &sa, NULL)!=-1);
}int main(int argc, char** argv)
{if (argc <= 2) {printf("usage:%s <ip> <port>\n",basename(argv[0]));return 1;}const char* ip = argv[1];int port = atoi(argv[2]);int ret = 0;struct sockaddr_in svr_addr;memset(&svr_addr, 0, sizeof(svr_addr));svr_addr.sin_family = AF_INET;inet_pton(AF_INET, ip, &svr_addr.sin_addr);svr_addr.sin_port = htons(port);int listenfd = socket(AF_INET, SOCK_STREAM, 0);bind(listenfd, (struct sockaddr*)&svr_addr, sizeof(svr_addr));listen(listenfd, 5);epoll_event events[MAX_EVENT_NUMBER];int epollfd = epoll_create(5);addfd(epollfd, listenfd);/*使用socketpair创建管道,也就是UNIX域套接字*/socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd);setnonblocking(pipefd[1]);addfd(epollfd,pipefd[0]);addsig(SIGHUP);addsig(SIGCHLD);addsig(SIGTERM);addsig(SIGINT);bool stop_server = false;while (!stop_server) {int number = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);if ((number < 0) && (errno != EINTR)) {printf("epoll fails!\n");break;}int i = 0;for (;i < number;i++) {int sockfd = events[i].data.fd;if (sockfd == listenfd) {struct sockaddr_in cli_addr;socklen_t cli_addr_len = sizeof(cli_addr);int clifd = accept(listenfd, (struct sockaddr*)cli_addr, &cli_addr_len);addfd(epollfd, clifd);}else if ( (sockfd == pipefd[0]) && (events[i].events & EPOLLIN) ) {int sig;char signals[1024];ret = recv(pipefd[0], signals, sizeof(signals), 0);if (ret == -1) {continue;}else if(ret == 0) {continue;}else {/*每个信号值占1字节,所以按字节逐个接收信号,以SIGTERM为例,来说明如何安全地终止服务器主循环*/int i = 0;for (; i < ret; ++i) {switch(signals[i]) {case SIGCHLD:case SIGHUP:{continue;}case SIGTERM:case SIGINT:{stop_server = true;}}}}}else {//这部分处理其他socket获取的数据}}}printf("close fds\n");close(listenfd);close(pipefd[0]);close(pipefd[1]);return 0;
}
参考:
1、linux信号机制
Linux 信号(signal)相关推荐
- linux 信号没有被处理方法,[计算机]Linux 信号signal处理机制.doc
[计算机]Linux 信号signal处理机制 Linux 信号signal处理机制 信号是Linux编程中非常重要的部分,本文将详细介绍信号机制的基本概念.Linux对信号机制的大致实现方法.如何使 ...
- linux signal用法,Linux 信号 signal 用法详解及注意事项
Linux 信号 signal 用法详解及注意事项 1) SIGHUP 本信号在用户终端连接 (正常或非正常) 结束时发出, 通常是在终端的控 制进程结束时, 通知同一 session 内的各个作业, ...
- Linux 信号signal\sigaction
转发:作者,故事狗 https://www.jianshu.com/p/f445bfeea40a Linux 信号signal 对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号.信 ...
- 非常好的一篇对linux信号(signal)的解析
[摘要]本文分析了Linux内核对于信号的实现机制和应用层的相关处理.首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理.接着分析了内核对于信号的处理流程包括信号的触发/注册/执 ...
- Linux信号signal的介绍和示例
如何让程序在后台运行 在之前的章节中,如果要运行程序,在命令提示行下输入程序名后回车,程序被执行,然后等待程序运行完成.在程序运行的过程中,可以用Ctrl+c中止它. 在实际开发中,我们需要让程序在后 ...
- linux信号11sigtstp,Linux信号(signal)机制
信号(signal)是一种软中断,信号机制是进程间通信的一种方式,采用异步通信方式 一.信号类型 Linux系统共定义了64种信号,分为两大类:可靠信号与不可靠信号,前32种信号为不可靠信号,后32种 ...
- Linux信号signal介绍,sigaction结构体,signal()函数,sigaction()函数
信号(signal)是一种进程间通信机制,它给应用程序提供一种异步的软件中断,使应用程序有机会接受其他程序活终端发送的命令(即信号).应用程序收到信号后,有三种处理方式:忽略,默认,或捕捉.进程收到一 ...
- 非常好的一篇对linux信号(signal)的解析 (转载)
Linux信号(signal) 机制分析 转载至:https://www.cnblogs.com/hoys/archive/2012/08/19/2646377.html [摘要]本文分析了Linux ...
- Linux 信号signal处理函数--转
alarm(设置信号传送闹钟) 相关函数 signal,sleep 表头文件 #include<unistd.h> 定义函数 unsigned int alarm(unsigned int ...
最新文章
- 分布式系统 一致性模型的介绍 以及 zookeeper的 “线性一致性“ 讨论
- 9个元素换6次达到排序序列_十大算法排序(Sorting Algorithm) Study notes
- Android dependency 'com.android.support:support-v4' has different version for the compile (26.1.0...
- 中国大学moocpython笔记_中国大学MOOC_高级语言程序设计(Python)笔记
- oracle发生重启动的介绍
- vs2015 下配置sfml
- C#中 ?? 的用法
- 021.4 IO流——字节、字符桥梁(编码解码)
- 三种 绘制奈奎斯特曲线 的方法
- 编译安装Greenplum源码包
- 图片验证码获取及验证
- golang读取pdf
- android 电视 vob格式转换,佳佳Android视频格式转换器
- Windows进行磁盘碎片化整理
- 在PHP中用sleep导致诡异事件
- 山景BP1048使用记录
- Python打开系统资源管理器并选中文件
- 秦储一行拜访陕西省文联和陕西新华出版传媒集团
- Android-Dialogs(一) AlterDialog基本使用
- Hive left semi join ,select 和 where中不能出现右表字段/不会生成笛卡尔积
热门文章
- 【编译原理笔记14】中间代码生成:布尔表达式的回填,控制流语句的回填,switch语句的翻译,过程调用语句的翻译
- C#------如何获取本机IP地址
- TCP三次握手连接和TCP四次挥手及大量TIME_WAIT解决方法:
- ajax将数据显示在class为content的标签中_python爬取微博评论(无重复数据)
- eclipse报错: Unhandled event loop exception No more handles
- 博客园博客美化相关文章目录
- qtp xml联合xsl输出html报表,通过xml和xsl实现数据和页面展示模板的解耦(简单完整网站代码示例)...
- src与href区别
- ORA-06413 连接未打开的处理办法【独家办法】
- spring batch 读mysql_spring batch csv文件导入到mysql数据库