Linux操作系统-信号

本文详细介绍了Linux操作系统中的信号机制,包括信号的基本概念、进程处理信号的三种方式、信号的产生及其分类,如键盘组合键、系统调用、软件条件和硬件异常。文章深入探讨了信号保存、核心转储、信号处理的时机和流程,以及信号处理函数的自定义。文中还提到了可重入函数、volatile关键字和SIGCHLD信号在进程管理中的作用。

信号的基本认识:

Linux信号机制:

它是一种异步的通知机制,用来提醒进程一个事件已经发生。

如上图,Linux操作系统中,共有编号为1~31的31个普通信号,编号为34~64的31个实时信号。日常中只会涉及和使用到普通信号。故下方对信号的学习仅对于1~31的普通信号。

每个信号都有一个编号和一个宏定义名称,本质上,这些都是通过#define的形式定义的。也就是用一个int型变量去代替某特定信号。(编译之后,这些宏定义都会变为int整型)

进程处理信号的三种方式:

信号的接收方一定是进程,因为信号就是用来提醒进程某个事件已经发生。故,进程在接收到信号之后,一定要处理这个信号。

  1. 执行该信号的默认处理动作。

  1. 忽略该信号。

  1. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。(其实就是程序员自定义进程对某信号的处理方法,存储在用户代码中)

简单来说就是默认,忽略,自定义捕捉。

信号产生:产生信号的若干种方式

1.通过键盘产生信号:

ctrl + c:通过键盘组合键向前台进程发送2号SIGINT信号。

ctrl + \:通过键盘组合键向前台进程发送3号SIGQUIT信号。

理解:键盘是通过中断的方式工作的。输入某组合键->OS解释识别组合键->查找进程列表->前台运行的进程->OS将前台运行进程的PCB(task_struct)中的pending信号集位图中组合键对应的信号所对应的比特位由0置1(pending 信号集本质是一个位图结构,见信号保存。)(其实最后一步一句话就是:OS向前台进程发送对应信号,发送信号的本质就是如此)

有关前台进程与后台进程: Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行(如 ./mysignal &),这样Shell不必等待进程 结束就可以接受新的命令,启动新的进程。Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号

前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行 到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的。

2.通过系统调用接口产生信号:

  1. int kill(pid_t pid, int signo); 向指定进程发送指定信号

  1. int raise(int signo); 向当前进程发送指定信号

  1. void abort(void); 向当前进程发送6号SIGABRT信号

  1. 还有一个kill命令,就是通过调用kill函数实现的。

// 通过系统调用发送信号

void handler(int signo)
{
    std::cout << "进程收到了一个" << signo << "号信号" << std::endl;
}

int main()
{
    signal(2, handler);  // 捕捉下方kill 和 raise发送的信号
    signal(SIGABRT, handler);  // 捕捉abort发送的6号SIGABRT信号
    kill(getpid(), 2);
    raise(2);
    abort();    // 向当前进程发送SIGABRT信号,使其异常终止(默认)

    while(true) sleep(1);
    return 0;
}

如上图,对6号SIGABRT信号明明捕捉了但是还是中止了。 这是一个很奇怪的现象,查了stack overflow:However, I cannot find any corroborating evidence of that behavior in the signal man page, which clearly states that The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored but makes no similar mention for SIGABRT. 也就是信号手册种明确说了9和19号信号不能被捕捉,阻塞,或者忽略,但是没有说SIGABRT信号不能被捕捉。其实这个是因为调用了abort,abort不仅发送了SIGABRT信号,还做了其他事情。所以如果把abort();换成kill(getpid(), SIGABRT); 就会捕捉SIGABRT且进程不会退出。

如何理解通过调用系统接口产生信号:其实很简单,系统调用接口通过提取参数,获取进程pid,信号编号,然后OS向进程PCB内写信号,也就是修改对应进程的pending位图的特定位,后续进程处理该信号。

3.由软件条件产生信号:

例一:进程间通过管道通信时,因软件条件而产生SIGPIPE信号。

进程间通过管道通信时,不管是命名管道还是匿名管道,都会提供访问控制。也就是管道读写的四种情况。情况之一是:当管道的读端关闭,写端继续写,则OS会向写端进程发送SIGPIPE信号从而终止写端进程。(SIGPIPE信号的默认处理方式就是terminate process,终止进程。产生条件:Broken pipe:write to pipe with no readers)有关进程间通信博客:http://t.csdn.cn/CpN9a

// 通过软件条件发送信号,比如管道读端关闭,写端继续写,OS会终止写端进程
void handler(int signo)
{
    std::cout << "进程收到了" << signo << "号信号" << std::endl;
}

int main()
{
    int pipefd[2] = {0};
    pipe(pipefd);  // 创建匿名管道

    pid_t id = fork();
    if(id == 0)
    {
        // 子进程,读端,关闭写端
        close(pipefd[1]);
        sleep(3);
        close(pipefd[0]); // 三秒后关闭读端
    }

    // 父进程写端
    signal(SIGPIPE, handler);
    close(pipefd[0]); // 关闭读端
    while(true)
    {
        const char* message = "haha";
        write(pipefd[1], message, strlen(message));
        sleep(1); // 每隔一秒写一次
    }
    return 0;
}

如上,对SIGPIPE信号进行捕捉,当OS发送SIGPIPE信号时,会执行handler方法。下方打印1s进行一次。

SIGPIPE是一种由软件条件产生的信号

例二:alarm函数,因软件条件产生SIGALRM信号。

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。(也就是一个进程在同一时刻只会有一个闹钟)

下方是一个基于alarm和SIGALRM信号的简单定时执行某任务的程序。

// 因软件条件产生信号:alarm函数,SIGALRM信号

typedef std::function<void()> func;
std::vector<func> callbacks;

uint64_t count = 0;

void showCount()
{
    std::cout << "current count is : " << count << std::endl;
}

void showLog()
{
    std::cout << "Log..." << std::endl;
}

void logUser()
{
    if(fork() == 0)
    {
        // 子进程
        execl("/usr/bin/who", "who", nullptr);
        exit(1);
    }
    // 父进程等待回收一下
    wait(nullptr);
}

void catchSIGALRM(int signo)
{
    std::cout << "进程收到了SIGALRM信号" << std::endl;
    // 对于SIGALRM的自定义捕
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值