Linux —— 信号(2)

本文详细介绍了Linux中的信号系统,包括信号的序号、kill和raise系统调用、不可被捕获的信号(如SIGKILL和SIGSTOP)、自定义信号的行为、以及alarm函数的使用。着重讨论了SIGINT、SIGTERM、SIGKILL、SIGFPESIGSEGV和SIGALRM信号的特点和应用。
摘要由CSDN通过智能技术生成

我们今天来接着了解信号:

信号的序号

我们来看看信号的序号:
在这里插入图片描述信号的序号是从1开始,到31我们称这区间的信号为普通信号,然后又从3464实时信号

这里我们要明确一点,没有0号信号

在Linux中,不存在编号为0的信号。实际上,信号编号是从1开始的,并没有0号信号。这是因为Linux内核和相关的POSIX标准在定义信号时,从1开始为各种信号分配了编号,而0号并没有被分配给任何信号。
此外,kill函数对信号编号0有特殊的应用。当你向一个进程发送信号0时,实际上并不会发送任何信号给该进程,而是用来测试进程是否存在以及当前用户是否有权限向该进程发送信号。如果进程存在并且用户有权限,那么kill函数会成功返回;否则,会返回一个错误码。
因此,Linux中没有0号信号是因为在信号编号的分配中,0并没有被分配给任何信号,而kill函数对信号编号0的特殊应用则是一种特殊的机制,用于测试进程的存在性和权限。

kill系统调用

我们除了可以命令行来向进程发送信号之外,我们还可以用kill来向进程发送信号:
在这里插入图片描述
在Linux中,kill系统调用(通常通过C语言库函数kill()来访问)用于向一个进程发送一个信号。这个系统调用允许一个进程向另一个进程发送一个信号,该信号可以是任何在系统中定义的信号。

以下是kill()函数的基本用法:

#include <sys/types.h>  
#include <signal.h>  
  
int kill(pid_t pid, int sig);

参数说明:

pid:要接收信号的进程的进程ID(PID)。如果pid小于-1,则信号将被发送到进程组ID等于-pid的所有进程。如果pid等于0,则信号将被发送到与调用进程属于同一进程组的所有进程。如果pid等于-1,则信号将被发送到除调用进程自身外的所有进程。
sig:要发送的信号。这可以是任何在系统中定义的信号,如SIGTERM(终止进程)、SIGINT(中断进程)、SIGKILL(强制终止进程,不能被捕获或忽略)等。

返回值:

如果成功,返回0。
如果失败,返回-1,并设置errno以指示错误。

以下是一个简单的示例,展示了如何使用kill()函数来终止一个进程:

#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>
#include<iostream>

static void Usage(const std::string& proc)
{
    std::cout << "Usage: ";
    std::cout <<  proc << "<pid>\n" <<std::endl;
}

int main(int argc, char *argv[]) 
{  
    if (argc != 3) 
    {  
        Usage(argv[0]);

        exit(EXIT_FAILURE);  
    }  
  
    int signalnumber = std::atoi(argv[1] + 1);

    pid_t pid = atoi(argv[2]);  

    if (kill(pid, signalnumber) == -1) 
    {  
        perror("kill");  
        exit(EXIT_FAILURE);  
    }  
  
    printf("Sent SIGTERM to process %d\n", pid);  
    exit(EXIT_SUCCESS);  
}

我们重新写一段代码:

#include<iostream>
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>

int main()
{
    while(true)
    {
        std::cout << "myprocess is running... pid:"<< getpid() << std::endl;
        sleep(1);
    }

    return 0;
}

然后我们跑起来,用我们写的程序来传递信号:
在这里插入图片描述

在这个示例中,程序接受两个个命令行参数(要传递的信号,要终止的进程的PID),并使用kill()函数向该进程发送信号。如果发送信号失败,程序将打印一条错误消息并退出。如果成功,程序将打印一条消息表明信号已发送。

不可被自定义的信号

还记得我们可以自定义信号的行为吗:

void signal_hander(int signum)
{
    printf("Caught SIGINT, %d\n",signum);
}

int main()
{
    while(true)
    {
        cout << "process running ... pid: "<< getpid() << endl;
        pid_t id = getpid();
        signal(2,signal_hander);
        kill(id,2);
        sleep(1);
    }
}

在这里插入图片描述但是如果换成9号信号:

#include<iostream>
#include<unistd.h>
#include <signal.h>
#include<cstdio>
using namespace std;

void signal_hander(int signum)
{
    printf("Caught SIGINT, %d\n",signum);
}

int main()
{
    while(true)
    {
        cout << "process running ... pid: "<< getpid() << endl;
        pid_t id = getpid();
        signal(9,signal_hander); //换成了九号信号
        kill(id,9);
        sleep(1);
    }
}

在这里插入图片描述
然后我们发现我们的9号信号并没有按照我们自定义的行为来执行,这是因为,有一些信号是不能被自定义的

在Linux系统中,并非所有信号都可以被程序自动捕捉并作出响应。有几个信号默认是不可被捕获的,也就是说,它们不能通过信号处理函数来处理,一旦这些信号被发送给进程,将导致默认的行为立即发生,无法通过编程来改变其后果。这些信号主要是:

  1. SIGKILL (信号9):此信号用于强制终止一个进程。操作系统保证无论进程处于何种状态,一旦收到这个信号,都会立即终止,且进程无法忽略或捕获这个信号。
  2. SIGSTOP (信号19):这个信号用于暂停(停止)一个进程的执行。同SIGKILL一样,进程无法忽略或捕获这个信号,一旦接收到就会立即停止运行。

这两个信号设计成不可被捕获,是为了确保系统有办法在任何时候控制和管理进程,即使进程本身出现问题或试图逃避正常的控制机制。这对于维护系统的稳定性和安全性至关重要。其他信号,如SIGINT (Ctrl+C触发, 信号2),SIGTERM (默认的终止信号, 信号15)等,则是可以被捕获并由程序自定义处理行为的。

raise

在Linux中,raise函数允许进程向自身发送一个信号。这是一个方便的机制,特别是在需要基于某些条件主动触发信号处理逻辑时。以下是raise函数的基本用法和说明:

#include <signal.h>

int raise(int sig);
  • sig:要发送给进程自身的信号编号。这应该是系统支持的信号之一,比如SIGINT(通常由Ctrl+C生成)、SIGUSR1SIGUSR2(用户自定义信号)等。需要注意的是,某些信号如SIGKILLSIGSTOP不能被raise调用发送,因为它们不能被捕获或忽略。

  • 成功:函数返回0。

  • 失败:返回-1,并且会设置errno来表明错误原因,例如,如果尝试发送一个无效的信号,或者进程没有权限发送该信号给自己。

下面是一个简单的示例,展示如何使用raise函数发送一个SIGINT信号给当前进程,模拟按下Ctrl+C的效果,进而触发预设的信号处理函数。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void signal_handler(int signum) 
{
    printf("catch a sign %d\n", signum);
    // 进程可以根据需要在这里执行清理工作或其它操作
}

int main() 
{
    // 设置信号处理器
    signal(SIGINT, signal_handler);

    printf("process begin, is coming to send a sign to themselves...\n");
    sleep(2); // 等待两秒,让信息显示出来

    // 发送SIGINT信号给自身
    if (raise(SIGINT) != 0) 
    {
        perror("raise fail");
        return 1;
    }

    printf("continue ...\n"); // 这行可能不会执行,取决于信号处理器的行为
    return 0;
}

在这个例子中,程序首先注册了一个信号处理器signal_handler来处理SIGINT信号。然后,它通过调用raise(SIGINT)主动向自己发送了这个信号,从而触发了预先设定好的信号处理逻辑。请注意,如果信号处理器终止了进程,后续的代码可能不会被执行。

abort

我们也可以用abort来终止进程
在C语言编程中,abort函数用于异常终止当前进程。这是一个标准库函数,通常用于在程序中遇到不可恢复的错误时强制退出。以下是关于abort函数的基本用法和特性:

#include <stdlib.h>

void abort(void);
  • 异常终止:调用abort函数会使当前进程立即无条件终止,不执行任何清理工作(如atexit注册的函数或对象的析构函数)。进程的终止状态将表明是异常终止。
  • 信号发送abort函数内部实际上通过发送SIGABRT信号到调用进程来实现这一行为。这意味着如果进程之前设置了SIGABRT的信号处理函数,该函数会被调用。但是,除非信号处理函数调用了exit_exit_Exitlongjmpsiglongjmp等导致进程直接终止的函数,否则一旦信号处理函数返回,abort将继续终止进程。
  • 缓冲区刷新:在发送SIGABRT之前,abort通常会先刷新所有已打开的输出缓冲区,确保缓冲区中的数据被写入到相应的文件或设备中。
  • 返回值:由于abort调用后不会正常返回到调用点,因此实际上它没有返回值。不过,从技术上讲,它可以被看作是返回一个非零值给操作系统,表示进程异常结束。

下面是一个简单的示例,展示了如何在遇到某种错误条件时使用abort终止程序:

#include<iostream>
#include<unistd.h>
#include <signal.h>
#include<cstdio>
using namespace std;

void signal_hander(int signum)
{
    printf("Caught SIGINT, %d\n",signum);
}

int main()
{
    while(true)
    {
        cout << "process running ... pid: "<< getpid() << endl;
        pid_t id = getpid();
        signal(2,signal_hander); //换成了九号信号
        kill(id,2);
        sleep(1);

        abort();
    }
}

在这里插入图片描述

异常传递信号

SIGFPE

我们之前都知道,再写代码的时候不能除以0,否则会报错:

int main()
{
    int a = 10;
    int b = a / 0; 
    printf("%d\n",b);

    return 0;
}

在这里插入图片描述
其实,这也是一个信号:
在这里插入图片描述我们也可以查man的7号手册,查看signal:
在这里插入图片描述
说明 / 0的时候,OS发送了8号信号,我们可以来试验一下:

#include<iostream>
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>

void signal_hander(int signum)
{
    std::cout << "Catch a sign: " << signum << std::endl;
    sleep(1);
}

int main()
{
    signal(8,signal_hander);
    int a = 10;
    int b = a / 0; 
    
    printf("%d\n",b);

    return 0;
}


这个时候程序会进入死循环,因为我们改变了8号信号的默认行为,改为了打印信息,但是在程序中/ 0的问题并没有得到解决,所以会一直打印。

SIGSEGV

空指针引用也是同样的问题,是11号信号:

#include<iostream>
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>

void signal_hander(int signum)
{
    std::cout << "Catch a sign: " << signum << std::endl;
    sleep(1);
}

int main()
{
    signal(11,signal_hander);


    int *p = nullptr;
    *p = 100;
    

    return 0;
}

在这里插入图片描述

alarm传递信号

我们可以设定闹钟,设定秒数,在设定几秒钟之后发送14号信号:
在这里插入图片描述

在C语言编程中,尤其是在Unix和类Unix系统如Linux中,alarm函数用于在指定的秒数后向进程发送SIGALRM信号。这个函数常用于实现定时操作或超时处理。以下是alarm函数的基本用法和相关说明:

#include <unistd.h>

unsigned int alarm(unsigned int seconds);
  • seconds:指定等待的秒数,在这个时间结束后,系统会向调用进程发送SIGALRM信号。如果设置为0,则会取消任何先前设置的闹钟。

  • 定时器设置:调用alarm函数后,将在指定的秒数后向调用进程发送SIGALRM信号。如果进程之前已经设置了闹钟,新的调用会覆盖旧的设置。

  • 信号处理:进程需要提前通过signalsigaction函数注册对SIGALRM信号的处理函数,否则默认情况下,进程会因未处理此信号而终止。

  • 返回值:如果之前已设置闹钟,alarm函数会返回剩余的秒数;如果没有设置过闹钟,则返回0。

以下是一个简单的使用alarm函数的示例,展示了如何设置一个1秒的定时器,并注册一个信号处理函数来响应SIGALRM信号:

#include<iostream>
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>

size_t cnt = 0;

void signal_hander(int signum)
{
    std::cout << "Catch a sign: " << signum << std::endl;
    exit(0);
}

int main()
{
    signal(14,signal_hander);


    alarm(1);

    while(true)
    {
       std::cout << "alarm: "<< cnt++ << std::endl;
    }
    
    return 0;
}

在这里插入图片描述
但是如果换一下位置,cnt的数字会更大:

#include<iostream>
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>

size_t cnt = 0;

void signal_hander(int signum)
{
    std::cout << "alarm: "<< cnt << std::endl;
    std::cout << "Catch a sign: " << signum << std::endl;
    exit(0);
}

int main()
{
    signal(14,signal_hander);


    alarm(1);

    while(true)
    {
       //std::cout << "alarm: "<< cnt++ << std::endl;
       cnt++;
       //std::cout << cnt << std::endl;
    }
    

    return 0;
}

在这里插入图片描述第一种写法里同时要处理输出和计算,第二种方法先计算完再打印。所以cnt的数值要大的多。

如果我们重新设了第二个闹钟,但是第一个闹钟的时间还没到,alarm会返回上一个闹钟的剩余时间。

#include<iostream>
#include <stdio.h>  
#include <stdlib.h>  
#include <signal.h>  
#include <unistd.h>  
#include <cstring>

size_t cnt = 0;

void signal_hander(int signum)
{
    int n = alarm(0);
    std::cout << "left time: "<< n << std::endl;
    std::cout << "Catch a sign: " << signum << std::endl;
    exit(0);
}

int main()
{
    signal(14,signal_hander);


    alarm(30);

    while(true)
    {
        std::cout <<"running pid :" << getpid() << std::endl;
        sleep(1);
    }
    

    return 0;
}

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值