进程间通信——信号原理及详解(附有案例代码)

简介: 进程间通信——信号原理及详解(附有案例代码)

1、信号概念


(1)信号是 Linux进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断.它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。


(2)发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下:


       对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入Ctrl+C通常会给进程发送一个中断信号。

       硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一条异常的机器语言指令,诸如被О除,或者引用了无法访问的内存区域。

       系统状态变化,比如 alarm定时器到期将引起SIGALRM信号,进程执行的CPU时间超限,或者该进程的某个子进程退出。

       运行kill 命令或调用kill 函数。


2、信号特点


(1)使用信号的两个主要目的是:


       让进程知道已经发生了一个特定的事情。


       强迫进程执行它自己代码中的信号处理程序。


(2)信号的特点:


       简单;


       不能携带大量信息;


       满足某个特定条件才发送优先级比较高;


(3)查看系统定义的信号列表:kill -l


(4) 前31 个信号为常规信号,其余为实时信号。


3、信号的物种默认处理动作


(1)查看信号的详细信息:man 7 signal


(2)信号的5 中默认处理动作

       Term        终止进程

       Ign           当前进程忽略掉这个信号

       core         终止进程,并生成一个core文件

       stop         暂停当前进程

       cont         继续执行当前被暂停的进程


(3)信号的几种状态:产生、未决、递达


(4)SIGKILL和SIGSTOP信号不能被捕捉、阻塞或者忽略,只能执行默认动作。


4、信号相关函数


int kill(pid_t pid,int sig);
int raise (int sig);
void abort (void);
unsigned int alarrm (unsigned int seconds);
int setitimer(int which,const struct itimerval *new_val, struct itimerval *old_value);


5、kill 函数详解及案例


(1)kill 函数详解

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
    - 功能:给任何的进程或者进程组pid, 发送任何的信号 sig
    - 参数:
        - pid :
            > 0 : 将信号发送给指定的进程
            = 0 : 将信号发送给当前的进程组
            = -1 : 将信号发送给每一个有权限接收这个信号的进程
            < -1 : 这个pid=某个进程组的ID取反 (-12345)
        - sig : 需要发送的信号的编号或者是宏值,0表示不发送任何信号
    kill(getppid(), 9);
    kill(getpid(), 9);
int raise(int sig);
    - 功能:给当前进程发送信号
    - 参数:
        - sig : 要发送的信号
    - 返回值:
        - 成功 0
        - 失败 非0
kill(getpid(), sig);   
void abort(void);
    - 功能: 发送SIGABRT信号给当前的进程,杀死当前进程
    kill(getpid(), SIGABRT);


(2)kill 函数案例

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main() {
    pid_t pid = fork();
    if(pid == 0) {
        // 子进程
        int i = 0;
        for(i = 0; i < 5; i++) {
            printf("child process\n");
            sleep(1);
        }
    } else if(pid > 0) {
        // 父进程
        printf("parent process\n");
        sleep(2);
        printf("kill child process now\n");
        kill(pid, SIGINT);
    }
    return 0;
}


6、alarm 函数详解及案例


(1) alarm 函数详解

#include <unistd.h>
unsigned int alarm(unsigned int seconds);
    - 功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候,
           函数会给当前的进程发送一个信号:SIGALARM
    - 参数:
        seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。
                取消一个定时器,通过alarm(0)。
    - 返回值:
        - 之前没有定时器,返回0
        - 之前有定时器,返回之前的定时器剩余的时间
- SIGALARM :默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。
    alarm(10);  -> 返回0
    过了1秒
    alarm(5);   -> 返回9
alarm(100) -> 该函数是不阻塞的


(2) alarm 函数案例

#include <stdio.h>
#include <unistd.h>
int main() {
    int seconds = alarm(5);
    printf("seconds = %d\n", seconds);  // 0
    sleep(2);
    seconds = alarm(2);    // 不阻塞
    printf("seconds = %d\n", seconds);  // 3
    while(1) {
    }
    return 0;
}


7、setitimer 函数详解及案例


(1)setitimer 函数详解

#include <sys/time.h>
int setitimer(int which, const struct itimerval *new_value,
                    struct itimerval *old_value);
    - 功能:设置定时器(闹钟)。可以替代alarm函数。精度微妙us,可以实现周期性定时
    - 参数:
        - which : 定时器以什么时间计时
            ITIMER_REAL: 真实时间,时间到达,发送 SIGALRM   常用
            ITIMER_VIRTUAL: 用户时间,时间到达,发送 SIGVTALRM
            ITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送 SIGPROF
        - new_value: 设置定时器的属性
            struct itimerval {      // 定时器的结构体
            struct timeval it_interval;  // 每个阶段的时间,间隔时间
            struct timeval it_value;     // 延迟多长时间执行定时器
            };
            struct timeval {        // 时间的结构体
                time_t      tv_sec;     //  秒数     
                suseconds_t tv_usec;    //  微秒    
            };
        过10秒后,每个2秒定时一次
        - old_value :记录上一次的定时的时间参数,一般不使用,指定NULL
    - 返回值:
        成功 0
        失败 -1 并设置错误号


(2)setitimer 函数案例

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
// 过3秒以后,每隔2秒钟定时一次
int main() {
    struct itimerval new_value;
    // 设置间隔的时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;
    // 设置延迟的时间,3秒之后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;
    int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
    printf("定时器开始了...\n");
    if(ret == -1) {
        perror("setitimer");
        exit(0);
    }
    getchar();
    return 0;
}


8、信号捕捉函数


(1)信号捕捉函数

sighandler_t signal(int signum,sighandler_t handler);
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact) ;


(2)信号捕捉函数详解

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
    - 功能:设置某个信号的捕捉行为
    - 参数:
        - signum: 要捕捉的信号
        - handler: 捕捉到信号要如何处理
            - SIG_IGN : 忽略信号
            - SIG_DFL : 使用信号默认的行为
            - 回调函数 :  这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。
            回调函数:
                - 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义
                - 不是程序员调用,而是当信号产生,由内核调用
                - 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。
    - 返回值:
        成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL
        失败,返回SIG_ERR,设置错误号
SIGKILL SIGSTOP不能被捕捉,不能被忽略。


(3)信号捕捉函数案例

#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
//回调函数,可以实现自己想要执行的内容
void myalarm(int num) {
    printf("捕捉到了信号的编号是:%d\n", num);
    printf("xxxxxxx\n");
}
// 过3秒以后,每隔2秒钟定时一次
int main() {
    // 注册信号捕捉
    // signal(SIGALRM, SIG_IGN);
    // signal(SIGALRM, SIG_DFL);
    // void (*sighandler_t)(int); 函数指针,int类型的参数表示捕捉到的信号的值。
    signal(SIGALRM, myalarm);//回调函数要在信号之前定义
    struct itimerval new_value;
    // 设置间隔的时间
    new_value.it_interval.tv_sec = 2;
    new_value.it_interval.tv_usec = 0;
    // 设置延迟的时间,3秒之后开始第一次定时
    new_value.it_value.tv_sec = 3;
    new_value.it_value.tv_usec = 0;
    int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的
    printf("定时器开始了...\n");
    if(ret == -1) {
        perror("setitimer");
        exit(0);
    }
    getchar();
    return 0;
}


9、信号集及相关函数


(1)信号集

2efc8c6ed17c43f9b22414f4c6d5473f.png


1.用户通过键盘  Ctrl + C, 产生2号信号SIGINT (信号被创建)
2.信号产生但是没有被处理 (未决)
    - 在内核中将所有的没有被处理的信号存储在一个集合中 (未决信号集)
    - SIGINT信号状态被存储在第二个标志位上
        - 这个标志位的值为0, 说明信号不是未决状态
        - 这个标志位的值为1, 说明信号处于未决状态
3.这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较
    - 阻塞信号集默认不阻塞任何的信号,标志位为0
    - 如果想要阻塞某些信号需要用户调用系统的API
4.在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了
    - 如果没有阻塞,这个信号就被处理
    - 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理


(2)相关函数

以下信号集相关的函数都是对自定义的信号集进行操作。
int sigemptyset(sigset_t *set);
    - 功能:清空信号集中的数据,将信号集中的所有的标志位置为0
    - 参数:set,传出参数,需要操作的信号集
    - 返回值:成功返回0, 失败返回-1
int sigfillset(sigset_t *set);
    - 功能:将信号集中的所有的标志位置为1
    - 参数:set,传出参数,需要操作的信号集
    - 返回值:成功返回0, 失败返回-1
int sigaddset(sigset_t *set, int signum);
    - 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号
    - 参数:
        - set:传出参数,需要操作的信号集
        - signum:需要设置阻塞的那个信号
    - 返回值:成功返回0, 失败返回-1
int sigdelset(sigset_t *set, int signum);
    - 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号
    - 参数:
        - set:传出参数,需要操作的信号集
        - signum:需要设置不阻塞的那个信号
    - 返回值:成功返回0, 失败返回-1
int sigismember(const sigset_t *set, int signum);
    - 功能:判断某个信号是否阻塞,判断信号signum是否在信号集set中
    - 参数:
        - set:需要操作的信号集
        - signum:需要判断的那个信号
    - 返回值:
        1 : signum被阻塞
        0 : signum不阻塞
        -1 : 失败


(3)信号集相关案例

#include <signal.h>
#include <stdio.h>
int main() {
    // 创建一个信号集
    sigset_t set;
    // 清空信号集的内容
    sigemptyset(&set);
    // 判断 SIGINT 是否在信号集 set 里
    int ret = sigismember(&set, SIGINT);
    if(ret == 0) {
        printf("SIGINT 不阻塞\n");
    } else if(ret == 1) {
        printf("SIGINT 阻塞\n");
    }
    // 添加几个信号到信号集中
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);
    // 判断SIGINT是否在信号集中
    ret = sigismember(&set, SIGINT);
    if(ret == 0) {
        printf("SIGINT 不阻塞\n");
    } else if(ret == 1) {
        printf("SIGINT 阻塞\n");
    }
    // 判断SIGQUIT是否在信号集中
    ret = sigismember(&set, SIGQUIT);
    if(ret == 0) {
        printf("SIGQUIT 不阻塞\n");
    } else if(ret == 1) {
        printf("SIGQUIT 阻塞\n");
    }
    // 从信号集中删除一个信号
    sigdelset(&set, SIGQUIT);
    // 判断SIGQUIT是否在信号集中
    ret = sigismember(&set, SIGQUIT);
    if(ret == 0) {
        printf("SIGQUIT 不阻塞\n");
    } else if(ret == 1) {
        printf("SIGQUIT 阻塞\n");
    }
    return 0;
}


相关文章
|
3月前
|
存储 Linux Docker
CentOS 7.6安装Docker实战案例及存储引擎和服务进程简介
关于如何在CentOS 7.6上安装Docker、介绍Docker存储引擎以及服务进程关系的实战案例。
190 3
CentOS 7.6安装Docker实战案例及存储引擎和服务进程简介
|
4月前
|
人工智能 PyTorch 算法框架/工具
Xinference实战指南:全面解析LLM大模型部署流程,携手Dify打造高效AI应用实践案例,加速AI项目落地进程
【8月更文挑战第6天】Xinference实战指南:全面解析LLM大模型部署流程,携手Dify打造高效AI应用实践案例,加速AI项目落地进程
Xinference实战指南:全面解析LLM大模型部署流程,携手Dify打造高效AI应用实践案例,加速AI项目落地进程
|
3月前
|
算法 调度 UED
操作系统中的进程管理:原理与实践
在数字世界的心脏跳动着无数进程,它们如同细胞一般构成了操作系统的生命体。本文将深入探讨进程管理的奥秘,从进程的诞生到成长,再到最终的消亡,揭示操作系统如何协调这些看似杂乱无章却又井然有序的活动。通过浅显易懂的语言和直观的比喻,我们将一起探索进程调度的策略、同步机制的重要性以及死锁问题的解决之道。准备好跟随我们的脚步,一起走进操作系统的微观世界,解锁进程管理的秘密吧!
76 6
|
3月前
|
Linux C语言
C语言 多进程编程(四)定时器信号和子进程退出信号
本文详细介绍了Linux系统中的定时器信号及其相关函数。首先,文章解释了`SIGALRM`信号的作用及应用场景,包括计时器、超时重试和定时任务等。接着介绍了`alarm()`函数,展示了如何设置定时器以及其局限性。随后探讨了`setitimer()`函数,比较了它与`alarm()`的不同之处,包括定时器类型、精度和支持的定时器数量等方面。最后,文章讲解了子进程退出时如何利用`SIGCHLD`信号,提供了示例代码展示如何处理子进程退出信号,避免僵尸进程问题。
|
3月前
|
NoSQL
gdb中获取进程收到的最近一个信号的信息
gdb中获取进程收到的最近一个信号的信息
|
4月前
|
Java Windows
【Azure Developer】Windows中通过pslist命令查看到Java进程和线程信息,但为什么和代码中打印出来的进程号不一致呢?
【Azure Developer】Windows中通过pslist命令查看到Java进程和线程信息,但为什么和代码中打印出来的进程号不一致呢?
|
4月前
|
Linux API C语言
Linux源码阅读笔记02-进程原理及系统调用
Linux源码阅读笔记02-进程原理及系统调用
|
4月前
|
网络协议 安全 Unix
从孤岛到大陆:Python进程间通信,让你的代码世界不再有隔阂
【8月更文挑战第1天】在编程领域,Python进程曾像孤岛般各自运行于独立内存中。随项目复杂度增长,进程协同变得重要。Python提供了多种机制搭建这些孤岛间的桥梁。本文介绍四种常见进程间通信(IPC)方式:管道(Pipes)、队列(Queues)、共享内存(Shared Memory)及套接字(Sockets),并附示例代码展示如何实现信息自由流通,使进程紧密相连,共建复杂程序世界。
34 2
|
5月前
|
弹性计算 DataWorks 关系型数据库
DataWorks操作报错合集之DataX在执行过程中接收到了意外的信号15,导致进程被终止,该怎么处理
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
|
4月前
|
消息中间件 存储 网络协议
从零开始掌握进程间通信:管道、信号、消息队列、共享内存大揭秘
在操作系统中,进程间通信(IPC)是至关重要的,它提供了多种机制来实现不同进程间的数据交换和同步。本篇文章将详细介绍几种常见的IPC方式,包括管道、信号、消息队列、共享内存、信号量和套接字,帮助你深入理解并合理应用这些通信方式,提高系统性能与可靠性。
458 0