Linux应用 信号

1、概念

1.1 定义

在 Linux 下,信号是一种进程间通信机制,用于通知进程发生了某种事件。信号可以由内核、其他进程或进程自身发送给目标进程。实现原理是通过向目标进程发送一个信号号码,目标进程在接收到信号后会根据信号的处理方式(信号处理函数或默认处理方式)来做出相应的处理。

1.2 信号产生

  • 键盘输入:用户在终端中按下键盘组合键(如Ctrl+C)可以发送信号给前台进程,通常是SIGINT信号。
  • 系统调用或库函数:可以使用kill系统调用或者raise库函数来向指定进程发送信号。
  • 硬件异常:硬件故障或异常(如内存访问错误)可能会导致操作系统向进程发送相应的信号(如SIGSEGV)。

1.3 信号状态

信号在Linux系统中有三个状态:未决状态(Pending)、递送状态(Delivered)和处理状态(Handling)。

  • 未决状态(Pending):表示信号已经产生,但尚未被进程处理。信号处于未决状态时,可以被阻塞或者等待进程处理。

  • 递送状态(Delivered):表示信号已经被递送给进程,但尚未开始处理。在进程接收到信号后,信号会从未决状态转换为递送状态。

  • 处理状态(Handling):表示进程正在处理信号,执行信号处理函数。处理状态是信号被处理的最终状态,处理完成后信号会从处理状态转换为完成状态。

阻塞是指进程对某个信号进行了阻塞,即暂时屏蔽了该信号的递送。当信号被阻塞时,即使信号产生了,也不会立即递送给进程,而是保持在未决状态,直到该信号被解除阻塞为止。

1.4 信号处理

在Linux系统中,每个进程都有一个信号处理表(signal table),用于存储对每种信号的处理方式。信号处理方式可以是忽略信号、执行默认操作、执行用户自定义的信号处理函数等。

1.5 小结

信号从产生到处理的状态转换过程如下:

  • 信号产生:信号由外部事件(如键盘输入、系统调用、硬件异常等)产生。
  • 信号未决:信号被发送给进程,处于未决状态,等待进程处理。
  • 信号递送:进程接收到信号,信号从未决状态转换为递送状态。
  • 信号处理:进程执行信号处理函数,处理信号的具体操作。
  • 信号处理完成:信号处理函数执行完毕,信号处理完成。

2、信号查看与分类

使用kill -l可以查看当前系统的信号:

信号分类汇总如下,不同系统可能稍有差别:

信号数值描述类型
SIGHUP1终端挂起或控制进程终止标准信号
SIGINT2中断进程(Ctrl+c)标准信号
SIGQUIT3退出进程标准信号
SIGILL4非法指令标准信号
SIGTRAP5跟踪/断点捕获标准信号
SIGABRT6异常终止标准信号
SIGBUS7总线错误标准信号
SIGFPE8浮点异常标准信号
SIGKILL9强制终止标准信号
SIGUSR110用户定义信号标准信号
SIGSEGV11无效内存引用标准信号
SIGUSR212用户定义信号标准信号
SIGPIPE13管道破裂标准信号
SIGALRM14定时器到期标准信号
SIGTERM15终止请求标准信号
SIGSTKFLT16协处理器栈错误实时信号
SIGCHLD17子进程状态发生改变标准信号
SIGCONT18继续(使暂停的进程继续执行)标准信号
SIGSTOP19停止进程标准信号
SIGTSTP20终端停止(通常由 Ctrl+Z 产生)标准信号
SIGTTIN21后台进程尝试读取控制终端标准信号
SIGTTOU22后台进程尝试写入控制终端标准信号
SIGURG23紧急情况标准信号
SIGXCPU24超过 CPU 时间限制标准信号
SIGXFSZ25超过文件大小限制标准信号
SIGVTALRM26虚拟定时器到期标准信号
SIGPROF27定时器到期标准信号
SIGWINCH28窗口大小调整实时信号
SIGIO29I/O 事件实时信号
SIGPWR30电源故障实时信号
SIGSYS31非法系统调用标准信号
SIGRTMIN34实时信号的起始编号实时信号
SIGRTMIN+135-63实时信号实时信号
SIGRTMAX64实时信号的结束编号实时信号

3、信号编程

3.1 常用接口介绍

3.1.1 signal函数

设置信号处理函数,当收到指定信号时调用该处理函数

sighandler_t signal(int signum, sighandler_t handler);
  • 入参:signum - 信号编号,handler - 信号处理函数
  • 返回值:成功时返回之前的信号处理函数指针,失败时返回 SIG_ERR
3.1.2 sigaction函数

设置信号处理函数及信号处理选项

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • 入参:signum - 信号编号,act - 新的信号处理方式,oldact - 之前的信号处理方式
  • 返回值:成功时返回0,失败时返回-1
struct sigaction {
    void (*sa_handler)(int);    // 信号处理函数的指针
    void (*sa_sigaction)(int, siginfo_t *, void *);    // 信号处理函数的指针,支持传递更多的信号信息
    sigset_t sa_mask;           // 在处理当前信号时需要屏蔽的信号集合
    int sa_flags;               // 信号处理的标志,如SA_RESTART等
};
3.1.3 kill函数

向指定进程发送信号。

int kill(pid_t pid, int sig);
  • 入参:pid - 进程ID,sig - 信号编号
  • 返回值:成功时返回0,失败时返回-1
3.1.4 sigprocmask函数

设置当前进程的信号屏蔽集合。

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 入参:how - 操作类型,set - 新的信号屏蔽集合,oldset - 之前的信号屏蔽集合
  • 返回值:成功时返回0,失败时返回-1

how常用取值:

  • SIG_BLOCK:将指定信号集合中的信号添加到当前进程的信号屏蔽集合中。
  • SIG_UNBLOCK:将指定信号集合中的信号从当前进程的信号屏蔽集合中移除。
  • SIG_SETMASK:将当前进程的信号屏蔽集合替换为指定的信号集合。
3.1.5 sigsuspend函数

挂起进程,直到收到一个信号。

int sigsuspend(const sigset_t *mask);
  • 入参:mask - 信号屏蔽集合
  • 返回值:永远不会返回,直到收到信号
3.1.6 sigismember函数

挂起进程,直到收到一个信号。

int sigismember(const sigset_t *set, int signum);
  • 入参:set:指向一个sigset_t类型的信号集合的指针,用于指定要检查的信号集合。signum:要检查的信号编号。
  • 返回值:如果指定信号signum包含在信号集合set中,则返回1;否则返回0。
3.1.7 sigemptyset和sigfillset函数

清空/填满信号集合,即将所有信号从信号集合中移除/加入。

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
  • 入参:set:指向一个sigset_t类型的信号集合的指针,用于指定要清空的信号集合。
  • 返回值:若成功清空信号集合,则返回0;否则返回-1,并设置errno来指示错误原因。
3.1.8 sigaddset和sigdelset函数

向信号集合中添加/删除指定信号。

int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
  • 入参:set:指向一个sigset_t类型的信号集合的指针,用于指定要添加/删除信号的信号集合。signum:要添加的信号编号。
  • 返回值:若成功向信号集合中添加指定信号,则返回0;否则返回-1,并设置errno来指示错误原因。
3.1.9 raise函数

向当前进程发送指定信号。

int raise(int sig);
  • 入参:sig:要发送的信号编号。
  • 返回值:若成功向当前进程发送指定信号,则返回0;否则返回非零值。
3.1.10 sigwait函数

等待指定信号的到来。

int sigwait(const sigset_t *set, int *sig);
  • 入参:set - 等待的信号集合,sig - 接收到的信号编号
  • 返回值:成功时返回0,失败时返回错误码

3.2 编程测试

测试代码如下:

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

int testNum = 1;

void signal_handler(int signum) 
{
    printf("Signal Set Received signal %d Recv %d tims\n", signum, testNum);
    testNum++;
}

void sigaction_handler(int signum) 
{
    printf("Sigaction Set Received signal %d\n", signum);
}

int main() 
{
    struct sigaction sa;
    sigset_t current_mask;
    sigset_t newset, oldset;

    // sigprocmask获取当前进程的信号屏蔽集合
    if (sigprocmask(SIG_BLOCK, NULL, &current_mask) == -1) 
    {
        perror("sigprocmask");
        return 1;
    }

    // signal设置SIGINT Ctrl+c信号处理函数
    signal(SIGINT, signal_handler);


    // sigaction设置SIGFPE信号处理函数
    sa.sa_handler = sigaction_handler;
    
    // 检查当前信号屏蔽集合,如果包含SIGFPE信号,则清除该信号
    if (sigismember(&current_mask, SIGFPE)) 
    {
        sigemptyset(&sa.sa_mask);
    } 
    else 
    {
        // 否则,将当前信号屏蔽集合设置为sa_mask
        sa.sa_mask = current_mask;
    }
    // 设置标志为0,表示无特殊标志
    sa.sa_flags = 0;

    // 设置SIGINT信号的处理方式为sa
    if (sigaction(SIGFPE, &sa, NULL) == -1)
    {
        perror("sigaction");
        return 1;
    }

    printf("Waiting for signal...\n");
    while(1) 
    {
        if(testNum == 6)
        {
            testNum++;
            printf("Ctrl + C Rev 5 , mask it\n");

            sigaddset(&newset, SIGINT);
            
            // 设置新的信号屏蔽集合
            sigprocmask(SIG_BLOCK, &newset, &oldset);

            sleep(3);
        }
        if(testNum == 7)
        {
            // 使用raise产生一次SIGFPE
            printf("\nraise Generate SIGFPE\n");
            raise(SIGFPE);
            testNum++;
            while(1);
        }
        usleep(1);
    }

    return 0;
}

代码使用了3.1中的大部分接口,使用signal和sigaction设置了SIGINT和SIGFPE的自定义处理方式,其中SIGINT(Ctrl + c)信号获取5次后将信号进行屏蔽,3秒钟后并通过软件异常和raise函数生成两次SIGFPE信号:

此时ctrl + c已经无法停止程序,使用ctrl + z暂停程序,然后使用SIGKILL(9)将程序强制停止,SIGKILL是无法被屏蔽的:

4、总结

本文阐述了信号的一些基本概念,列出了应用编程常用的接口,并编写示例进行简单测试。

  • 26
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值