APUE学习之信号(Signal)

目录

一、信号

1、基本概念

2、用户处理信号的方式     

3、查看信号

4、可靠信号和不可靠信号

5、信号种类

 6、终止进程信号的区别

二、进程对信号的处理

1、signal()函数

2、sigaction()函数

3、代码演示

4、运行结果

三、实战演练

 四、补充

1、alarm()函数

2、wait()函数

3、僵尸进程和孤儿进程


一、信号

1、基本概念

        信号是Linux系统中用于进程之间通信或者操作的机制,它给进程提供一种异步的软件中断(信号可以在任何时候发送给某一进程,而无须知道该进程的状态)。如果该进程并未处于执行状态,则该信号就由内核保存起来,直到该进程恢复执行并传递给他为止。如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

2、用户处理信号的方式     

进程接受到信号后,有三种处理方式:

(1)忽略:忽略某个信号,对该信号不做任何处理,就像从未发生过。

(2)捕捉:类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来进行处理。

(3)默认/缺省:Linux对每种信号都规定了默认操作,通常是终止该进程。

        注意:有两个信号比较特殊,需要特殊记一下——SIGKILL 和 SIGSTOP,这是两个不能捕捉的信号或忽略的信号。不能被忽略的原因是:他们向超级用户提供了使进程终止或停止的可靠方法。大概讲一下两个信号的区别,SIGKILL这是一个 “我不管您在做什么,立刻停止”的信号。假如您发送SIGKILL信号给进程,Linux就将进程停止在那里。SIGSTOP 停止进程的执行,但是该进程还未结束, 只是暂停执行.。

3、查看信号

        我们可以使用如下的命令查看当前系统支持的信号,需要注意的是不同的系统支持的信号是不一样的:

查看信号的命令:kill    -l

给大家看一下我系统所支持的信号:

        注意:信号本质上是 int 类型的数字编号。内核针对每个信号,都给其定义了一个唯一的整数编号,从数字 1 开始顺序展开。并且每一个信号都有其对应的名字(其实就是一个宏), 信号名字与信号编号乃是一一对应关系,但是由于每个信号的实际编号随着系统的不同可能会不一样,所 以在程序当中一般都使用信号的符号名(也就是宏定义)。这些信号在头文件中定义,每个信号都是以 SIGxxx 为开头。

4、可靠信号和不可靠信号

        Linux信号机制基本上是从UNIX系统中继承过来的。早期UNIX系统中的信号机制比较简单和原始,信号值小于SIGRTMIN的信号都是不可靠信号。这就是“不可靠信号”的来源,它的主要问题就是信号可能丢失。随着时间发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已经有许多应用,不好再做改动,最后只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。

        信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。对于目前Linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数,而经过signal安装的信号不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。

5、信号种类

        经过上面的学习,我们已经知道了如何查看信号,那么每个信号都代表着什么意思呢?该信号的默认动作又是什么呢?让我们继续往下学习吧!(红色信号为常见信号,其余信号了解即可)

信号编号信号名信号说明默认动作
1SIGHUP在终端的控制进程结束时发出程序终止
2SIGINTCTRL+c按键终止程序运行的信号程序终止
3SIGQUITCTRL+\按键输入时产生的信号程序终止
4SIGILL非法的指令程序终止
5SIGTRAP跟踪自陷,由断点指令或其它trap指令产生建立CORE文件
6SIGABRT当调用abort函数时会产生当前信号程序终止
7SIGBUS运行非本CPU相关编译器编译的程序程序终止
8SIGFPE算术异常时产生建立CORE文件
9SIGKILL强制杀死程序序号,任何程序都不可以捕捉该信号程序终止,不可被捕捉
10SIGUSR1用户自定义信号1,不会自动产生,只能使用kill函数或者命令给指定的进程发送当前信号程序终止
11SIGSEGV段错误系统给程序发送的信号程序终止
12SIGUSR2用户自定义信号2,不会自动产生,只能使用kill函数或者命令给指定的进程发送当前信号程序终止
13SIGPIPE管道破裂信号程序终止
14SIGALRM当alarm函数设置的时间到达时,会产生当前信号程序终止
15SIGTERMkill命令默认发送的信号,默认动作是终止信号程序终止
16SIGSTKFLT数学协处理器的栈异常程序终止
17SIGCHLD子进程退出信号忽略该信号
18SIGCONT当产生当前信号后,当前停止的进程会恢复运行停止的进程恢复运行
19SIGSTOP停止进程的执行停止进程
20SIGTSTPCTRL+z按键输入时产生的信号,但该信号可以被处理和忽略停止进程
21SIGTTIN后台进程读终端停止进程
22SIGTTOU后台进程写终端停止进程
23SIGURG有"紧急"数据或out-of-band数据到达socket时产生忽略该信号
24SIGXCPU超出CPU限制程序终止
25SIGXFSZ文件长度过长程序终止
26SIGVTALRM虚拟定时器超时程序终止
27SIGPROF统计分布图用计时器到时程序终止
28SIGWINCH终端窗口尺寸发生变化忽略该信号
29SIGIO异步IO程序终止
30SIGPWR电力故障程序终止
31SIGSYS无效系统调用程序终止

 6、终止进程信号的区别

        经过上面的学习,我们知道终止进程的信号有三种,即SIGINT、SIGKILL、SIGTERM,三者都是结束/终止进程运行.但三者之间却有区别。让我们一起来看看吧!

(1)SIGINT
        产生方式: 键盘Ctrl+C
        产生结果: 只对当前前台进程,和他的所在的进程组的每个进程都发送SIGINT信号,之后这些进程会执行信号处理程序再终止。

(2)SIGTERM
        产生方式: 和任何控制字符无关,用kill函数发送
        本质: 相当于 kill  pid
        产生结果: 当前进程会收到信号,而其子进程不会收到.如果当前进程被kill(即收到SIGTERM),则其子进程的父进程将为init,即pid为1的进程。与SIGKILL的不同,SIGTERM可以被阻塞,忽略,捕获,也就是说可以进行信号处理程序,那么这样就可以让进程很好的终止,允许清理和关闭文件。

(3)SIGKILL
        产生方式: 和任何控制字符无关,用kill函数发送
        本质: 相当于kill -9 pid
        产生结果: 当前进程收到该信号,注意该信号是无法被捕获的,也就是说进程无法执行信号处理程序,会直接发送默认行为,也就是直接退出。这也就是为什么kill -9 pid一定能杀死程序的原因。 故这也造成了进程被结束前无法清理或者关闭资源等行为。

二、进程对信号的处理

        Linux下有signal()和sigaction()两种信号安装的函数,让我们分别来看看:

1、signal()函数

函数原型如下:

#include    <signal.h>

 

typedef void (*sighandler_t)(int);

sighandler_t   signal(int   signum, sighandler_t   handler);

函数描述:

        signal函数用来在进程中指定当一个信号到达进程后该做什么处理,信号处理函数的handler有两个默认值,分别是SIG_IGN表示忽略行为和SIG_DFL表示默认行为。而且signal函数是阻塞的,比如当进程正在执行SIGUSR1信号的处理函数,此时又来一个SIGUSR1信号,signal会等到当前信号处理函数处理完后才继续处理后来的SIGUSR1。

2、sigaction()函数

函数原型如下:

#include    <signal.h>

 

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

 

stuct sigaction
{
      void (*)(int) sa_handle;        //信号处理函数
      sigset_t sa_mask;                //信号屏蔽集
      int sa_flags;
}

参数说明:

(1)第一个参数 signum:信号值。

(2)第二个参数act:信号的处理参数。

(3)第三个参数oldact:保存信号上次安装时的处理参数。

补充:

(1)信号阻塞:和signal函数类似,当正处于某个信号的处理函数中时,这个信号再次到达会被阻塞,待信号处理函数完成之后再处理。

(2)sa_mask:信号屏蔽集,所谓屏蔽并不是忽略,屏蔽的时间段是在信号处理函数执行期间,一旦处理函数执行完毕将会重新唤醒此信号。

(3)sa_flag:通常取值为0,则表示默认行为。

3、代码演示

        上面已经讲解了signal()和sigaction()两个函数的用法,接下来我们用一个代码来实际操作一下吧!

代码如下:

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>int             g_signal = 0;void signal_stop(int signum)
{if( SIGTERM == signum ){printf("SIGTERM signal detected\n");}else if( SIGALRM == signum ){printf("SIGALRM signal detected\n");g_signal = 1;}}void signal_user(int signum)
{if( SIGUSR1 == signum ){printf("SIGUSR1 signal detected\n");}else if( SIGUSR2 == signum ){printf("SIGUSR2 signal detected\n");}g_signal = 1;
}void signal_code(int signum)
{if( SIGBUS == signum ){printf("SIGBUS signal detected\n");}else if( SIGILL == signum ){printf("SIGILL signal detected\n");}else if( SIGSEGV == signum ){printf("SIGSEGV signal detected\n");}exit(-1);}int main(int argc,char *argv[])
{char                    *ptr = NULL;struct sigaction        sigact,sigign;/*Use signal() install signal*/signal(SIGTERM,signal_stop);signal(SIGALRM,signal_stop);signal(SIGBUS,signal_code);signal(SIGILL,signal_code);signal(SIGSEGV,signal_code);/*Use sigaction() install signal*/
/*Initialize the catch signal structure.*/sigemptyset( &sigact.sa_mask );sigact.sa_flags = 0;sigact.sa_handler = signal_user;/*Setup the ignore signal*/sigemptyset( &sigign.sa_mask );sigign.sa_flags = 0;sigign.sa_handler = SIG_IGN;sigaction(SIGINT,&sigign,0);            /*ignore SIGINT signal by CTRL+C*/sigaction(SIGUSR1,&sigact,0);   /*catch SIGUSR1*/sigaction(SIGUSR2,&sigact,0);   /*catch SIGUSR2*/printf("Program start running for 20 seconds...\n");alarm(20);while( !g_signal ){;}printf("Program start stop running...\n");printf("Invalid pointer operator will raise SIGSEGV signal\n");*ptr = 'h';    return 0;}

4、运行结果

        大家是否理解这个运行结果呢?如图可以看出,进程一共接受到了两种信号,分别是SIGALRMSIGSEGV。为什么呢?接收到SIGALRM是因为我们在程序中调用了alarm()函数;接收到SIGSEGV是因为代码最下面我们用了*ptr = 'h',而这条语句是一个指针错误。

        大家应该注意到代码中有一行是sigaction(SIGINT,&sigign,0);并且sigign.sa_handler = SIG_IGN;这就代表着键盘CTRL+c输入的SIGINT信号会被忽略掉,那我们实际操作一下,看看是不是如我们所想呢?

        正如我们所想,键盘CTRL+c输入的SIGINT信号因为已经被忽略掉,所以不能在终止进程了。

三、实战演练

题目:

        我们知道,父进程在创建子进程之后,究竟是父进程还是子进程先运行没有规定,这由操作系统的进程调度策略决定,而如果在某些情况下我们需要确保父子进程运行的先后顺序,则可以使用信号来实现进程间的同步。

        要求:写一个程序,实现父子进程之间使用信号进行同步。如果父进程先执行则进入到循环休眠等待状态,直到子进程给他发送信号之后才能跳出循环继续运行,确保子进程先执行它的任务。同样,子进程在执行完毕之后,就等待父进程给他发送信号之后才能退出,而父进程则通过调用wait()系统调用等待子进程退出后,父进程再退出。

参考代码如下:

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>int     g_child_stop = 0;
int     g_parent_run = 0;void sig_child(int signum)
{if(SIGUSR1 == signum){g_child_stop = 1;}
}void sig_parent(int signum)
{if(SIGUSR2 == signum){g_parent_run = 1;}
}int main(int argc,char *argv[])
{int             pid;int             status;signal(SIGUSR1,sig_child);signal(SIGUSR2,sig_parent);if((pid=fork()) < 0){printf("Create child process failure:%s\n",strerror(errno));return -1;}else if(pid == 0){/*Child process can do something first here.*/printf("Child process start runing!\n");/*when child process have done,then tell parent process to start running*/printf("Child process send parent a signal to tell parent process to run!\n");kill(getppid(),SIGUSR2);/*Waiting the stopping signal sent by parent process*/while( !g_child_stop ){sleep(1);}/*Child process have received the stopping signal*/printf("child process receive signal from parent and exit now!\n");return 0;}/*Only parent process run the codes beneath*//*Parents hangs up until receive signal from child*/while( !g_parent_run ){sleep(1);}/*Parent process have received the running signal from child process*//*Parent process can do something here*/printf("Parent start running now!\n");/*Parent process send a signal to tell child process to exit*/kill(pid,SIGUSR1);/*parent wait child process exit*/wait(&status);printf("Parent wait child process die and exit now!\n");return 0;}

运行结果如下:

 四、补充

最后,针对上文提到的一些知识,进行一些简单的补充:

1、alarm()函数

函数原型如下:

unsigned int alarm(unsigned int seconds)

(1)功能: 

        在进程中设置一个定时器,在seconds秒之后,将会发送SIGALRM信号给当前的进程,故而alarm函数也被称为闹钟函数。(如果在seconds秒内再次调用了alarm函数设置了新的闹钟,那么之前设置的秒数将会被新的闹钟时间所取代)

(2)参数:

        定时时间,单位为秒。

(3)返回值:

        如果该alarm函数是进程中第一次调用,则返回0,如果不是第一次调用,则返回上一次调用alarm函数剩余的时间。

2、wait()函数

函数原型如下:

#include  <sys/types.h>
#include  <wait.h>

 int wait(int * status)

(1)函数功能:

         父进程一旦调用wait函数就立即开始阻塞,然后wait会分析当前进程的某个子进程是否已经退出,如果让它找到了这样一个退出的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回,如果没有找到,就一直阻塞,直至找到一个结束的子进程或接收到了一个指定的信号为止。

【注意:  当父进程忘记调用wait()等待已终止的子进程,子进程就会进入一种没有父进程的状态,此时子进程就是zombie(僵尸)进程。】

(2)参数status:

        用来保存被收集进程退出时的状态,它是一个指向int类型的指针,如果我们对这个子进程如何死掉的不在意,只想这把这个被僵尸进程消灭掉,就把这个参数置为NULL。如果status的值不是NULL,wait把子进程的退出状态取出并存入其中,这是一个整数值(int)。

3、僵尸进程和孤儿进程

(1)孤儿进程:

        父进程先于子进程结束,当父进程退出时,系统会让pid为1的进程接管子进程。所以孤儿进程的pid都是1 。

(2)僵尸进程:

        子进程先于父进程结束,子进程成了僵尸(zombie)进程,并且子进程会一直保持这样的状态直至重启,此时内核只会保留进程的一些必要信息以备父进程所需,此时子进程始终占有资源,同时也减少了系统可以创建的最大进程数。

本篇文章用到了许多进程的知识,如果大家对进程不是很了解,可以看一下这篇文章《APUE学习之多进程编程》。我会坚持使用博客来整理自己所学知识,同时也希望能够帮助到大家,如果有哪些错误或者疑问,也欢迎大家在评论区一起讨论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/645152.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

k8s---HPA 命名空间资源限制

目录 HPA相关知识 HPA&#xff08;Horizontal Pod Autoscaling&#xff09;Pod 水平自动伸缩&#xff0c;Kubernetes 有一个 HPA 的资源&#xff0c;HPA 可以根据 CPU 利用率自动伸缩一个 Replication Controller、 Deployment 或者Replica Set 中的 Pod 数量。 &#xff08;1…

LTD261次升级 | 小程序支持抖音客服、支持抖音登录 • 短信发送需实名认证 • 表单提交成功收邮件提醒

1、 抖音小程序新增抖音IM客服功能&#xff1b; 2、 抖音小程序支持一键登录、支持快捷授权手机号 3、 表单新增发送邮件到提交者邮箱&#xff1b; 4、 表单支持配置不自动推送客户管理&#xff1b; 5、 短信发送需实名认证签署承诺书&#xff1b; 6、 其他已知问题修复与优化&…

安装 nvm

前言&#xff1a; nvm 即 node 版本管理工具 (node version manager)&#xff0c;好处是方便切换 node.js 版本。 通过将多个 node 版本安装在指定路径&#xff0c;然后通过 nvm 命令切换时&#xff0c;就会切换我们环境变量中 node 命令指定的实际执行的软件路径。 使用场景…

数据结构笔记1

来自《Python数据结构学习笔记》&#xff08;张清云 编著&#xff09; 第一章 数据结构基础 1.逻辑结构 集合&#xff1a;结构中的数据元素除了同属于一种类型外&#xff0c;别无其他关系线性结构&#xff1a;数据元素之间一对一的关系树形结构&#xff1a;数据元素之间一对…

抖音向微信引流主要有哪几种方法-数灵通

近年来&#xff0c;随着智能设备的普及和信息技术的进步&#xff0c;短视频制作门槛逐渐降低&#xff0c;用户自创视频数量迅猛增长&#xff0c;用户规模持续扩大&#xff0c;有力推动了移动短视频的繁荣发展&#xff0c;市场规模也在不断扩张。作为当下炙手可热的短视频APP&am…

pycharm安装过程

1、安装包官网下载 PyCharm: the Python IDE for Professional Developers by JetBrains 点击下载 下拉选择社区版本 选择下载 下载完成后&#xff0c;双击exe安装。 安装完成&#xff0c;生成的桌面快捷方式 同意并继续 进入开发界面

网络安全学习 --- 小实验

题目 要求 1.防火墙线下使用子接口分别对应两个内部区域。 2.所有分区设备可以ping通网关。 过程 1.接口&#xff0c;区域配置完成。 2.配置SWL2 vlan 2 vlan 3 # interface GigabitEthernet0/0/1port link-type trunkport trunk allow-pass vlan 2 to 3 # interface Giga…

JavaWeb之JavaScript-Vue --黑马笔记

什么是JavaScript&#xff1f; JavaScript&#xff08;简称&#xff1a;JS&#xff09; 是一门跨平台、面向对象的脚本语言。是用来控制网页行为的&#xff0c;它能使网页可交互。 JavaScript 和 Java 是完全不同的语言&#xff0c;不论是概念还是设计。但是基础语法类似。 …

java数据结构与算法刷题-----LeetCode667. 优美的排列 II

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 解题思路 题目要求我们返回一个数组长度为n的数组&#xff0c;必须含有1~n…

AI 欺诈事件频出,如何重塑身份认证的安全性?

据报告表示&#xff0c;生成式人工智能每年可为世界经济注入相当于 4.4 万亿美元的资金。预计到 2030 年&#xff0c;人工智能对全球财政的潜在贡献将达到 15.7 万亿美元。人们惊叹于 AI 强大工作效率&#xff0c;期待能帮忙节省不必要的劳动力&#xff0c;但事实上 AI 出现之后…

C++类的简单学习

C语言结构体中只能定义变量&#xff0c;在C中&#xff0c;结构体内不仅可以定义变量&#xff0c;也可以定义函数 之前在数据结构学习中&#xff0c;我们用C语言方式实现的栈&#xff0c;结构体中只能定义变量&#xff1b;现在以C方式实现&#xff0c;会发现struct中也可以定义函…

C++ Qt day1

提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数(要求使用C风格字符串完成) #include <iostream> #include <string.h> #include <array> using namespace std;int main() {string str;cout <<…

数据结构Java版(5)——链栈和链队列的实现

之前我们对链表进行了讲解&#xff0c;这次我们来用链表的方式来实现栈和队列两个接口&#xff0c;来加深我们对链表的理解。 一、链栈 栈的接口与顺序栈的接口相同&#xff0c;这里我们主要展示如何用我们自己写的链表来实现这个接口&#xff0c;代码展示如下&#xff1a; 接…

AI-数学-高中-8-函数奇偶性

原作者视频&#xff1a;函数】6函数奇偶性&#xff08;易-中档&#xff09;_哔哩哔哩_bilibili 示例&#xff1a; 已知奇偶性求解析式&#xff1a; 奇偶、单调函数综合示例&#xff1a;

黑马程序员——javase进阶——day01——匿名对象 , 继承 , 抽象类

目录&#xff1a; 面向对象回顾 面向对象的核心思想是什么?现有的类还是先有的对象?Java类的创建?类中的组成成分?创建对象所使用的关键字?创建对象的格式?调用对象的成员?定义构造方法的格式?定义构造方法的特点?构造方法的作用?面向对象三大特征是什么?封装的思想…

Java面试题:如何实现线程循环切换?

嗨大家好&#xff0c;我是小米&#xff01;今天我们要聊一个非常有趣的话题——社招面试题&#xff1a;Java中如何实现线程循环切换&#xff1f;大家都知道&#xff0c;在Java中处理多线程是一项非常常见而又重要的任务&#xff0c;而线程的循环切换更是其中的一大亮点。那么&a…

【图神经网络】GNNExplainer代码解读及其PyG实现

GNNExplainer代码解读及其PyG实现 使用GNNExplainerGNNExplainer源码速读前向传播损失函数 基于GNNExplainer图分类解释的PyG代码示例参考资料 接上一篇博客图神经网络的可解释性方法及GNNexplainer代码示例&#xff0c;我们这里简单分析GNNExplainer源码&#xff0c;并用PyTor…

基于sentinel-2 遥感数据的水体提取(水体指数法)

本文框架设置如下&#xff1a; 简单介绍senintel-2数据&#xff1b;如何利用sentinel-2数据获取水体边界/范围 1 Sentinel-2数据介绍及下载方式 有Sentinel-2A/2B两颗卫星&#xff0c;其参数基本一致&#xff0c;因此两颗卫星的数据联合使用很方便。 分辨率有&#xff1a;1…

Laya2.13.3接入FGUI

下载与复制文件与Laya1.x类似&#xff0c;可以看我上一篇&#xff1a; Laya1.8.4接入FariyGui&#xff0c;以及其中踩的坑-CSDN博客 不同的是&#xff1a; 两个库文件需要在index.js中引入 新建一个脚本将fgui中搭建好的UI包引入&#xff1a; export default class GameApp…

食品加工厂可视化视频AI智能监管方案,助力工厂数字化运营

一、背景与需求分析 随着科技的不断进步和人们对食品安全和质量的日益关注&#xff0c;食品智慧工厂的建设成为了食品行业的一个重要趋势。智能化的食品工厂可以利用先进的技术和自动化系统&#xff0c;提高生产效率、降低监管成本&#xff0c;并确保产品的质量和安全。 行业…