Linux进程信号

信号:软件中断

用于通知一个事件的发生,会打断当前进程的操作去处理这个事件;前提:必须识别这个信号
信号有生命周期:

  • 信号的产生
  • 信号在进程中的注册
  • 信号在进程中注销
  • 信号的处理

信号种类有很多,每个都代表不同事件:
使用 kill -l 命令查看:62种
1~31:继承 unix 而来,非可靠信号(信号有可能会丢失,丢失事件),也叫 非实时信号
34~64:可靠信号(信号不会丢失),也叫 实时信号
在这里插入图片描述

信号的产生

硬件产生信号:

  • ctrl + c(中断)
  • ctrl + |(退出)
  • ctrl + z(停止)

软件产生信号:

  • 向进程发送一个signum信号
    kill -signum pid
  • 给指定 pid 进程发送指定 sig 信号
    int kill(pid_t pid, int sig);
  • 给进程自己发送 sig 信号
    int raise(int sig);
  • 给进程自己发送SIGABRT信号
    void abort(void);
  • 在 seconds 秒之后给进程自己发送一个 SIGALRM 信号,若 seconds = 0, 则作用是取消上一个定时器
    unsigned int alarm(unsigned int seconds);
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>

int main()
{
    // int kill(pid_t pid, int sig);
    // 向pid进程发送sig信号
    //kill(getpid(), SIGKILL);
    
    // int raise(int sig);
    // 向自己发送sig信号
    //raise(SIGTERM);
    
    // void abort(void);
    // 给自己发送SIGABRT信号
    //abort();
    
    // unsigned int alarm(unsigned int seconds);
    // 经过seconds秒之后,给自己发送一个SIGALRM信号-->定时器
    alarm(3);

    while(1)
    {
        printf("hello world\n");
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

信号在进程中的注册:在进程pcb中做标记,标记进程收到了哪些信号
  • sigset_t 结构体的认识:
    unsigned long int_val[_SIGSET_NWORDS];
    是一个128位的位图,用于对信号是否到来做标记
  • 非可靠信号注册:判断pcb的 pending 位图中相应信号是否已经注册(位图是否已经置1)
    若未注册,则位图修改为1,向 sigqueue 链表中添加一个信号节点
    若注册,则不做任何操作(事件丢失)
  • 可靠信号注册:不管信号是否已经注册,都会向链表中添加一个新的信号节点(事件不会丢失)
  • 未决:是一个信号从产生到信号处理之间所处的状态
  • 未决信号:注册了但是没有被处理的信号
信号在进程中的注销

非可靠信号:节点只有一个,注销就是删除节点,位图置0
可靠信号:节点可能有多个,注销就是删除一个节点,判断链表中是否还有相同信号的节点;若没有则位图置0;否则位图不变,依然需要标记进程有这个信号待处理

信号的处理
  • 信号的处理并不是立即被处理;而是选择一个合适的时机去处理信号
  • 信号处理有多种方式:
    默认处理方式:SIG_DFL
    忽略处理方式:SIG_IGN
    自定义处理方式:用户自己写一个事件处理函数
  • 如何修改信号处理方式:
    第一种:
    在这里插入图片描述
    signum:信号编号,用户传入的处理函数替换 signum 这个信号的处理
    handler:函数指针,用户传入的处理函数typedef void(*sighandler_t)(int);

       第二种:

在这里插入图片描述
       使用 act 动作替换 signum 信号的处理方式,将原来的处理方式放到 oldact 中
       signum:信号编号
       act:信号新的处理动作
       oldact:用于保存信号原来的处理动作

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

void sigcb(int signo)
{
    printf("receive a signo:%d\n", signo);
}

int main()
{
    //将SIGINT信号设置为忽略处理
    signal(SIGINT, SIG_IGN);
    
    struct sigaction new_act, old_act;
    new_act.sa_flags = 0;
    new_act.sa_handler = sigcb;

    // int sigemptyset(sigset_t *set);
    // 清空set信号集合
    sigemptyset(&new_act.sa_mask);

    // sigaction使用newact替换SIGINT信号的处理动作,将原有动作保存到oldact中
    sigaction(SIGINT, &new_act, &old_act);

    while(1)
    {
        printf("hello world\n");
        sleep(1);
    }

    return 0;
}

在这里插入图片描述

信号的阻塞:阻止信号被递达(暂时不处理信号)

递达:一个动作,即信号的处理
在pcb中还有一个集合(阻塞信号集合,标记哪些信号暂时不被处理)
在所有的信号中,9号信号 SIGKILL 和19号信号 SIGSTOP,无法被阻塞,无法被自定义,无法被忽略

操作方式:
在这里插入图片描述
how:

  • SIG_BLOCK:向阻塞集合中加入 set 集合中的信号 block = mask | set
  • SIG_UNBLOCK:从阻塞集合中移除 set 集合中的信号 block = mask & (~set)
  • SIG_SETMASK:将 set 集合的信号设置为阻塞集合 block = set

set:要阻塞/解除阻塞的信号集合
oldset:用于保存修改前阻塞集合中的信号

关于set的接口操作:
在这里插入图片描述
操作步骤(demo):

  • 将一些信号的处理函数自定义
  • 将所有的信号都给阻塞
  • 在解除阻塞之前给进程发送信号
  • 解除阻塞,查看信号的处理情况
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sigcb(int signo)
{
    printf("receive signo:%d\n", signo);
}

int main()
{
    //自定义函数处理SIGINT信号,非可靠信号
    signal(SIGINT, sigcb);
    //自定义函数处理SIGRTMIN+4信号,可靠信号
    signal(SIGRTMIN+4, sigcb);

    sigset_t set;
    sigemptyset(&set); //清空set集合

    sigfillset(&set); //将所有信号都添加到set集合中

    sigprocmask(SIG_BLOCK, &set, NULL); //阻塞set集合中的信号

    printf("press enter continue\n");
    getchar(); //按下回车键后继续向下运行

    sigprocmask(SIG_UNBLOCK, &set, NULL); //将set集合中的信号解除阻塞

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

在这里插入图片描述

在这里插入图片描述

可重入函数与不可重入函数
  • 可重入:多个执行流程同时执行进入相同的函数,不会造成数据二义性以及代码逻辑混乱
  • 不可重入:多个执行流程同时执行进入相同的函数,有可能会造成数据二义性以及代码逻辑混乱
  • 当用户设计一个函数或使用一个函数时在多个执行流中,就需要考虑函数是否可重入情况
  • 函数可重入与不可重入的关键点:
    这个函数是否对临界资源(全局数据)进行了非原子操作
//这个demo来演示函数的可重入与不可重入

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

int a = 1;
int b = 1;

int sum(int* a, int* b)
{
    (*a)++;
    sleep(3);
    (*b)++;
    return *a + *b;
}

void sigcb(int signo)
{    
    (void)signo;
    printf("signal---------%d\n", sum(&a, &b));
}

int main()
{
    //当用户输入ctrl + c时,会用sigcb函数替换当前信号的执行
    signal(SIGINT, sigcb);
    printf("main---------%d\n", sum(&a, &b));
    return 0;
}

在这里插入图片描述

在这里插入图片描述

SIGCHLD:17号信号

因为子进程退出后,操作系统通过 SIGCHLD 信号通知父进程,由于 SIGCHLD 信号处理方式默认是忽略,因此父进程不知道子进程何时退出,因此调用 wait 死等,浪费了父进程。

现在有了信号,那么我们就可以自定义 SIGCHLD 信号的处理方式,在信号回调函数中调用 wait 。子进程退出,向父进程发送 SIGCHLD 信号,触发信号回调函数,在函数中执行wait。

因为SIGCHLD是非可靠信号,如果同时有多个进程退出,有可能信号会丢失,导致 sigcb 只会被回调一次,只处理了一个子进程退出;其它退出的子进程成为僵尸进程;因此在一次回调中要将所有的僵尸进程全部处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值