进程间通信方式--信号(signal)

本文详细介绍了Linux信号系统的概念、五种默认执行方式,以及信号处理的不同策略,包括捕获、默认行为和忽略。还探讨了kill、raise、alarm和pause等API的使用,以及自定义信号的定义和发送。

一、信号 signal

1、概念:

(1)信号是进程间通信的方式之一,这种方式不能传输数据,只是在内核中传递一个信号(整数),信号表示一个整数。

(2)不同信号的值所代表的信号不同。

(3)允许用户自定义信号,自定义信号的含义由程序员自行定义,但是信号值不允许和系统默认拥有的信号值一样。

2、五种默认执行方式:
信号默认执行方式描述
Term中止终止进程,通常用于正常终止进程。
Ign忽略忽略信号,进程收到此信号时不做任何动作。
Core中止并输出终止进程,并生成核心转储文件,用于调试和分析进程崩溃时的状态。
Stop停止暂停进程执行,使其停留在当前状态,直到收到 SIGCONT 信号。
Cont继续执行用于恢复因 SIGSTOP 信号而暂停的进程,使其继续执行。

二、信号表

在这里插入图片描述

三、信号处理

1.收到信号的三种处理方式

(1)捕获信号: 通过将一个信号与用户自定义的处理函数关联起来,进程可以捕获并处理信号。当进程收到与已注册的处理函数关联的信号时,操作系统会调用相应的处理函数,允许程序员指定自定义的行为。

(2)默认行为: 对于大多数信号,操作系统定义了默认的行为。这些默认行为通常是终止进程,生成核心转储文件,或暂停进程执行等。当进程收到信号时,如果没有设置信号的自定义处理函数,将会执行操作系统默认的行为。

(3)忽略信号: 进程可以选择忽略某些信号,这意味着进程在收到该信号时不做任何动作。这通常用于对某些不需要处理的信号进行过滤,或者是在特定情况下暂时屏蔽某些信号的影响。

2.信号处理过程
  1. 接收信号
  2. 中断执行
  3. 执行信号处理函数
  4. 恢复执行

上下文切换: 上下文切换属于信号处理过程中的"中断执行"这一步骤。在接收到信号后,操作系统会暂停当前进程的执行,保存当前进程的上下文(包括寄存器状态、堆栈信息等),然后执行信号处理函数。这个过程中就发生了上下文切换,因为操作系统需要从当前进程切换到信号处理函数的上下文。

在这里插入图片描述

关于“上下文,进程上下文和中断上下文概念,上下文切换”的讲解可以参考这位博主的帖子,个人觉得简单易懂。 https://blog.csdn.net/lqy971966/article/details/119103989

四、Linux下信号相关API

1.发送信号 kill raise alarm
(1)kill: 发送一个指定信号给指定的进程
函数原型:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数:
	@pid	指定接收该信号的进程id
		pid > 0		发送指定信号给 pid 对应的那个进程
        pid = 0		发送指定信号给 调用kill 的进程 同组的所有的进程(getpgrp 获取进程组 pid)
        pid = -1	发送指定信号给 所有进程(有权限发送)
        pid < -1	发送指定信号给 进程组pid = abs(pid) 同组的所有的进程
	@sig	指定要发送的信号<参照信号表,尽量使用宏>
返回值:
		成功返回 0
		失败返回 -1 errno被设置。
(2)raise: 发送一个指定的信号给自己<也就是调用处的进程>
函数原型:
#include <signal.h>
int raise(int sig);
参数:
	@sig:	指定要发送的信号<参照信号表,尽量使用宏>
返回值:
	成功返回 0
    失败返回 -1 errno被设置。
(3)alarm: 发送一个时钟信号
函数原型: 
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
参数: 
	@seconds: 多少 秒 后发送一个闹钟信号[SIGALARM]
返回值:
	成功返回 上一个 "闹钟" 的剩余时间,如果没有上一个闹钟,则返回 0
注意:
	①alarm(0) 表示取消所有的 alarm
	②每个进程都有属于自己的一个闹钟,如果设定的时间到了,就会收到SIGALARM,默认执行为Term[中断]
	③同一时刻一个进程只有一个"闹钟",即:在程序中多次调用 alarm() 函数设置定时器,只有最后一次设置的定时器会生效,之前设置的定时器会被新的定时器覆盖。因此,在任何时刻,一个进程只有一个定时器在运行,控制着定时器到期时发送的 SIGALRM 信号。
2.等待信号 pause
pause: 让 进程/线程 休眠,直到等待到一个可以被捕捉的信号
函数原型:
#include <unistd.h>
int pause(void);
参数:
    无参数
返回值:
    失败返回  -1,errno被设置,并且一般情况下错误码为 EINTR,表示收到了一个信号,导致 pause 被中断。
    
注:SIGKILL、SIGTOP这两个信号是不可被捕获的,所以不适用于 pause。
3.自定义信号

(1)定义信号量

(2)定义信号处理函数

(3)注册信号

(4)发送信号

函数原型:
#include <signal.h>
void signal(int signum, void (*handler)(int));
参数:
	@signum: 要设置处理函数的信号编号
	@handler:指向处理函数的指针。可以是一个自定义的函数,也可以是系统提供的预定义处理函数。
返回值:
	无返回值
	
注意:
(1)关于参数函数指针的参数:
调用 signal() 函数将自定义信号 MY_SIGNAL 与信号处理函数 signal_handler 关联起来。当进程收到信号 MY_SIGNAL 时,操作系统会调用 signal_handler 函数,并将收到的信号编号作为参数传递给它。
(2)关于信号的接收调用:
信号处理函数是由操作系统调用的,而不是直接调用的。当进程收到指定的信号时,操作系统会检查信号处理函数,如果已经注册了与该信号对应的处理函数,就会调用这个处理函数,并传递信号编号作为参数。
(3)总结:
信号处理函数是由操作系统来调用的,它能够知道有信号发生是因为操作系统调用了它。当信号处理函数被调用时,它就知道有信号发生了,并且能够通过参数来获取信号的编号,从而知道这个信号是给自己的。

五、代码实现

五、代码演示

1.kill的使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

#include <sys/wait.h>   // wait 头文件
#include <sys/types.h>


int main()
{
    int i = 0;
    if(fork() == 0)
    {
        while(1)
        {
            printf("子进程: 死循环 %d\n",i++);
            if(i == 5)
            {
                printf("子进程: 发送信号\n");
                // 接收这个信号的进程 id, 信号值
                kill(getpid(),SIGKILL);
            }
            sleep(1);
        }
    }
    else{
        printf("父进程: 等待子进程结束\n");
        wait(NULL);
    }
    
    printf("进程结束\n");
    return 0;
}

在这里插入图片描述

2.raise的使用
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main()
{
    int i = 0;
    while(1)
    {
        printf("i = %d\n",i++);
        sleep(1);
        if(i == 5)
        {
            printf("发送 SIGKILL 信号\n");
            raise(SIGKILL);
        }
    }
    printf("over\n");
    return 0;
}

在这里插入图片描述

3.alarm pause 的使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

// 定义信号处理函数
void alarm_handler(int signum) {
    printf("定时器到期,收到 SIGALRM 信号\n");
}

int main() {
    // 注册信号处理函数
    signal(SIGALRM, alarm_handler);

    printf("设置定时器,在 5 秒后发送 SIGALRM 信号\n");

    // 设置定时器,在 5 秒后发送 SIGALRM 信号
    alarm(5);

    // 等待 SIGALRM 信号的到来
    pause();

    printf("程序退出\n");

    return 0;
}

在这里插入图片描述

4.自定义信号的使用
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>

// 定义自定义信号
#define MY_SIGNAL 36
// #define MY_SIGNAL SIGUSR1

// 定义信号处理函数
void signal_handler(int signum)
{
    printf("收到自定义信号!\n");
}


int main()
{
    printf("注册自定义信号\n");

    // 注册信号处理函数
    signal(MY_SIGNAL,signal_handler);

    // 等待信号
    printf("等待一个信号\n");
    sleep(2);

    // 发送自定义信号
    pid_t pid = getpid();
    int k = kill(pid,MY_SIGNAL);
    if(k == -1)
    {
        perror("信号发送失败\n");
        exit(1);
    }
    return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值