【Linux】IPC-信号

在这里插入图片描述


📄前言

信号,一种无论是生活还是编程都离不开的东西。生活中,我们通过信号来对外部发生的事情进行反应,就好像你的手机来了一条信息,你可以选择点开处理它,也可以忽略掉它,而在Linux中的信号也是如此。

信号

概念

信号是Linux进程间通信的一种方式,主要用于进程接受异步事件的通知。你可以把信号看出是一种软件中断(软件触发的中断机制),进程通过接受信号来对响应外部的事情,例如程序异常、子进程结束、终端中按下中断键(Ctrl + C)都是会给进程传递信号。

Linux中信号分为两类: 标准信号与实时信号。

  • 标准信号:linux中的1-32号信号都是标准信号,信号被处理前,只能被存储一次。

  • 实时信号:指的是34号信号以上的信号,他们比标准信号更高级,可以使用队列来存储。

╭─ ~/.lcpr                                                           
╰─❯ kill -l	# 使用kill -l 来查看所有信号
HUP INT QUIT ILL TRAP IOT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STKFLT CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH POLL PWR SYS

# 如果我们想要对某个进程发送信号,也是使用kill
kill 【you_process】 【signal】;

# 当然,我们也有更简易的发送信号的方式
# 使用键盘(Ctrl + C)发送 INT 信号
# 使用(Ctrl + Z) 发送 STOP 信号
# 使用 (Ctrl + \)发送

信号阻塞

概念

当我们程序正在执行某些重要任务的时候,可以会因为用户的任性(Ctrl + C)而随意传来SIGINT信号来结束进程,为了不让程序收到这种事情的影响,linux信号机制设计了两个关键的标志位:阻塞位与未决位(2bit)。

信号工作原理:

  • 信号到达且阻塞位为0,系统则将未决位设为1,并执行信号处理函数,处理完成将未决位重新设置位0
  • 信号到达阻塞位为1,系统则将未决位设为1,信号处理等待状态,直到阻塞被取消,信号被处理。
+---------------------+
|      task_struct    |
+----------+----------+----------------------+
| Signal   | Block    | Pending  | Handler   |
+----------+----------+----------+-----------+
| SIGHUP   |    0     |    0     | SIG_DFL   |
| SIGINT   |    1     |    1     | SIG_IGN   |
| SIGQUIT  |    1     |    0     | handler   | --*
|   ...    |   ...    |   ...    |   ...     |   |
+----------+----------+----------+-----------+   |
                                                 |
                          *----------------------*
                          |
                          v
                      void sighandler(int signo) {
                          ...
                      }

信号阻塞函数的介绍:

// 涉及头文件 <signal.h>

struct sigset_t;	// 信号的集合,用于阻塞、等待、检查信号

int sigemptyset(sigset_t *set);	//置空set
int sigfillset(sigset_t *set);	//初始化set,将所有信号添加到set中

int sigaddset(sigset_t *set, int signum);	//为set增加信号
int sigdelset(sigset_t *set, int signum);	//删除set中的信号

int sigismember(const sigset_t *set, int signum);
// 检查set中是否加入了signum

int sigprocmask(int how, const sigset_t* set, sigset_t* oset);	//用于设置堵塞信号
// HOW 有三种参数,SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK

// SIG_BLOCK:将set指向的信号集中的信号添加到当前阻塞信号集中

// SIG_UNBLOCK:从当前阻塞信号集中移除set指向的信号集中的信号

// SIG_SETMASK:将当前阻塞信号集设置为set指向的信号集的确切副本

int sigpending(sigset_t* set);	
//用于检查当前进程哪些信号是阻塞未处理的,set用于储存这些信号。

阻塞信号函数的简单使用:

void task()
{
    pid_t id = getpid();
    for (int i = 1; i <= 32; ++i)
    {
        if (i == SIGSTOP && i == SIGKILL)	//SIGSTOP 和 SIGKILL 无法被阻塞。
            continue;

        kill(id, i);
        printf("kill %d %d\n", id, i);
        sleep(1);
    }
    exit(0);
}

void test()
{
    sigset_t set;

    // sigemptyset(&set);	//将set信号集设为空
    // for (int i = 1; i <= 32; ++i)		
    //     sigaddset(&set, i);	// 一个个添加信号

    sigfillset(&set);		// 初始化:将所有信号填加
    sigprocmask(SIG_SETMASK, &set, nullptr);	// 将set中的信号全部设为阻塞
    pid_t id = fork();	
    if (id == 0)
        task();

    wait(nullptr);
}

信号的处理

信号的处理方式有三种:默认处理、忽略信号或捕捉信号(自定义处理)。大部分信号的默认成立方式都是终止进程,但程序可以通过捕获信号(自定义信号处理函数)。除此之外,我们也可以将信号设为阻塞状态,这样程序就无法接受到信号。

    +-----------+       +-----------+       +-------------+
    |           |       |           |       |             |
    |  Kernel   |  ---> |  Process  |  ---> |  Signal     |
    | (System)  |       |           |       |  Handler    |
    +-----------+       +-----------+       +-------------+
       ^  |                  |                    |
       |  |                  |                    |
       |  +---- 发送信号 ----->             自定义的处理函数
       |                                          |
       +------- 产生信号(如:Ctrl+C, 异常)---------+

在C语言中,可以使用 signal 函数与 sigaction 函数来设置处理函数。

信号处理函数的介绍:

void(*signal(int sig, void(*func)(int)))(int);
// 看到这个这个函数原型你可能会面露难色。 但其实在里面我们只需要认识两个参数就足够了。
// sig:需要捕获的信号     func:一个函数指针,指向你自定义的捕获函数。
// 而signal的返回值也是和func一个类型的函数指针

int sigaction(int sig, const struct sigaction* act, struct sigaction* oldact);
// sig:需要处理的信号   
// act:指向对信号的处理方式
// oldact:用于保存信号先前的处理方式,可设为NULL


// struct sigaction的结构
struct sigaction {
    void     (*sa_handler)(int);	//信号处理函数指针
    void     (*sa_sigaction)(int, siginfo_t *, void *);	// 另一个处理信号的函数,当sa_flags 设置了”SA_SIGINFO“的时候使用。
    sigset_t   sa_mask;	//在信号处理函数执行时,额外需要阻塞的信号
    int        sa_flags;	// 用于指定信号处理的各种选项与行为
    void     (*sa_restorer)(void);	// 通常不使用
};

信号的使用

void task()
{
    struct sigaction act;
    sigset_t set;
    sigemptyset(&set);	//将set设空
    sigfillset(&act.sa_mask);	//初始化
    sigprocmask(SIG_SETMASK, &act.sa_mask, nullptr);	//sa_mask信号集阻塞

    for (int i = 1; i <= 32; ++i)
    {
        if (i == SIGSTOP || i == SIGKILL)
            continue;
        raise(i);	//使用raise来向本进程发送信号
    }
    sigpending(&set);

    for (int i = 32; i > 0; --i)
    {
        if (sigismember(&set, i) == 0)	//检查信号是否未决
            std::cout << i << std::endl;
    }
    std::cout << std::endl;
}

int main()
{
    signal(SIGCHLD, SIG_IGN);	//子进程结束时发送SIGCHLD信号,将它设为SIG_IGN可免除wait
    pid_t id = fork();

    if (id == 0)
    {
        task();
        return 0;
    }

    return 0;
}

📓总结

本篇文章我们探索从信号的概念到阻塞和处理函数的应用,相信你对linux系统编程又有了一个新的了解吧。

📜博客主页:主页
📫我的专栏:C++
📱我的github:github

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值