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学习之多进程编程》。我会坚持使用博客来整理自己所学知识,同时也希望能够帮助到大家,如果有哪些错误或者疑问,也欢迎大家在评论区一起讨论!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙开发案例002

1、目标需求 界面有增大字体按钮&#xff0c;每次点击增大字体按钮&#xff0c;“Hello ArkTS”都会变大 2、源代码 Entry Component struct Page {textValue: string Hello ArkTSState textSize: number 50myClick():void{this.textSize 4}build() {Row() {Column() {//…

基于若依的ruoyi-nbcio流程管理系统一种简单的动态表单模拟测试实现(五)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; https://gitee.com/nbacheng/n…

日志级别与配置

日志的级别 FATAL致命信息表明严重的问题&#xff0c;可能导致应用程序崩溃。ERROR错误信息指示应用程序遇到了一个错误&#xff0c;可能导致功能受损。通常用于记录异常信息。WARN警告信息表明可能存在问题&#xff0c;但不会导致应用程序失败。仍然可以正常运行&#xff0c;…

【数据结构四】栈与Stack详解

目录 栈与Stack 1.实现一个自己的栈 2.Stack的基本使用 3.栈的一些oj题训练 4.栈&#xff0c;虚拟机栈&#xff0c;栈帧的区别 栈与Stack 栈 &#xff1a;一种特殊的线性表&#xff0c;其 只允许在固定的一端进行插入和删除元素操作 。进行数据插入和删除操作的一端称为栈顶…

uvicorn日志清空问题以及uvicorn日志配置

uvicorn日志清空问题 1、配置&#xff1a; uvicorn starlette 2、现象描述&#xff1a; 当我使用uvicorn starlette进行Python web开发的时候&#xff0c;本来想把所有的日志都打印到一个文件里面&#xff0c;于是我写了一个启动脚本&#xff0c;所有的日志都输出到log.t…

jQuery语法知识(表单)

表单 1、:button Selector 描述:选择所有按钮元素和类型为按钮的元素 语法 :jQuery(":button") <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"wid…

C++:C/C++内存管理

C&#xff1a;C/C内存管理 C语言C语言内存分配回顾malloc & calloc & realloc & free Cnew & deletenew[ ] & delete[ ]定位newnew & delete原理 malloc / free 与 new / delete对比 C语言 C语言内存分配回顾 我们先回顾一下C语言的内存分配&#xf…

分享一个剧本(改编自我)

不知道是不是错过了一个喜欢我的女孩&#xff0c;一个很不错的女孩&#xff0c;当初没勇气表白。去年表白过但女孩表示仅想是永远的朋友&#xff0c;今天翻他的朋友圈发现2021年我生日时&#xff0c;她分享了这首歌曲&#xff0c;还评论Best wishes!!!&#xff0c;高中有一次我…

抖音怎么引导到公众号丨数灵通

抖音是一款非常流行的社交媒体应用程序&#xff0c;用户可以在其中分享短视频和互动内容。许多用户希望通过抖音来引流到他们的微信公众号&#xff0c;以扩大影响力并吸引更多粉丝。以下是一些关于如何在抖音上跳转到微信公众号的科普信息&#xff1a; 1.信息流广告&#xff1a…

PPP协议原理介绍+报文分析+配置指导-RFC1661

个人认为&#xff0c;理解报文就理解了协议。通过报文中的字段可以理解协议在交互过程中相关传递的信息&#xff0c;更加便于理解协议。 因此本文将在PPP协议报文的基础上进行介绍。 关于PPP协议基本原理&#xff0c;可参考RFC1661-The Point-to-Point Protocol (PPP)。 关于P…

Linux的奇妙冒险———vim的用法和本地配置

vim的用法和本地配置 一.vim的组成和功能。1.什么是vim2.vim的多种模式 二.文本编辑&#xff08;普通模式&#xff09;的快捷使用1.快速复制&#xff0c;粘贴&#xff0c;剪切。2.撤销&#xff0c;返回上一步操作3.光标的控制4.文本快捷变换5.批量化操作和注释 三.底行模式四.v…

响应拦截器的 return Promise.reject(res.data.message)

今天在看老师讲解代码的时候,解决了我心中的一些疑惑。 在做excel文件导出的时候,没有告诉浏览器文件的格式是Blod产生了报错。 看下图: 可以看到下面的内容:如果业务成功 返回 res.data 如果业务失败,给出错误信息的提示&#xff0c;将这个错误抛出去。 因此我们在发送一个…

2023春秋杯冬季赛 --- Crypto wp

文章目录 前言Cryptonot_wiener 前言 比赛没打&#xff0c;赛后随便做一下题目 Crypto not_wiener task.py: from Crypto.Util.number import * from gmpy2 import * import random, os from hashlib import sha1 from random import randrange flagb x bytes_to_long(f…

面试题合集

目录 二叉树和动态规划的框架图内容补充数组为什么下标从0开始&#xff1f;windows内存上存储数据采用是什么模式&#xff1f;atoi 和itoa函数的实现字节对齐方式&#xff0c;为什么进行内存对齐&#xff1f;结构体的大小二分查找有重复数字中最左边的数 最右边的数工厂模式 单…

幻兽帕鲁PalWorld服务器搭建详细教程

幻兽帕鲁PalWorld是一款由Pocketpair开发的游戏&#xff0c;融合了多种玩法&#xff0c;其独特的题材和画风吸引了很多玩家。为了更好地进行游戏体验&#xff0c;很多玩家选择自行搭建服务器。本文将详细介绍如何搭建幻兽帕鲁PalWorld服务器。 第一步&#xff1a;购买服务器 根…

面试官灵魂一问,曾写过什么剧本?我:“简单的有,使用Ansible对lnmp架构部署!”

引言&#xff1a;今天带大家使用ansible进行对lnmp的架构部署&#xff0c;并做wordpress网站项目 准备ansible端 db1(安装nginx与php和项目) db2(安装数据库) 并做好管理关联配置 一、创建角色 路径可以自定义&#xff0c;例/root/juben.dir #ansible-galaxy init nginx#an…

.NET 跨平台图形库 SkiaSharp 基础应用

写在前面 SkiaSharp 是适用于 .NET 和 C# 的 2D 图形系统&#xff0c;由开源 Skia 图形引擎提供支持&#xff0c;在 Google 产品中广泛使用。 可以在应用程序中使用 SkiaSharp Xamarin.Forms 绘制 2D 矢量图形、位图和文本。支持跨平台&#xff0c;Windows、Linux、Anroid、IO…

docker-compose搭建redis哨兵模式

文件存放如下图&#xff1a; docker-compose.yml文件内容如下&#xff1a; version: 3.3 services:master:image: redis:3.2.12restart: alwayscontainer_name: redis-mastercommand: redis-server /usr/local/redis/conf/redis.confports:- 6380:6380volumes:- /root/redis/…

java开发——《并发编程》

目录 一.jmm 二.并发了什么 1.只有一个核&#xff08;单核&#xff09;并发还有没有意义 2.单核&#xff0c;还有什么可见性问题 3.并发和并行 三.volitaile 1.变量的可见性问题 2.原因是什么 3.本次修改的变量直接刷到主内存 4.声明其他内存对于这个地址的缓存无效 …

Java Web(三)--CSS

介绍 为什么需要&#xff1a; 在没有 CSS 之前&#xff0c;想要修改 HTML 元素的样式需要为每个 HTML 元素单独定义样式属性&#xff0c;费心费力&#xff1b;CSS 可以让 html 元素(内容) 样式(CSS)分离&#xff0c;提高web 开发的工作效率(针对前端开发)&#xff0c;从而…