Linux下的Signal信号处理及详解

信号是软件中断。很多比较重要的应用程序都需处理信号。信号提供了一种处理异步事件的方法。举一个不恰当的例子,比如你正兴奋在玩游戏,突然你手机响了,你立马放下手上的游戏,去接听电话。手机随时都会响,随时都会中断你当下的事情。所以称之为异步事件。

虽然信号是一种低级的IPC方式,但同时它保持了很简单的特性。在一些大型服务端程序中,很多时候也要考虑信号造成的影响。这里还是值得一学的。下面对信号进行介绍。

信号概述

每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。

在终端上kill -l来查看信号的名字以及序号。
在这里插入图片描述

Linux 下的信号分为可靠信号不可靠信号,或称为实时信号非实时信号,信号是从1开始编号的,不存在0号信号。对应于 Linux 的信号值为 1-31 和 34-64。0信号用来测试对应进程是否存在或者是否由权限给其发送信号。

可靠信号:也是阻塞信号,当发送了一个阻塞信号,并且该信号的动作时系统默认动作或捕捉该信号,如果信号从发出以后会一直保持未决的状态,直到该进程对此信号解除了阻塞,或将对此信号的动作更改为忽略。

不可靠信号:信号可能会丢失,一旦信号丢失了,进程并不能知道信号丢失。

信号的处理有三种方法

1、忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
2、捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
3、系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。

具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。在此,我就不详细展开了,需要查看的,可以自行查看。

man 7 signal

在这里插入图片描述

一般程序收到信号时进程的现象

停止进程
终止(kill)进程
忽略信号
等等。。。

常用信号类型即默认行为

信号名称 说明 默认动作

SIGINT Ctrl-C终端下产生 终止当前进程
SIGABRT 产生SIGABRT信号 默认终止进程,并产生core(核心转储)文件
SIGALRM 由定时器如alarm(),setitimer()等产生的定时器超时触发的信号 终止当前进程
SIGCHLD 子进程结束后向父进程发送 忽略
SIGBUS 总线错误,即发生了某种内存访问错误 终止当前进程并产生核心转储文件
SIGKILL 必杀信号,收到信号的进程一定结束,不能捕获 终止进程
SIGPIPE 管道断裂,向已关闭的管道写操作 进程终止
SIGIO 使用fcntl注册I/O事件,当管道或者socket上由I/O时产生此信号 终止当前进程
SIGQUIT 在终端下Ctrl-\产生 终止当前进程,并产生core文件
SIGSEGV 对内存无效的访问导致即常见的“段错误” 终止当前进程,并产生core文件
SIGSTOP 必停信号,不能被阻塞,不能被捕捉 停止当前进程
SIGTERM 终止进程的标准信号 终止当前进程

信号处理函数注册与发送

信号处理函数的注册有两种方法分别是:

#include <signal.h>

void (*signal(int sig, void (*func)(int)))(int);
#include <signal.h>

int sigaction(int sig, const struct sigaction *restrict act,
       struct sigaction *restrict oact);

信号发送函数也有两个:

#include <signal.h>

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

int sigqueue(pid_t pid, int signo, union sigval value);

信号函数

UNIX系统信号机制最简单的接口是signal函数。

#include <signal.h>

void (*signal(int sig, void (*func)(int)))(int);

signal函数原型说明此函数要求型数,返回一个函数指针,而该指针指向的函数无返回值,第一个参数sig是一个整型数,第二个参数是函数指针,它所指向的函数需要一个整形参数,无返回值。

设计信号函数需要注意哪些

1、一般而言,信号处理函数设计的越简单越好,因为当前代码的执行逻辑被打断,最好尽快恢复到刚才被打断之前的状态。从而避免竞争条件的产生。
2、在信号处理函数中,建议不要调用printf等与I/O相关的函数。
3、在信号处理函数中,不要使用任何不可重入的函数

实例

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>

#define     MSG         "Catch signal SIGINT processing \n"
#define     MSG_END     "Finished process SIGINT return \n"

void time_fun() ;

static void sig_handler (int signuum ) 
{

    /*
    在信号处理程序中,尽量不要调用与标准IO相关的和不可重入的函数。

    STDIN_FILENO:接收键盘的输入

    STDOUT_FILENO:向屏幕输出
     */
    
    write ( STDOUT_FILENO , MSG , strlen (MSG) ) ;
    time_fun();
    write ( STDOUT_FILENO , MSG_END , strlen (MSG_END) ) ;
}


void time_fun() 
{
    long long s = 0 ;
    long long i ;

    for ( i= 0 ; i < 500000000L ; i++ ) 
    {
        s += i ;    
    }
}


int main(int argc, char **argv) 
{

    // 注册信号处理函数
    
    if ( SIG_ERR == signal ( SIGINT , sig_handler ) ) 
    {
        fprintf (stderr , "signal error ") , perror ("") ;
        exit (1) ;
    }

    // 让主程序不退出,挂起,等待信号产生
    while (1) 
    {
        pause () ; //使调用进程在接到一信号前挂起。
    }

    return 0 ;
}

编译输出:
在这里插入图片描述

简单的总结一下,我们通过 signal 函数注册一个信号处理函数,如果系统通过 ctrl+c 产生了一个 SIGINT(中断信号),显然不是所有程序同时结束,那么,信号一定需要有一个接收者。对于处理信号的程序来说,接收者就是自己。

注册信号处理函数

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>


#define     MSG         "Catch signal SIGINT processing \n"
#define     MSG_END     "Finished process SIGINT return \n"

void time_fun() ;

static void sig_handler (int signuum ) 
{

    /*
    在信号处理程序中,尽量不要调用与标准IO相关的和不可重入的函数。

    STDIN_FILENO:接收键盘的输入

    STDOUT_FILENO:向屏幕输出
     */
    
    write ( STDOUT_FILENO , MSG , strlen (MSG) ) ;
    time_fun();
    write ( STDOUT_FILENO , MSG_END , strlen (MSG_END) ) ;
}


void time_fun() 
{
    long long s = 0 ;
    long long i ;

    for ( i= 0 ; i < 500000000L ; i++ ) 
    {
        s += i ;    
    }
}


int main(int argc, char **argv) 
{
       /*
     struct sigaction {
     void     (*sa_handler)(int);
     void     (*sa_sigaction)(int, siginfo_t *, void *);
     sigset_t   sa_mask;
     int        sa_flags;
     };
     */

    // 注册信号处理函数
    struct sigaction  newact;

    // 将信号处理函数执行期间掩码设置为空
    sigemptyset (&newact.sa_mask );
    // 将标志设置为0即默认
    newact.sa_flags = 0;
    // 注册信号处理函数
    newact.sa_handler = sig_handler ;

    if ( 0 > sigaction ( SIGINT , &newact , NULL ) ) {
        fprintf (stderr , "sigaction error ") , perror ("") ;
        exit (1) ;
    }
    // 让主程序不退出,挂起,等待信号产生
    while (1) {
        pause () ;
    }

    return 0 ;
}

编译运行

在这里插入图片描述

执行效果和上面的一样。

如何如何发送信号

1、在终端下可以用kill/killall命令发送(缺省为SIGTERM信号),如下:

在这里插入图片描述

kill -SIGKILL 2582

在这里插入图片描述

在这里插入图片描述

2、可以在终端下使用Ctrl+C(SIGINT信号),Ctrl+(SIGQUIT信号).Ctrl+z(SIGSTOP信号)等

3、在程序中可以使用 kill、sigqueue 等。

总结

信号用于大多数复杂的应用程序中,理解进行信号的处理的原因和方式对于Unix编程及其重要。

在信号处理函数中,建议不要调用printf等与I/O相关的函数。不然会使得信号处理函数的执行时间变长。
在信号处理函数中,不要使用任何不可重入的函数。

下一篇讲解信号的各种用法。

参考:《Unix环境高级编程 第三版》

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中的信号处理是通过信号机制实现的。当一个进程接收到一个信号时,它会根据事先定义好的处理方式来处理这个信号信号的处理方式包括终止进程、忽略信号、终止进程并生成core文件、停止进程和继续运行进程等不同的动作。 在Linux中,信号的处理是通过设置信号的处理函数来完成的。当一个信号到达时,内核会调用相应的处理函数来处理这个信号。可以通过系统提供的函数来设置自定义的信号处理函数。 信号的发送可以通过多种方式,包括按键产生、终端按键产生、系统调用产生、软件条件产生和硬件异常产生等。不同的事件会触发不同的信号发送。例如,按下Ctrl+C会发送SIGINT信号,而按下Ctrl+Z会发送SIGTSTP信号。 对于进程来说,接收到信号后,不管正在执行什么代码,都会暂停运行,去处理信号。这种处理方式类似于硬件中断,被称为“软中断”。对于用户来说,由于信号的实现方式,信号的延迟时间非常短,几乎不可察觉。 总而言之,Linux中的信号处理是通过信号机制实现的,程序在接收到信号后会根据事先定义好的处理方式来处理这个信号。这种处理方式可以通过设置信号的处理函数来自定义。信号的发送可以通过多种方式,不同的事件会触发不同的信号发送。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Linux信号signal)](https://blog.csdn.net/weixin_43408582/article/details/115523424)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值