Linux信号

110 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情

后台运行程序

如果要运行程序,在命令提示行下输入程序名后回车,程序被执行,然后等待程序运行完成,在程序运行的过程中,也可以用Ctrl+c中止它。

加“&”符号

在后台运行的程序,用Ctrl+c无法中断,并且就算终端退出了,程序仍在后台运行。

如果终端退出了,后台运行的程序将由系统托管。

采用fork

另一种方法是采用fork,主程序执行fork,生成一个子进程,然后父进程退出,留下子进程继续运行,子进程将由系统托管。

 if (fork()>0) return 0;

如何让中止后台运行中程序

1)killall 程序名

2)先用“ps -ef|grep 程序名”找到程序的进程编号,然后用“kill 进程编号”。

signal信号

signal信号是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断,从它的命名可以看出,它的实质和使用很像中断。

基本概念

信号只是用来通知某进程发生了什么事件,无法给进程传递任何数据,进程对信号的处理方法有三种:

1)第一种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。

2)第二种是设置中断的处理函数,收到信号后,由该函数来处理。

3)第三种方法是,对该信号的处理采用系统的默认操作,大部分的信号的默认操作是终止进程。

信号的类型

信号名信号值默认处理动作发出信号的原
SIGINT2A键盘中断Ctrl+c
SIGKILL9AEF采用kill -9 进程编号 强制杀死程序。
SIGSEGV11C无效的内存引用
SIGTERM15A采用“kill 进程编号”或“killall 程序名”通知程序。
SIGCHLD20,17,18B子进程结束信号

A 缺省的动作是终止进程。

B 缺省的动作是忽略此信号,将该信号丢弃,不做处理。

C 缺省的动作是终止进程并进行内核映像转储(core dump),内核映像转储是指将进程数据在内存的映像和进程在内核结构中的部分内容以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员 提供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并且可以调试他们的程序。

D 缺省的动作是停止进程,进入停止状况以后还能重新进行下去。

E 信号不能被捕获。

F 信号不能被忽略。

signal库函数

signal库函数可以设置程序对信号的处理方式。

函数声明:

sighandler_t signal(int signum, sighandler_t handler);

参数signum表示信号的编号。

参数handler表示信号的处理方式,有三种情况:

1)SIG_IGN:忽略参数signum所指的信号。

2)一个自定义的处理信号的函数,信号的编号为这个自定义函数的参数。

3)SIG_DFL:恢复参数signum所指信号的处理方法为默认值。

程序员不关心signal的返回值。

栗子

这么做的目的是不希望程序被干扰。然后,再设置程序员关心的信号的处理函数。

程序员关心的信号有三个:SIGINT、SIGTERM和SIGKILL。

程序在运行的进程中,如果按Ctrl+c,将向程序发出SIGINT信号,信号编号是2。

采用“kill 进程编号”或“killall 程序名”向程序发出的是SIGTERM信号,编号是15。

采用“kill -9 进程编号”向程序发出的是SIGKILL信号,编号是9,此信号不能被忽略,也无法捕获,程序将突然死亡。

所以,程序员只要设置SIGINT和SIGTERM两个信号的处理函数就可以了,这两个信号可以使用同一个处理函数,函数的代码是释放资源。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>void exectask(){
    printf("执行了一次任务\n");
}
void fun(int sig)
{
    printf("收到了信号%d。\n",sig);
}
 
int main()
{
    for (int ii=0;ii<100;ii++) signal(ii,SIG_IGN); // 屏蔽全部的信号
    signal(SIGINT,fun);  signal(SIGTERM,fun); // 设置SIGINT和SIGTERM的处理函数
    while (1)  // 一个死循环
    {
        exectask();
        sleep(5);
    }
}

信号的作用

服务程序运行在后台,如果想让中止它,强行杀掉不是个好办法,因为程序被杀的时候,程序突然死亡,没有释放资源,会影响系统的稳定,用Ctrl+c中止与杀程序是相同的效果。

如果能向后台程序发送一个信号,后台程序收到这个信号后,调用一个函数,在函数中编写释放资源的代码,程序就可以有计划的退出,安全而体面。

可靠性号与不可靠信号

信号分为不可靠信号(1-32)与可靠信号(34-64)

不可靠信号主要有一下问题

1、每次信号处理完后,就恢复默认处理(已修复)

2、存在信号丢失的问题,进程收到的信号不作排队处理,相同的信号多次到来会合并为一个

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>void exectask(){
    printf("执行了一次任务\n");
}
void fun(int sig)
{
    printf("收到了信号%d。\n",sig);
    for (int i = 0; i < 5; i++)
    {
        printf("jj(%d)=%d\n",sig,i);
        sleep(1);
    }
    
}
 
int main()
{
    signal(15,fun);  signal(34,fun); // 设置SIGINT和SIGTERM的处理函数
    while (1)  // 一个死循环
    {
        int ii = 1; 
        printf("ii=%d\n",ii++);
        sleep(1);
    }
}

如果连续发送三个kill -15 3410838,只会触发2次fun函数,因为它是不可靠信号。

如果连续发送三个kill -34 3410838,都会触发3次fun函数,因为它是可靠信号。

信号处理函数被中断

当一个信号到达后,调用处理函数,如果这时候有其它的信号发生,会中断之前的处理函数,等新的信号处理函数执行完成后再继续执行之前的处理函数。

但同一个信号会排队阻塞。

eg:

发送一个kill -15 3410838后发送kill -34 3410838;-15会被中断,执行玩-34后再回来继续执行-15

发送一个kill -15 3410838后发送kill -153410838;则会等前一个-15执行完再执行后一个-15

信号的阻塞

如果不希望在接到信号时中断当前的处理函数,也不希望忽略该信号,而是延时一段时间再处理这个信号,这种情况可以通过阻塞信号实现。

信号的阻塞和忽略信号是不同的,被阻塞的信号不会影响进程的行为,信号只是暂时被阻止传递。

进程忽略一个信号时,信号会被传递出去但进程会将信号丢弃。

执行信号的处理动作称为信号传递(Delivery),信号从产生到传递之间的状态,称为信号未决(Pending)

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void fun1(int sig)
{
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set,15);
    sigprocmask(SIG_BLOCK,&set,NULL);
    printf("收到了信号%d。\n",sig);
    for (int i = 0; i < 5; i++)
    {
        printf("jj(%d)=%d\n",sig,i);
        sleep(1);
    }   
    sigprocmask(SIG_UNBLOCK,&set,NULL);
}
void fun2(int sig)
{
    printf("收到了信号%d。\n",sig);
    for (int i = 0; i < 5; i++)
    {
        printf("jj(%d)=%d\n",sig,i);
        sleep(1);
    }   
​
}
int main()
{
    signal(2,fun1);  signal(15,fun2); // 设置SIGINT和SIGTERM的处理函数
    while (1)  // 一个死循环
    {
        int ii = 1; 
        printf("ii=%d\n",ii++);
        sleep(1);
    }
}

发送一个kill -2 3410838后立马发送kill -15 3410838;也会先执行-2,再执行-15

因为执行-2的时候把-15屏蔽了