Linux信号处理

目录 

前言

一、基本概念 

二、信号捕获  

 1.信号处理函数

 2.设置信号处理函数(信号的捕获)

 使用signal函数

使用sigaction函数

三、 信号的发送方式

四、信号集和信号阻塞 

五、进程休眠信号


前言

在Linux系统中,使用C语言处理信号是一个常见的需求,尤其是在编写需要响应特定系统事件(如中断、定时器到期、用户请求等)的程序时。Linux提供了一套机制来允许进程捕获和处理信号。这里,我们将详细讨论如何使用C语言在Linux中设置信号处理程序。


一、基本概念 

    1、中断
        当程序接收到消息后中止当前正在执行的程序,转而执行其它任务,等其它任务执行完成后再返回,这种执行模式叫做中断
        分为硬件中断和软件中断
    2、信号
        是一种软件中断,由操作系统发出,程序接收后会执行相应的操作
    3、常见的信号
        命令 kill -l 查看所有信号
        SIGINT(2)   Ctrl+c                    终止
        SIGQUIT(3)  Ctrl+\                   终止+core
        SIGFPE(8)   除零                     终止+core
        SIGKILL(9)  终止信号               终止  注意:该信号不能捕获、忽略。
        SIGSEGV(11) 非法访问内存    终止+core
    4、不可靠信号和可靠信号
        建立在早期的信号处理机制上(1~31)的信号是不可靠信号
            不支持排队、可能会丢失,如果同一个信号连续产生多次,进程可能只响应了一次
        建立在新的信号处理机制上(34~64)的信号是可靠信号
            支持排队、信号不会丢失
    5、信号的来源
        硬件异常:除零、非法访问内存、未定义的指令、总线错误
        软件异常:通过一些命令、函数产生的信号
    6、信号的处理方式
        1、忽略
        2、终止进程
        3、终止进程并产生core文件(记录内存映像)
        4、停止 只是暂停进程还在,用ps -aux依然可以看到进程
        5、继续
        6、捕获并处理   (在信号发生前,先向内核注册一个函数,当信号来临时系统会自动执行该函数) 

总结:在Linux中,信号是软件中断,它们提供了一种机制来通知进程发生了某个事件。当信号被发送给进程时,该进程可以选择忽略它、捕获它以便进行特定处理,或者采取默认操作(通常是终止进程)。 


二、信号捕获  

 1.信号处理函数

信号处理函数是一个用户定义的函数,用于响应特定信号。这个函数需要具有特定的原型,并接受一个整数参数(信号编号)。

#include <signal.h>  
  
void signal_handler(int signum) {  
    // 处理信号signum的代码  
    printf("捕获到信号 %d\n", signum);  
    // 清理工作...  
    // 注意:不要在信号处理函数中调用非异步信号安全的函数  
}

 2.设置信号处理函数(信号的捕获)

 在C语言中,可以使用signal()函数或sigaction()函数来设置信号处理函数。

 使用signal函数

signal()函数用于设置与指定信号相关联的处理函数。但请注意,signal()的行为在不同的系统上可能有所不同(特别是当处理函数被调用时,如果信号再次发生),这被称为信号的"不可靠性"行为。 

#include <signal.h>  
  
int main() {  
    signal(SIGINT, signal_handler); // 设置SIGINT信号的处理函数  
  
    // 主程序循环或其他操作  
    while(1) {  
        // 等待信号...  
    }  
  
    return 0;  
}

使用sigaction函数

sigaction()函数提供了一种更可靠的方式来设置信号处理函数,并允许你获取和修改与信号相关联的其他属性。 

函数原型

#include <signal.h>  
  
int sigaction(int signum, const struct sigaction *act, struct sigaction *oact);

 参数

  • signum:指定要操作的信号编号。可以是除SIGKILL和SIGSTOP之外的任何有效信号。
  • act:指向struct sigaction类型的指针,该结构体定义了与signum信号相关联的新处理动作。如果此参数非空,则使用其定义的新处理动作。
  • oact:如果非空,则指向的struct sigaction会被填充为调用前的信号处理动作。这允许调用者保存旧的信号处理动作并在需要时恢复它。

 struct sigaction结构体

struct sigaction {  
    union {  
        __sighandler_t sa_handler;  // 信号处理函数指针,指向一个函数,该函数只有一个整型参数(信号编号)  
        void (*_sa_sigaction)(int, siginfo_t *, void *); // 可选的处理函数,提供附加信息  
    } _u;  
    sigset_t sa_mask;               // 信号屏蔽字,在信号处理函数执行期间需要阻塞的信号集  
    unsigned long sa_flags;         // 标志位,用于控制信号处理的行为  
    void (*sa_restorer)(void);      // 已废弃,不用关心  
};

sa_flags标志位 

sa_flags字段可以包含以下标志位,用于控制信号处理的行为:

  • SA_RESETHAND:当信号处理函数返回时,将该信号的处理方式重置为默认(SIG_DFL)。
  • SA_NODEFER:在信号处理函数执行期间,不自动阻塞该信号。
  • SA_RESTART:如果信号中断了某个系统调用,自动重启该系统调用。
  • SA_SIGINFO:使用sa_sigaction成员作为信号处理函数,而不是sa_handler。此时,信号处理函数会接收到更多的信息,如信号的产生原因和发送信号的进程ID。

示例 

#include <stdio.h>  
#include <signal.h>  
#include <stdlib.h>  
#include <unistd.h>  
  
void signal_handler(int signum) {  
    printf("捕获到信号 %d\n", signum);  
    // 清理工作...  
}  
  
int main() {  
    struct sigaction act;  
    memset(&act, 0, sizeof(act));  
  
    act.sa_handler = signal_handler;   // 设置信号处理函数 
    sigemptyset(&act.sa_mask);         // 初始化信号掩码
    act.sa_flags = 0;                  // 清除标志
  
    // 绑定SIGINT到处理函数
    if (sigaction(SIGINT, &act, NULL) == -1) {  
        perror("sigaction");  
        exit(EXIT_FAILURE);  
    }  
  
    // 主程序循环或其他操作  
    while (1) {  
        pause(); // 暂停进程,等待信号  
    }  
  
    return 0;  
}

三、 信号的发送方式

 1、通过键盘
        Ctrl+c
        Ctrl+\
        Ctrl+z 暂停\挂起  fg 继续
2、程序出现错误
        除零
        非法访问内存
        总线错误
3、通过命令
        kill -信号编号 进程号
        功能:向指定的进程发送信号
        killall -信号编号 进程名/进程号
            可以给同名进程发送同一个信号
4、通过调用函数

int kill(pid_t pid, int sig);
功能:向指定进程发送指定信号
pid:进程号
sig:信号编号

int raise(int sig);
功能:向进程自己发送信号sig

void abort(void);
功能:向进程自己发送信号SIGABRT

unsigned int alarm(unsigned int seconds);
功能:让内核在seconds秒后向进程发送SIGALRM信号
返回值:上次alarm设置的剩余秒数
返回值注意:
    如果之前的 alarm 调用已经设置了一个定时器,并且该定时器还没有到期,那么新的 alarm 调用会取消旧的定时器,并返回旧定时器剩余的秒数(即,从当前时间到旧定时器到期的时间)。
    如果之前没有 alarm 定时器设置,或者旧的定时器已经到期,那么 alarm 会返回 0

四、信号集和信号阻塞 

 信号集:是一种数据类型,定义的变量可以存储多个信号sigset_t 128位的二进制数,每一位都固定代表了一种信号。
 相关函数:

int sigemptyset(sigset_t *set);
功能:清空信号集

int sigfillset(sigset_t *set);
功能:填满信号集

int sigaddset(sigset_t *set, int signum);
功能:向信号集set中添加信号signum

int sigdelset(sigset_t *set, int signum);
功能:从信号集set中删除信号signum

int sigismember(const sigset_t *set, int signum);
功能:测试信号集中是否存在signum信号
返回值:
    0   不存在
    1   存在
   -1  signum信号非法

信号阻塞:

当程序执行到一些特殊操作时,不适合处理信号,此时可以让内核先屏蔽信号,等操作执行完成后再解除屏蔽重新发送信号。

当信号产生时,内核会在其维护的信号表中为对应的进程设置与该信号对应的标记,这个过程叫做递送。

从信号产生到完成递送有个时间间隔,处于这个间隔的信号状态称为未决信号屏蔽\阻塞就是让被屏蔽的信号先处于未决状态、暂停递送,当屏蔽解除时在继续递送

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:设置要屏蔽的信号、这些信号是存储在信号集里面
how:信号屏蔽的方式
   SIG_BLOCK   把set中的信号添加到要屏蔽的信号集里(追加)
   SIG_UNBLOCK 从信号屏蔽集中删除set中的信号 解除
   SIG_SETMASK 把set替换之前的信号屏蔽集(替换)
set:准备好的信号集
oldset:获取旧的信号屏蔽集
应用:sigprocmask(SIG_SETMASK, NULL, &oldset)//获取当前的信号屏蔽集,保存在oldset中。
     sigprocmask(SIG_SETMASK, &oldset, NULL)//恢复之前保存的信号屏蔽集

五、进程休眠信号

int pause(void);
功能:让调用者进入休眠状态,直到进程遇到信号才会唤醒
返回值:要么不返回在休眠,要么唤醒后返回-1
相当于没有时间限制的sleep

unsigned int sleep(unsigned int seconds);
功能:让调用者进入休眠指定的秒数,当遇到信号时会提前唤醒返回
返回值:剩余的休眠时间
  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值