当前位置: 首页 > article >正文

【Linux】信号的产生、保存与处理

信号

什么是信号

信号是Linux提供的一种向指定进程发送特定事件的方式。

Linux中规定了64种信号,其中1到31号为不可靠信号(可能丢失),32到64号为可靠信号(不可能丢失)。

注:绝大多数的默认操作都是终止进程。 

信号取值名称作用默认操作
1SIGHUP挂起
2SIGINT中断
3SIGQUIT退出
4SIGILL非法指令
5SIGTRAP断点或陷阱指令
6SIGABRTabort发出的信号
7SIGBUS非法内存访问
8SIGFPE浮点异常
9SIGKILLkill信号不能被忽略、处理和阻塞
10SIGUSR1用户信号1
11SIGSEGV无效内存访问
12SIGUSR2用户信号2
13SIGPIPE管道破损,没有读端的管道写数据
14SIGALRMalarm发出的信号
15SIGTERM终止信号
16SIGSTKFLT栈溢出
17SIGCHLD子进程退出默认忽略
18SIGCONT进程继续
19SIGSTOP进程停止不能被忽略、处理和阻塞
20SIGTSTP进程停止
21SIGTTIN进程停止,后台进程从终端读数据时
22SIGTTOU进程停止,后台进程向终端写数据时
23SIGURGIO有紧急数据到达当前进程默认忽略
24SIGXCPU进程的CPU时间片到期
25SIGXFSZ文件大小的超出上限
26SIGVTALRM虚拟时钟超时
27SIGPROFprofile时钟超时
28SIGWINCH窗口大小改变默认忽略
29SIGIOIO相关
30SIGPWR关机默认忽略
31SIGSYS系统调用异常

        信号通常经历以下三个阶段,产生、保存、递达(处理)。处于保存时期的信号处于一个叫未决的状态。可以简单理解为信号到了,但未处理的状态。 

        举个例子,你的朋友让你给他买瓶汽水,但由于你手头上有更加重要的事,所以你默默地记下给他买瓶汽水这个信号等到你忙完后才去执行。此时你对信号的处理是阻塞,信号在被递达前都处于未决状态。当然,除了阻塞你也可以选择忽略,忽略则是信号到了但是你直接无视。

 

信号的产生

信号的产生是异步的,他能够使一个正在执行的进程被异步打断,转而去处理一个突发事件。

信号的产生大致有以下五种原因,但从始至终信号都是有OS来发送的

  1. 通过kill命令向指定进程发信号。例:kill -2 8888  (2是信号取值,代表2号信号。8888是一个进程的pid)
  2. 键盘也可以产生信号(Ctrl+c  就相当于向当前进行发送2号信号)
  3. 系统调用,例如  int kill(pid_t pid,int sig)函数。
  4. 软件条件,例如管道读端关闭,写端一直写,那么OS就会向他发送13号信号SIGPIPE关闭写端。(这就是读端关闭写端就不写了的原因)
  5. 异常,出现异常进程停止运行也是OS对其发送了信号。 

信号的保存与处理

每个进程都有一个对应的task_struct,而在task_struct中有一个管理信号的结构。这个结构主要分为三部分。pending、block是两张位图,pengding的每个位置都代表对应的信号,0或1代表该信号是否未决(到达但未处理)。clock的每个位置同样代表对应的信号,0或1代表该信号是否被阻塞。而handle是一个函数指针数组,他的每个位置都对应信号的处理方法,不修改则为默认,可以通过系统调用进行自定义。

当信号到来时,OS就会遍历pending位图,为0则查看下一个比特位,为1则查看block位图的对应位置,此时block的该比特位为1则不做处理(被阻塞),为0则将对应位置pending置0再执行对应位置的handle操作。

信号的捕捉

信号的捕捉流程

 内核态对比用户态最明显的区别的内核态拥有更大的权力。所以进行自定义的信号处理时,要从内核态回到用户态,这是对操作系统的保护,防止用户定义的函数在内核态进行非法操作。

signal函数 

作用:捕捉一个指定信号,设定该信号的操作方法。

  • #include <signal.h>

    typedef void (*sighandler_t)(int);

    sighandler_t signal(int signum, sighandler_t handler);

  • 第一个参数signum代表信号的取值。
  • 第二个参数有三种,第一种是返回值为void,参数为int的自定义函数指针;第二种是  SIG_IGN  代表忽略一个信号;SIG_DFL代表默认处理。
#include <iostream>
#include <unistd.h>
#include <signal.h>

void sigcb(int sig)
{
    std::cout << "get a sig : " << sig << std::endl;
}

int main()
{
    signal(2, sigcb);
    while (true)
    {
        std::cout << "process is running , pid : " << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

向进程3348发送2号信号,进程收到信号后执行了sigcb函数。 

 sigaction函数

作用:捕捉一个指定信号,设定该信号的操作方法。

  • #include <signal.h>                                                                                                            int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
  • 第一个参数signum代表信号的取值。
  • 第二个参数设定新的结构体(结构体中包含handle函数指针,与signal中的一致)
  • 第三个参数输出原来的结构体
#include <iostream>
#include <unistd.h>
#include <signal.h>

void sigcb(int sig)
{
    std::cout << "get a sig : " << sig << std::endl;
}

/*struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
}
*/

int main()
{
    struct sigaction act;
    act.sa_handler = sigcb;
    sigaction(2, &act, nullptr);
    while (true)
    {
        std::cout << "process is running , pid : " << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

sigprocmask函数 

作用: sigprocmask函数用于检查或修改当前进程的信号屏蔽字(signal mask)

  • #include <signal.h>                                                                                                          int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 第一个参数,操作标志,决定如何修改信号屏蔽字。有三个选项:                 SIG_BLOCK:把 set 指向的信号集中的信号添加到当前信号屏蔽字中。         SIG_UNBLOCK:从当前信号屏蔽字中移除 set 指向的信号集中的信号。     SIG_SETMASK:用 set 指向的信号集替换当前信号屏蔽字。
  • const sigset_t *set:指向要修改的新信号集的指针。
  • sigset_t *oldset:如果不为 NULL,则存储之前的信号屏蔽字。
#include <iostream>
#include <unistd.h>
#include <signal.h>

void PrintfPending(sigset_t &pending)
{
    std::cout << "cur process pid : " << getpid() << "\n";
    std::cout << "pending signo :";
    for (int signo = 31; signo >= 1; signo--)
    {
        if (sigismember(&pending, signo))
        {
            std::cout << 1;
        }
        else
        {
            std::cout << 0;
        }
    }
    std::cout << "\n";
}

int main()
{
    signal(2, handler);

    sigset_t block_set, old_set;
    sigemptyset(&block_set);
    sigemptyset(&old_set);
    sigaddset(&block_set, 2);

    sigprocmask(SIG_BLOCK, &block_set, &old_set);

    while (true)
    {
        sigset_t pending;
        sigpending(&pending);

        PrintfPending(pending);
        sleep(2);
    }
    return 0;
}


http://www.kler.cn/a/301462.html

相关文章:

  • 【流量分析】常见webshell流量分析
  • NVR录像机汇聚管理EasyNVR多品牌NVR管理工具视频汇聚技术在智慧安防监控中的应用与优势
  • Javascript高级—函数柯西化
  • 【MYSQL】分库分表
  • 如何从docker-hub下载镜像
  • 若依笔记(十):芋道的菜单权限与数据隔离
  • 网页时装购物系统:Spring Boot技术的实际应用
  • 【双指针】N数之和
  • [SWPUCTF 2021 新生赛]web方向(一到六题) 解题思路,实操解析,解题软件使用,解题方法教程
  • 猫咪掉毛怎么处理?希喂、米家、范罗士宠物空气净化器用哪款?
  • Linux 删除 当前下的 mysql-8.0.31 空文件夹
  • ChatGPT的底层逻辑
  • 物联网的设计
  • ubuntu 安装 jdk
  • 【游戏杂谈】关于靠谱及不靠谱的游戏立项方式探讨
  • 大模型系列-fastgpt,ollama搭建本地知识库
  • 爬虫基础知识+豆瓣电影实战
  • 2024年六月英语四级真题及解析PDF共9页
  • STM32时钟树
  • linux-用户与权限管理-文件权限
  • C#中的数组
  • 基于SSM的二手交易管理系统的设计与实现 (含源码+sql+视频导入教程+文档)
  • Java-手机号码检验
  • PyTorch:优化读取LMDB数据的五大策略
  • 828华为云征文 | 华为云Flexus X实例上实现Docker容器的实时监控与可视化分析
  • 实时监控电脑屏幕如何做到?怎么监视电脑屏幕?(30秒学会这3种简单又实用的方法)