异步处理方式之信号(一):基础知识和signal函数说明
文章目录
1. 引言
信号是一种软中断。很多比较重要的应用程序都需要处理信号。信号提供了一种异步处理事件的方法,例如:终端用户输入中断键,会通过信号机制终止一个程序等。早期的信号存在丢失的风险,且执行在临界代码区时无法关闭所选择的信号,后来一些系统便增加了可靠信号机制。下面的章节提供详细的说明。
2. 信号的概念
首先,每一个信号都有一个名字。这些名字都是以"SIG"开头的。Linux支持31种基本信号,不同的操作系统可能支持的信号数量略有不同。信号是在头文件<signal.h>中定义的,且每一种信号都被定义为整形常量(信号编号)。
toney@ubantu:~$ 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
不存在编号为0的信号。在后面的章节中会说明编号为0的信号的特殊用途。
产生信号的条件有很多:
- 当用户按某些终端键时会产生信号。例如使用‘Delete’键会产生SIGINT信号(有些系统中组合键Ctrl+C也会产生相同的效果)。
- 硬件异常产生信号。例如:除数为0、无法的内存访问(常见的有段错误)等。这些条件通常是由硬件检测到的,并通知内核,之后由内核产生适当的信号并通知该进程。
- 进程调用kill(2)函数可将任意信号发送给另一个进程或者进程组。对此有一个限制:要么发送信号的进程所有者是超级用户,要么发送进程和接收进程拥有相同的所有者。
- 用户调用kill(1)命令将信号发送给其他的进程。我们常用此命令终止(个人更喜欢说杀死)一个后台进程。
- 当检测到某种软件条件发生时,系统也会产生相应的信号通知该进程。例如定时时间到产生SIGALRM信号、管道读进程已经关闭却任然要往管道中写数据时产生SIGPIPE信号。
信号是异步事件的典型示例。产生信号的事件对进程而言是随机出现的。进程不能通过测试一个简单的变量(如errno)来判断是否有信号发生,而是应该告诉内核:“当此信号发生时,应该执行如下操作”。这里一共有三种方式可供选择:
(1)忽略此信号。
(2)捕捉此信号。
(3)执行系统默认操作。
2.1 信号操作之忽略信号
首先来说忽略信号的用法。大多数的信号都可以使用这种方式来处理信号,但是有两种信号是绝不能被忽略的,它们分别是SIGKILL和SIGSTOP信号。这有两种信号不能被忽略的原因是:它们向内核和用户提供了使进程终止或者停止的可靠方法。此外,如果忽略某些由硬件产生的信号(例如SIGSEG信号),会导致软件出现无法预料的问题。
2.2 信号操作之捕捉信号
为了实现捕捉信号的目的,我们必须通知内核在某种信号发生时,调用一个用户函数。在用户函数中,我们可以执行我们希望对该信号的处理方式。例如我们可以捕捉SIGALRM信号,当定时时间到时打印某些提示信息等。注意:不能捕捉SIGKILL和SIGSTOP信号。
2.3 信号操作之执行系统默认操作
对于大多数信号的系统默认操作都是终止该进程。
2.4 常见的信号
下表中列出了31中信号编号、信号名称,Linux系统的默认操作,并对其中常见或者常用到的信号做了一个简单的说明。如果以后用到再做详细补充说明。
| 序号 | 信号名称 | 说明 | 默认操作 |
|---|---|---|---|
| 1 | SIGHUP | 暂不介绍 | terminate |
| 2 | SIGINT | 当用户按中断键(一般是Ctrl+C或Delete键)时,驱动程序会产生此信号来终止进程。 | terminate |
| 3 | SIGQUIT | 当用户按退出键(一般是Ctrl+)时,中断驱动程序会产生该信号,发送给所有前台进程。 | coredump |
| 4 | SIGILL | 该信号表示已经执行一条非法硬件指令。 | coredump |
| 5 | SIGTRAP | 指示一个实现的硬件故障。 | coredump |
| 6 | SIGABRT | 调用abort()函数来终止进程时会产生该信号。 | coredump |
| 7 | SIGBUS | 指示一个已定义的硬件故障 | coredump |
| 8 | SIGFPE | 表示算数运算异常。例如除0操作,浮点溢出等。 | coredump |
| 9 | SIGKILL | 无法被忽略和捕捉的信号。它向系统提供一种可以杀死任意进程的可靠方法。 | terminate |
| 0 | SIGUSR1 | 用户定义的信号,可用于应用程序 | terminate |
| 11 | SIGSEGV | 无效的内存访问。例如经典的“段错误”。 | coredump |
| 12 | SIGUSR2 | 用户定义的另一个信号,可用于应用程序 | terminate |
| 13 | SIGPIPE | 管道的读进程已经终止时写管道会产生该信号。 | terminate |
| 14 | SIGALRM | 当使用alarm()函数,或者setitimer()设置的定时时间到时会产生此信号 | terminate |
| 15 | SIGTERM | 是由kill(1)命令发送的系统默认终止信号。该信号可被应用程序捕获,从而进行清理工作,完成优雅的终止(相对于SIGKILL而言,SIGKILL信号不能被捕获或者忽略)。 | terminate |
| 16 | SIGSTKFLT | 暂不介绍 | |
| 17 | SIGCHLD | 一个进程终止或者停止时,SIGCHLD信号会发送给其父进程。按系统默认,将忽略此信号。如果父进程需要被告知该子进程退出状态,则需要捕捉此信号。一般在信号处理函数中调用wait()函数回收子进程的资源。 | ignore |
| 18 | SIGCONT | 作业控制信号。它用来发送给需要继续运行,但当前处于停止状态的进程。收到此信号后,挂起的进程继续运行。如果本来已经在运行则忽略该信号。 | ignore |
| 19 | SIGSTOP | 作业控制信号,它停止一个进程。不能被捕捉或忽略。 | stop |
| 20 | SIGTSTP | 交互停止信号。当用户按挂起键(一般是Ctrl+z)时,中断驱动程序产生此信号。 | stop |
| 21 | SIGTTIN | 暂不介绍 | stop |
| 22 | SIGTTOU | 暂不介绍 | stop |
| 23 | SIGURG | 暂不介绍 | ignore |
| 24 | SIGXCPU | 暂不介绍 | coredump |
| 25 | SIGXFSZ | 暂不介绍 | coredump |
| 26 | SIGVTALRM | 暂不介绍 | terminate |
| 27 | SIGPROF | 暂不介绍 | terminate |
| 28 | SIGWINCH | 暂不介绍 | ignore |
| 29 | SIGIO | 暂不介绍 | terminate |
| 30 | SIGPWR | 暂不介绍 | terminate |
| 31 | SIGUNUSED / SIGSYS | 一个无效的系统调用 | coredump |
3. 函数signal
3.1 signal函数介绍
Unix系统信号机制最简单的接口是signal函数。
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
返回值:若成功,返回之前的信号处理配置;若失败,返回SIG_ERR.
代码中:
signo是指信号名称(详情参见2.4常见的信号)
func是常量SIG_IGN、SIG_DFL或者接收到信号时自定义的信号处理函数地址。
- 如果为SIG_IGN, 则向内核表示要忽略此信号
- 如果为SIG_DFL, 则表示接收到此信号时执行系统的默认操作。
- 当指定的是函数地址时,则在信号发生时,由内核调用该函数。我们称此函数为信号处理函数,或者信号捕捉函数
说句心里话,signal的函数原型看起来有点看不懂:( 。下面我们也按前辈先人的说法再熟悉下(原话):
本节开头所示的signal函数原型太复杂了,如果使用下面的typedef,则可以使其简单些。
typedef void Sigfunc(int); (3-1)
然后,可将signal函数原型写成:
Sigfunc *signal(int, Sigfunc *); (3-2)
这样,signal的函数看起来就简单了很多:signal函数要求两个参数,并返回一个函数指针(如3-2所示),而该函数指针指向的函数有一个整型参数且无返回值(如3-1所示)。
用通俗一点的话描述:定义一个信号处理函数,它有一个整型参数signo, 无返回值;当调用signal函数设置信号处理程序时,signal函数的第二个参数是指向该信号处理函数的指针,signal函数的返回值是指向未修改之前的信号处理函数指针。
在上述的描述中,我们提到了三个宏定义: SIG_IGN、SIG_DFL、SIG_ERR。这三个宏Linux上的原型如下:
typedef void __sighandler_t(int);
#define SIG_DFL ((__sighandler_t)0) /* default signal handling */
#define SIG_IGN ((__sighandler_t)1) /* ignore signal */
#define SIG_ERR ((__sighandler_t)-1) /* error return from signal */
这三个常量可用于表示“指向函数的指针”。
3.2 signal函数示例
该实例中定义了两个信号处理函数,捕获了三个信号(SIGUSR1, SIGUSR2共用一个信号处理函数)。
/*************************************************************************
> File Name: signal_demo.c
> Author: Toney Sun
> Mail: vip_13031075266@163.com
> Created Time: 2020年04月27日 星期一 11时50分47秒
************************************************************************/
#include <stdio.h>
#include <signal.h>
static void sig_handler(int); /*自定义的信号处理函数*/
static void sig_usr(int); /*自定义的信号处理函数*/
int signal_install()
{
if(signal(SIGINT, sig_handler)==SIG_ERR){
printf("SIGINT handle function register error\n");
}
if(signal(SIGUSR1, sig_usr)==SIG_ERR){
printf("SIGUSR1 handle function register error\n");
}
if(signal(SIGUSR2, sig_usr)==SIG_ERR){
printf("SIGUSR2 handle function register error\n");
}
}
void sig_handler(int signo)
{
if(signo == SIGINT){
printf("Recieved SIGINT signal\n");
}else{
printf("sig_handler receieve Error signal\n");
}
}
void sig_usr(int signo)
{
if(signo == SIGUSR1){
printf("Recieved SIGUSR1 signal\n");
}else if(signo == SIGUSR2){
printf("Recieved SIGUSR2 signal\n");
}else{
printf("sig_usr receieve Error signal\n");
}
}
void main(int argc, char *argv[])
{
//signal_demo();
//exec_funcs();
signal_install();
while(1){
pause();
}
}
结果如下:
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out
^CRecieved SIGINT signal
^CRecieved SIGINT signal
^CRecieved SIGINT signal
^Z
[3]+ Stopped ./demo.out
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ ./demo.out &
[6] 19518
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ kill -USR2 19518
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ Recieved SIGUSR2 signal
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ kill -USR1 19518
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$ Recieved SIGUSR1 signal
toney@ubantu:/mnt/hgfs/em嵌入式学习记录/schedule调度器$
3.3 signal函数的限制
- 如果想使用signal函数来获取当前进程对某一信号的处理方式,会修改当前的处理方式,否则无法确定当前的处理方式。常见的用法如下:
if(signal(SIGINT, SIG_IGN)!=SIG_IGN)
signal(SIGINT, sig_handler);
if(signal(SIGUSR1, SIG_IGN)!=SIG_IGN)
signal(SIGINT, sig_usr);
后面我们将使用另一种信号处理方式:sigaction()函数,此函数无需修改便可以查询当前的处理方式。
进程创建
当一个进程调用fork时,其子进程继承了父进程的信号处理方式。因为子进程在创建时复制了父进程的内存映像,所以信号捕捉函数的地址在子进程中是有效的。
异步处理方式之信号(一):基础知识和signal函数说明的更多相关文章
- 异步处理方式之信号(三):kill、raise、alarm、pause函数简介
文章目录 6. 函数kill和raise 7. 函数alarm和pause 7.1 alarm() 7.2 pause() 6. 函数kill和raise kill函数用来将信号发送给进程或者进程组. ...
- python基础-基础知识(包括:函数递归等知识)
老男孩 Python 基础知识练习(三) 1.列举布尔值为 False 的值空,None,0, False, ", [], {}, () 2.写函数:根据范围获取其中 3 和 7 整除的所有 ...
- js基础知识之_函数
javascript函数 函数概念 将完成某一特定功能的代码集合起来,可以重复使用 白话函数理解-函数就是一个工厂,帮大家实现某一个功能 优点 -时程序更加简洁 -逻辑更有条例 -调用方便 -维护更加 ...
- Linux 信号详解一(signal函数)
信号列表 SIGABRT 进程停止运行 SIGALRM 警告钟 SIGFPE 算述运算例外 SIGHUP 系统挂断 SIGILL 非法指令 SIGINT 终端中断 SIGKILL 停止进程(此信号不能 ...
- js if for 详解 获取元素方式 及一些js 基础知识
##获取元素的新方法## --document.querySelector('Css Selector{css选择器}') 接收一个css选择器(通配,群组,类,包含,id....等) 若这个选择器对 ...
- ES6基础知识(Generator 函数应用)
1.Ajax 是典型的异步操作,通过 Generator 函数部署 Ajax 操作,可以用同步的方式表达 function* main() { var result = yield request(& ...
- Js基础知识6-JavaScript匿名函数和闭包
匿名函数 1,把匿名函数赋值给变量 var test = function() { return 'guoyu'; }; alert(test);//test是个函数 alert(test()); 2 ...
- python基础知识梳理-----7函数
基本内容梳理 1:函数定义,函数名,函数体以及函数的调用方式 2:函数的返回值 3:函数的参数 4:函数---动态传参数 5:名称空间,局部名称的加载顺序,全局名称空间,作用域,加载顺序 6:函数的嵌 ...
- C++基础知识:成员函数、对象拷贝、私有成员
一.综述 类是我们自己定义的数据类型(新类型) 设计类时要考虑的角度: (1)站在设计和实现者的角度来考虑 (2)站在使用者的角度来考虑 (3)父类,子类 二.类基础 (1)一个类就是一个用户自己定义 ...
随机推荐
- 一键设置WPS_Office_2019专业版的定时自动备份的批处理文件
一键设置WPS_Office_2019专业版的定时自动备份的批处理文件 rem ================================================ rem 一键设置WPS ...
- 20初识前端HTML(1)
1 .HTML 1.1 网页的组成 文字 图片 链接 等元素构成.除了这些元素之外 网页中还可以包含音频 视频 等 1.2 WEB前端开发的流程 现在主流的开发流程: 前后端分离的开发模式. 美工:p ...
- C++ //继承同名静态成员处理方式
1 //继承同名静态成员处理方式 2 #include <iostream> 3 #include <string> 4 using namespace std; 5 6 cl ...
- 面试官:MySQL 有哪些锁??
大家好,我是小林. 这次,来说说 MySQL 的锁,主要是 Q&A 的形式,看起来会比较轻松. 不多 BB 了,发车! 在 MySQL 里,根据加锁的范围,可以分为全局锁.表级锁和行锁三类. ...
- Docker部署Mysql实践
前言:由于Docker部署容器时,没有指定IP,当机器重启后,容器的IP会变化,所以在创建容器的时候,最好能固定IP:同时,在Ubuntu系统中,每次执行命令,都需要root权限,命令需要加sudo标 ...
- JVM-超全图
- ECC(Ellipse Curve Cryptography)+AES(Advanced Encryption Standard)前端通讯加密模拟(使用eccrypto-js)
前置知识 不了解对称加密与非对称加密的小伙伴可以看看下面的文章,想详细学习与区块链有关的加密算法可以戳这里 对称与非对称加密 https://blog.csdn.net/u013320868/arti ...
- 浅谈模拟彩票代码,html,javascript
今天简单介绍一下用html,javascript来模拟双色球彩票选择器. 双色球彩票规则:由6个红球和1个蓝球组成,其中6个红球是从1-33中随机选出的不重复的6个数,从小到大一次排列:蓝球是1-16 ...
- C#基础知识---is与as
一.is与as对比 is检查一个对象是否兼容于指定的类型,并返回一个Boolean值:true或者fasle. 注:is操作符永远不会抛出异常 经常按如下方法使用: ClassA { .... } O ...
- 【java虚拟机】内存分配与回收策略
作者:平凡希 原文地址:https://www.cnblogs.com/xiaoxi/p/6557473.html 前言 对象的内存分配,往大的方向上讲,就是在堆上分配,少数情况下也可能会直接分配在老 ...