首页 > 系统相关 >进程--信号

进程--信号

时间:2024-09-30 19:47:44浏览次数:7  
标签:-- pid int exit 信号 进程 include

目录

什么是信号

信号的种类

信号处理流程

信号的发送

kill

raise

等待信号

信号处理

用户自定义处理

信号定时器

子进程退出信号


什么是信号

信号是在软件层面上是一种通知机制,对中断机制的一种模拟,是一种异步通信方式。一般具有如下特点:
进程在运行过程中,随时可能被各种信号打断
进程可以忽略或者去调用相应的函数去处理信号
进程无法预测信号到达的精准时间

在 Linux 中信号一般的来源如下:
程序执行错误,如内存访问越界,数学运算除0
由其他进程发送
通过控制终端发送,如ctrl + c
子进程结束时向父进程发送的SIGCHLD信号
程序中设定的定时器产生的SIGALRM信号

信号的种类

在Linux系统中 kill -l 可查看

SIGINT 该信号在用户键入INTR字符(通常是Ctrl-C)时发出,终端驱动程序发送此信号并送到前台进
程中的每一个进程。
SIGQUIT 该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-)来控制。
SIGILL 该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、
堆栈溢出时)发出。
SIGFPE 该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0
等其它所有的算术的错误。
SIGKILL 该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。
SIGALRM 该信号当一个定时器到达的时候发出。
SIGSTOP 该信号用于暂停一个进程,且不能被阻塞、处理或忽略。
SIGTSTP 该信号用于交互停止进程,用户可键入SUSP字符时(通常是Ctrl-Z)发出这个信号。
SIGCHLD 子进程改变状态时,父进程会收到这个信号
SIGABRT 进程异常中止

信号处理流程

信号处理流程包含以下两个方面:
信号的发送 :可以由进程直接发送
信号投递与处理 : 由内核进行投递给具体的进程并处理

在 Linux 中对信号的处理方式如下
忽略信号,即对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
捕捉信号,定义信号处理函数,当信号发生时,执行相应的处理函数。
执行缺省操作,Linux对每种信号都规定了默认操作

信号的发送

当由进程来发送信号时,则可以调用 kill() 函数与 raise () 函数

kill

函数头文件
#include <sys/types.h>
#include <signal.h>

函数原型
int kill(pid_t pid, int sig);
函数功能
向指定的进程发送一个信号
函数参数
pid:进程的id
sig:信号的id

raise

函数头文件
#include <sys/types.h>
#include <signal.h>

函数原型
int raise(int sig);
函数功能
send a signal to the caller
函数参数
sig:信号编号

代码示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int main()
{
        pid_t pid = fork();
        if(pid == -1)
       {
                perror("fork");
                exit(EXIT_FAILURE);
       }
        else if(pid==0)
       {
                // 执行子进程
                fprintf(stdout,"child <%d> is running.\n",getpid());
                // SIGSTOP 暂停一个进程,不能阻塞、处理或忽略,只能采用默认处理
                raise(SIGSTOP);
                fprintf(stdout,"child <%d> is exit.\n",getpid());
                // 成功退出子进程
                exit(EXIT_SUCCESS);
       }
        else
       {
                int ret;
                sleep(1);
                ret = kill(pid,SIGKILL);//给指定的进程发送kill信号,只能采用默认操
作,结束进程
                if(ret == 0)
               {
                        fprintf(stdout,"parent<%d> kill 
child<%d>.\n",getpid(),pid);
               }
                waitpid(pid,NULL,0);
                fprintf(stdout,"father<%d> exit.\n",getpid());
                exit(EXIT_SUCCESS);
       }
        return 0;
}

等待信号

在进程没有结束时,进程在任何时间点都可以接受到信号
需要阻塞等待信号时,则可以调用 pause() 函数

函数头文件
#include <unistd.h>
函数原型
int pause(void);

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int main()
{
        pid_t pid = fork();
        if(pid == -1)
       {
                perror("fork");
                exit(EXIT_FAILURE);
       }
        else if(pid==0)
       {
                // 执行子进程
                fprintf(stdout,"child <%d> is running.\n",getpid());
                //子进程向父进程发送SIGUSR1信号,SIGUSR1信号的默认处理是终止进程
                sleep(2);
                kill(getppid(),SIGUSR1);
                fprintf(stdout,"child <%d> is exit.\n",getpid());
                // 成功退出子进程
                exit(EXIT_SUCCESS);
       }
        else
       {
                int ret;
                sleep(1);
                pause();//等待信号,让进程阻塞直到收到信号
                fprintf(stdout,"father<%d> exit.\n",getpid());
                exit(EXIT_SUCCESS);
       }
        return 0;
}

提醒:pause函数一定要在收到信号之前调用,让进程进入到睡眠状态

信号处理

信号是由操作系统内核发送给指定进程,进程收到信号后则需要进行处理
处理信号有三种方式:
忽略 : 不进行处理
默认 : 按照信号的默认方式处理
用户自定义 : 通过用户实现自定义处理函数来处理,由内核来进行调用
每种信号都有相应的默认处理方式:

进程退出
SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
进程忽略
SIGCHLD,SIGPWR,SIGURG,SIGWINCH
进程暂停
SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU

用户自定义处理

当有信号时,我们可以自由设定进程该怎样处理

1. 实现自定义处理函数
用户实现自定义处理函数,需要按照下面的格式

typedef void (*sighandler_t)(int);
typedef void (*)(int) sighandler_t

类似与这样的一个函数

2. 设置信号处理处理方式
通过 signal 函数设置信号处理方式

函数头文件 
#include <signal.h>
函数原型 
sighandler_t signal(int signum, sighandler_t handler);
函数功能 
设置信号的处理方式,如果是自定义处理方式,提供函数地址,注册到内核中

函数参数 
signum:信号编号
handler:信号处理方式
 SIG_IGN---->忽略信号
 SIG_DFL---->按照默认方式处理
  自定义处理函数的地址

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
// 自定义处理函数
void signal_handler(int signum)
{
        printf("%s\n",strsignal(signum));
}
int main()
{
        // 注册处理函数的地址到内核
        if(signal(SIGUSR1,signal_handler)==SIG_ERR)
       {
                perror("signal error.");
                exit(EXIT_FAILURE);
       }
        pid_t pid = fork();
        if(pid==-1)
       {
                perror("fork");
                 exit(EXIT_FAILURE);
       }
        else if(pid == 0)
       {
                printf("child process <%d> is running...\n",getpid());
                pause();// 等待信号唤醒
                printf("child process exit\n");
                exit(EXIT_SUCCESS);
       }
        else if(pid > 0)
       {
                sleep(1);
                int ret = kill(pid,SIGUSR1);
                if(ret == 0)
               {
                        printf("kill signum<%d> to process<%d> is 
successful.\n",SIGUSR1,pid);
               }
                waitpid(pid,NULL,0);
       }
        return 0;
}

信号定时器

有时信号间处理太快,不是我们想要的结果

这时Linux系统中提供了 alarm 函数,用于设置定时器

函数原型 
unsigned int alarm(unsigned int seconds);

seconds:定时的时间秒数

函数返回值 
返回上一次进程设置定时器剩余的秒数,如果进程上一次没有设置定时器,则返回0

定时器的定时任务由内核完成的,alarm 函数负责设置定时时间,并告诉内核启动定时器
当定时时间超时后,内核会向进程发出 SIGALRM 信号

设置定时器的定时时间为 3s ,并处理 SIGALRM 信号

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void sigalrm_handler(int signum)
{
        printf("sigalrm:%s\n",strsignal(signum));       
}
int main()
{
        unsigned int ret;
        // 注册信号处理函数
        if(signal(SIGALRM,sigalrm_handler) == SIG_ERR)
       {
                perror("signal:");
                exit(EXIT_FAILURE);
       }
        ret = alarm(3);
        pause();
        printf("main process end....\n");
        return 0;
}

子进程退出信号

在使用 wait() 函数时,由于阻塞或者非阻塞都非常消耗资源。并且在阻塞情况下,父进程不能执行其他逻辑。
如何解决?
子进程退出是异步事件,可以利用在子进程退出时,会自动给父进程发送 SIGCHLD 信号

示例代码:使用信号处理僵死态进程

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void sigchld_handler(int signum)
{
        printf("SIGCHLD handler:<%s>\n",strsignal(signum));
        int status=0;
        wait(&status);
}
int main()
{
        pid_t pid;
        if(signal(SIGCHLD,sigchld_handler)==SIG_ERR)
       {
                perror("signal failed.");
                exit(EXIT_FAILURE);
       }
        pid = fork();
        if(pid == -1)
       {
                perror("fork");
                exit(EXIT_FAILURE);
       }
        else if(pid == 0)
       {
                printf("child process<%d> is running.....\n",getpid());
                sleep(2);
                exit(EXIT_SUCCESS);
       }
        else
       {
                while(1)
               {
               }
       }
        return 0;
}

标签:--,pid,int,exit,信号,进程,include
From: https://blog.csdn.net/Y25845/article/details/142659189

相关文章

  • Leetcode:栈和队列的互相实现
    目录一、用两个队列实现栈题目:分析:代码实现: 二、用两个栈实现队列题目: 分析:代码:总结:核心就在于先进先出和后进先出之间的一个灵活变换,两个栈能够先进先出,而两个队列能够后进先出 一、用两个队列实现栈题目:.-力扣(LeetCode).-备战技术面试?力扣提供海量技术......
  • 如何学习JAIN-SLEE
    如何学习JAIN-SLEE_安装mobicentsjain-slee容器-CSDN博客 要系统地学习 JAIN-SLEE(JavaAPIforIntegratedNetworks–ServiceLogicExecutionEnvironment),你需要从基础概念到高级应用逐步深入学习。以下是详细的入门学习路径和顺序,涵盖必要的知识点、学习顺序和步骤......
  • 大单元综合测试(一):第一章,第二章题解
    \(6.\)已知\(3a>b>0\),则\(\large\frac{a}{3a-b}-\frac{b}{a+b}\)的最小值为多少?基本方法\(\qquad\)对于高中基本不等式,这种分母较为复杂的求最值问题,我们一般都会采用将分母换元换元的方法,理由很自然,因为分式是分子除分母,所以分母形式的简单可以方便我们对问题的处理。那么......
  • P6105 [Ynoi2010] y-fast trie
    这可能也是一个关于匹配的经典trick。题意给定常数\(C\),你需要维护一个集合\(S\),支持以下操作:1x,加入数\(x\),保证\(x\)之前不存在。2x,删除数\(x\),保证\(x\)之前存在。每次操作后你需要回答$$\max_{i,j\inS,i\not=j}(i+j)\bmodC$$\(Q\le5\times10^5\),强制在......
  • 全家桶 win激活教程
            ......
  • USB和CAN都是用差分信号来传输数据,为什么CAN的传输距离能比USB远那么多?
    USB和CAN的区别今天在看USB项目设计实例的时候,突然想到一个问题,从而引发了一些思考。经过思考加上查阅资料,写出了这一篇文章作为记录。问题​ USB和CAN都是用两条线作为差分线以差分信号进行数据传输。总所周知,差分信号有着很强的抗干扰能力。那为什么USB的一般传输距离是5米......
  • js进阶——FormData常用知识点介绍
    FormData是JavaScript中用于构建表单数据对象的API,它主要用于处理enctype="multipart/form-data"类型的表单提交,即上传文件和数据。通过FormData,开发者可以在客户端构建和发送表单数据,尤其是在没有使用传统的HTML表单提交时,允许开发者进行更多的自定义和控制。For......
  • 理解傅里叶
    笔者是电子信息工程专业的学生,在学习专业课时逐渐发现线性代数、复变函数与积分变换、信号与系统等数学基础类课程几乎渗透在专业的方方面面。只有充分理解底层数学的本质,才能更好地对信号处理、控制系统等进行理解、分析,从而应用于实践,进行学术研究。故笔者决定开设此专栏,......
  • 长江存储致态TiPlus7100 4TB满盘读写测试:性能几乎没有下降
    一、前言:看看满盘状态下致态TiPlus71004TB性能会如何!现在还有很多同学对于长江存储品牌的存储产品不太信任,在选择SSD时会优先考虑三星、西数这样的品牌。有鉴于此,我们此次会将手上的长江存储致态TiPlus71004TBSSD进行更严苛测试,将SSD填入80%的数据,也就是在近乎满盘的状态下,看......
  • 自定义类型:结构体,枚举,联合
    文章目录结构体**结构体声明****结构体的基础知识**结构体变量的定义和初始化结构体内存对齐修改默认对齐数百度笔试题:结构体传参位段什么是位段位段的内存分配3.2剩余空间利用的问题位段的跨平台问题总结:位段的应用枚举枚举类型的定义枚举的优点枚举的使用联合(共......