异步处理方式之信号(一):基础知识和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)一个类就是一个用户自己定义 ...
随机推荐
- Linux下MySQL基础及操作语法
什么是MySQL? MySQL是一种开源关系数据库管理系统(RDBMS),它使用最常用的数据库管理语言-结构化查询语言(SQL)进行数据库管理.MySQL是开源的,因此任何人都可以根据通用公共许可证下 ...
- 蓝凌OA前台任意文件读取漏洞利用
近期CNVD爆出漏洞编号:CNVD-2021-28277,首次公开日期为2021-04-15,蓝凌oa存在多个漏洞,攻击者可利用该漏洞获取服务器控制权.今天挑选一个蓝凌OA前台任意文件读取漏洞进行分析 ...
- OpenGL学习笔记(六)坐标系统
目录 一.衔接 二.概述 三.各个坐标系统 局部空间 世界空间 观察空间 裁剪空间 四.两种投影矩阵 正射投影 透视投影 五.把它们都组合到一起 六.编码实现 1. 实现卡片旋转 2. 实现正方体旋转 ...
- 第5篇-调用Java方法后弹出栈帧及处理返回结果
在前一篇 第4篇-JVM终于开始调用Java主类的main()方法啦 介绍了通过callq调用entry point,不过我们并没有看完generate_call_stub()函数的实现.接下来在ge ...
- 【动态规划】树形DP完全详解!
蒟蒻大佬时隔三个月更新了!!拍手拍手 而且是更新了几篇关于DP的文章(RioTian狂喜) 现在赶紧学习和复习一下树形DP.... 树形DP基础:Here,CF上部分树形DP练习题:Here \[QA ...
- 通过Mssql提权的几种姿势
本文记录针对SQL Server数据库,在拿到shell之后进行提权的5种方法. 一. xp_cmdshell提权 上面的数据库连接需要知道sa的密码,连接之后,在下面的sql命令处执行: exec ...
- Mysql使用存储过程快速添加百万数据
前言 为了体现不加索引和添加索引的区别,需要使用百万级的数据,但是百万数据的表,如果使用一条条添加,特别繁琐又麻烦,这里使用存储过程快速添加数据,用时大概4个小时. 创建一个用户表 CREATE TA ...
- NOIP 模拟 $17\; \rm 时间机器$
题解 \(by\;zj\varphi\) 一道贪心的题目 我们先将节点和电阻按左边界排序,相同的按右边界排序 对于每一个节点,我们发现,选取左边界小于等于它的电阻中右边界大于它且最接近的它的一定是最优 ...
- 题解 Emotional Flutter
传送门 因为一个等号挂掉了10pts 发现每个黑色段一定对应了一段不可行的出发区间 检查是否存在所有黑色段的并集的补集即可 具体来说,我们对于每个黑色段计算出一个(有的是两个)区间 \([l, r]\ ...
- MongoDB使用命令创建用户权错误分析--- 权限不够Error:couldn't add user:command createUser requires authentication
MongoDB使用命令创建用户权错误分析 错误一:权限不够Error:couldn't add user:command createUser requires authentication. 解决方 ...