Linux多进程和多线程(三)进程间通讯-信号处理方式和自定义处理函数

news2024/11/24 17:51:40
  • 进程间通信之信号
    • 信号
    • 信号的种类
      • 信号在操作系统中的定义如下:
    • 信号的处理流程
    • 在 Linux 中对信号的处理⽅式
      • 自定义信号处理函数
    • 信号的发送
      • kill() 函数:
      • raise() 函数:
    • 示例 : 创建⼀个⼦进程,⼦进程通过信号暂停,⽗进程发送 终⽌信号
    • 等待信号
      • pause() 函数:
  • 信号的处理
    • ⽤户⾃定义处理基本的流程
      • 一. 实现⾃定义处理函数
      • 二.设置信号处理处理⽅式
    • 示例: 创建⼀个⼦进程, ⽗进程给⼦进程发送 SIGUSR1 信号,并使⽤⾃定义的处理函数处理信号

进程间通信之信号

信号

信号是在软件层次上 是⼀种通知机制, 对中断机制的⼀种模拟,是⼀种异步通信⽅式, ⼀般具有
如下特点:

1. 进程在运⾏过程中,随时可能被各种信号打断
2. 进程可以忽略, 或者去调⽤相应的函数去处理信号
3.进程⽆法预测到达的精准时间

在 Linux 中信号⼀般的来源如下

程序执⾏错误,如内存访问越界,数学运算除 0

由其他进程发送

通过控制终端发送 如 ctrl + c

⼦进程结束时向⽗进程发送的 SIGCLD 信号

程序中设定的定时器产⽣的 SIGALRM 信号

信号的种类

在 Linux 系统可以通过 kill -l 命令查看, 常⽤的信号列举如下

在这里插入图片描述

  • SIGINT 该信号在⽤户键⼊ INTR 字符 (通常是 Ctrl-C) 时发出,终端驱动程序发送此
    信号并送到前台进>程中的每⼀个进程。

  • SIGQUIT 该信号和 SIGINT 类似,但由 QUIT 字符 (通常是 Ctrl-) 来控制。

  • SIGILL 该信号在⼀个进程企图执⾏⼀条⾮法指令时 (可执⾏⽂件本身出现错误,或者
    试图执⾏数据段、堆栈溢出时) 发出。

  • SIGFPE 该信号在发⽣致命的算术运算错误时发出。这⾥不仅包括浮点运算错误,还
    包括溢出及除数 > 为 0 等其它所有的算术的错误。

  • SIGKILL 该信号⽤来⽴即结束程序的运⾏,并且不能被阻塞、处理和忽略。

  • SIGALRM 该信号当⼀个定时器到时的时候发出。

  • SIGSTOP 该信号⽤于暂停⼀个进程,且不能被阻塞、处理或忽略。

  • SIGTSTP 该信号⽤于交互停⽌进程,⽤户可键⼊ SUSP 字符时 (通常是 Ctrl-Z) 发出
    这个信号。

  • SIGCHLD ⼦进程改变状态时,⽗进程会收到这个信号

  • SIGABRT 进程异常中⽌

信号在操作系统中的定义如下:

#define SIGHUP       1
#define SIGINT       2
#define SIGQUIT      3
#define SIGILL       4
#define SIGTRAP      5
#define SIGABRT      6
#define SIGIOT       6
#define SIGBUS       7
#define SIGFPE       8
#define SIGKILL      9 
#define SIGUSR1     10 // 用户自定义信号
#define SIGSEGV     11
#define SIGUSR2     12
#define SIGPIPE     13
#define SIGALRM     14
#define SIGTERM     15
#define SIGSTKFLT   16
#define SIGCHLD     17
#define SIGCONT     18
#define SIGSTOP     19
#define SIGTSTP     20
#define SIGTTIN     21

信号的处理流程

  • 信号的发送 :可以由进程直接发送

  • 信号投递与处理 : 由内核进⾏投递给具体的进程并处理

在 Linux 中对信号的处理⽅式

  • 忽略信号, 即对信号不做任何处理,但是有两个信号不能忽略:即 SIGKILL 及
    SIGSTOP。

  • 捕捉信号, 定义信号处理函数,当信号发⽣时,执⾏相应的处理函数。

  • 执⾏缺省操作,Linux 对每种信号都规定了默认操作
    在这里插入图片描述

内核通过task_struct找到相应的进程,然后将信号的类型和进程号传递给信号处理函数。信号处理函数根据信号类型做相应的处理。

在内核中的⽤于管理进程的结构为 task_struct , 具体定义如下:
在这里插入图片描述

任务队列

内核把进程的列表存放在叫做任务队列(task list) 的双向循环链表中。链表中的每一 项都是类型为task_struct

备注:有些操作系统会把任务队列称为任务数组。但是Linux实现时使用的是队列而不是静态数组,所以称为任务队列

https://blog.csdn.net/qq_41453285/article/details/103743235
更多关于task_struct 的信息,请参考《深入理解LINUX内核》

记录进程信号和相应的处理方式
在这里插入图片描述

自定义信号处理函数

这种方式需要在程序中编写信号处理函数,并在程序内核中注册信号处理函数。

信号的发送

当由进程来发送信号时, 则可以调⽤ kill() 函数与 raise () 函数

kill() 函数:

用于向指定进程发送信号

函数头文件:

#include <signal.h>
#include <sys/types.h>

原型如下:

int kill(pid_t pid, int sig);

参数:

pid_t pid: 进程ID
int sig: 信号值

返回值:

- 成功: 0
- 失败: -1  并设置 errno

raise() 函数:

用于向当前进程发送信号

函数头文件:

#include <signal.h>
#include <sys/types.h>

原型如下:

int raise(int sig);

参数:

int sig: 信号值

返回值:

- 成功: 0
- 失败: -1  并设置 errno

示例 : 创建⼀个⼦进程,⼦进程通过信号暂停,⽗进程发送 终⽌信号

/*
 * 创建⼀个⼦进程,⼦进程通过信号暂停,⽗进程发送 终⽌信号
 * */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

int main(){
    pid_t child_pid; // ⼦进程ID  pid_t 类型在<sys/types.h>是⼀个整数类型,用来存储进程ID, 它是系统中⼀个进程的唯一标识符。系统中每个进程都有⼀个独⽴的pid。
    child_pid = fork(); // 创建⼀个⼦进程,⼦进程复制⽗进程的地址空间,并返回⼦进程的pid。
    if (child_pid ==-1){ // 创建失败
        perror("fork");// 输出错误信息
        exit(EXIT_FAILURE);// 退出程序
    } else if (child_pid == 0) { //只在⼦进程运行的代码
        //fprintf和printf的区别在于fprintf可以指定输出到哪个文件,printf默认输出到标准输出。
        //stdout是标准输出,输出到屏幕上,还有stderr是错误输出,输出到屏幕上。stdin是标准输入,输入从键盘上。
        fprintf(stdout, "子进程正在运行...子进程ID:%d\n", getpid());

        raise(SIGSTOP); // 发送SIGSTOP信号给⼦进程自己,暂停⼦进程的运行。

        fprintf(stdout, "子进程暂停自己后被父进程信号kill,不会打印这句话:%d\n", getpid());
        exit(EXIT_SUCCESS); // 退出⼦进程
    } else if (child_pid > 0) { // 父进程运行的代码
        int ret;
        sleep(2); // 父进程休眠2秒,等待⼦进程

        ret = kill(child_pid, SIGKILL);// 发送SIGKILL信号给⼦进程,终⽌⼦进程。
        //SIGKILL信号是强制终⽌进程的信号,它会杀死进程,并释放资源, 但是它不能被捕获和处理。

        if (ret == -1){ // 发送失败
            perror("kill");// 输出错误信息
            exit(EXIT_FAILURE);//退出程序
        } else {
            fprintf(stdout, "父进程终⽌⼦进程成功!\n");
            wait(NULL); // 等待⼦进程结束,防止⼦进程僵死。
            //wait函数传入NULL,表示等待任意⼦进程结束,返回值是⼦进程的终⽌状态。
        }
    }

    return 0;
}

运行结果:

子进程正在运行...子进程ID:3957
父进程终⽌⼦进程成功!

等待信号

在进程没有结束时,进程在任何时间点都可以接受到信号

需要阻塞等待信号时,则可以调⽤ pause() 函数

pause() 函数:

用于进程暂停,直到收到信号

函数头文件:

#include <signal.h>

原型如下:

int pause(void);

参数:

返回值:

- 成功: 0
- 失败: -1  并设置 errno

示例 : 创建创建⼀个⼦进程, ⽗进程调⽤ pause 函数,⼦进程给⽗进程发送信号

int main(){
    pid_t child_pid; // ⼦进程ID  pid_t 类型在<sys/types.h>是⼀个整数类型,用来存储进程ID, 它是系统中⼀个进程的唯一标识符。系统中每个进程都有⼀个独⽴的pid。
    child_pid = fork(); // 创建⼀个⼦进程,⼦进程复制⽗进程的地址空间,并返回⼦进程的pid。
    if (child_pid ==-1){ // 创建失败
        perror("fork");// 输出错误信息
        exit(EXIT_FAILURE);// 退出程序
    } else if (child_pid == 0) { //只在⼦进程运行的代码
        //fprintf和printf的区别在于fprintf可以指定输出到哪个文件,printf默认输出到标准输出。
        //stdout是标准输出,输出到屏幕上,还有stderr是错误输出,输出到屏幕上。stdin是标准输入,输入从键盘上。
        fprintf(stdout, "子进程正在运行...子进程ID:%d\n", getpid());
        sleep(1); // ⼦进程休眠1秒

        kill(getppid(), SIGUSR1); // 发送SIGUSR1信号(用户自定义信号1)给父进程,这个信号默认是结束进程

        fprintf(stdout, "发送SIGUSR1信号(用户自定义信号1)给父进程:%d\n", getpid());
        exit(EXIT_SUCCESS); // 退出⼦进程
    } else if (child_pid > 0) { // 父进程运行的代码

        fprintf(stdout, "父进程...父进程ID:%d\n", getpid());
        pause(); // 父进程阻塞,等待信号

        fprintf(stdout, "父进程...父进程收到信号");

        wait(NULL);
    }

    return 0;
}

运行结果:

父进程...父进程ID:4782
子进程正在运行...子进程ID:4783
发送SIGUSR1信号(用户自定义信号1)给父进程:4783

pause 函数⼀定要在收到信号之前调⽤,让进程进⼊到睡眠状态

信号的处理

信号是由操作系统内核发送给指定进程, 进程收到信号后则需要进⾏处理

处理信号三种⽅式:

  • 忽略 : 不进⾏处理
  • 默认 : 按照信号的默认⽅式处理
  • ⽤户⾃定义 : 通过⽤户实现⾃定义处理函数来处理,由内核来进⾏调⽤

三种方式都是内核来处理.
⾃定义处理函数:需要将信号处理函数地址注册到内核中, 并在信号发⽣时, 由内核调用相应的处理函数。


对于每种信号都有相应的默认处理⽅式

进程退出:

SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,

SIGUSR1,SIGUSR2,SIGVTALRM

进程忽略

SIGCHLD,SIGPWR,SIGURG,SIGWINCH

进程暂停

SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU


⽤户⾃定义处理基本的流程

一. 实现⾃定义处理函数

⽤户实现⾃定义处理函数, 需要按照下⾯的形式定义

typedef void (*sighandler_t)(int);

typedef void (*)(int) sighandler_t
//sighandler_t 是信号处理函数的类型, 它是一个函数指针, 指向信号处理函数的起始地址。

二.设置信号处理处理⽅式

通过 signal 函数设置信号处理⽅式

函数头⽂件

#include <signal.h>

函数原型

sighandler_t signal(int signum, sighandler_t handler);

//sighandler_t 是信号处理函数的类型, 它是一个函数指针, 指向信号处理函数的起始地址。

函数功能

设置信号的处理⽅式, 如果是⾃定义处理⽅式,提供函数地址,注册到内核中

函数参数

signum : 信号编号 

handler : 信号处理⽅式
    - SIG_IGN (1): 忽略信号//信号处理函数不做任何事情
    - SIG_DFL (0): 按照默认⽅式处理//信号处理函数是系统默认的处理函数
    - 其他 : 自定义处理函数的地址//信号处理函数是⾃定义的处理函数

三种处理⽅式互斥,一般选择一种即可。

返回值

成功 : 信号处理函数的地址

失败 : 返回 SIG_ERR (-1) 并设置 errno

在这里插入图片描述

示例: 创建⼀个⼦进程, ⽗进程给⼦进程发送 SIGUSR1 信号,并使⽤⾃定义的处理函数处理信号

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
/*
 * 创建⼀个⼦进程, ⽗进程给⼦进程发送 SIGUSR1 信号,并使⽤⾃定义的处理函数处理信号
 *
 * */

//信号处理函数,通过signal函数关联对应的信号
//@param  sign 当前接受到的信号(与这个处理函数相关联的)
void sig_handler(int sign);

int main(int argc, char *argv[]) {
    __sighandler_t ret;//信号处理函数的返回值
    ret= signal(SIGUSR1, sig_handler);//关联信号处理函数
    if(ret==SIG_ERR){//出错处理
        perror("signal");//出错处理
        exit(1);//退出程序
    }
    //成功返回的信号处理函数指针

    //创建⼀个⼦进程
    pid_t pid=fork();
    if(pid==-1){//出错处理
        perror("fork");//出错处理
        exit(1);//退出程序
    }else if(pid==0){//⼦进程
        printf("⼦进程开始\n");
        //使⽤⾃定义的处理函数处理信号
        pause();
        //函数处理后回到子进程,继续执行
        printf("⼦进程结束\n");

    }else{//⽗进程
        sleep(1);//等待⼦进程启动
        printf("⽗进程发送信号\n");
        //给⼦进程发送 SIGUSR1 信号
        //信号投递是由内核完成,通过task_struct找到对应的进程,再去调用信号处理函数
        kill(pid, SIGUSR1);
        //等待⼦进程结束
        wait(NULL);
    }
    return 0;
}
//信号处理函数
void sig_handler(int sign){
    //处理信号
    printf("信号处理函数运行 %s\n", strsignal(sign));//strsignal函数将信号转换为字符串,返回一个字符串,描述信号编号的含义
}

运行结果:

⼦进程开始
⽗进程发送信号
信号处理函数运行 User defined signal 1

eep(1);//等待⼦进程启动
printf(“⽗进程发送信号\n”);
//给⼦进程发送 SIGUSR1 信号
//信号投递是由内核完成,通过task_struct找到对应的进程,再去调用信号处理函数
kill(pid, SIGUSR1);
//等待⼦进程结束
wait(NULL);
}
return 0;
}
//信号处理函数
void sig_handler(int sign){
//处理信号
printf(“信号处理函数运行 %s\n”, strsignal(sign));//strsignal函数将信号转换为字符串,返回一个字符串,描述信号编号的含义
}


运行结果:

⼦进程开始
⽗进程发送信号
信号处理函数运行 User defined signal 1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1879563.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql解压版本安装5.7

1. 官网下载好解压版本 我这边5.7版本 https://dev.mysql.com/downloads/file/?id523570 mysql官网 创建 my.ini文件 内容如下 [client] #客户端设置&#xff0c;即客户端默认的连接参数# socket /data/mysqldata/3306/mysql.sock #用于本地连接的socket套接字 # 默…

Nginx和CDN运用

一.Web缓存代理 1.工作机制 代替客户机向网站请求数据&#xff0c;从而可以隐藏用户的真实IP地址。将获得的网页数据&#xff08;静态Web元素&#xff09;保存到缓存中并发送给客户机&#xff0c;以便下次请求相同的数据时快速响应。 2.代理服务器的概念 代理服务器是一个位…

YOLOv8目标检测在RK3588部署全过程

一&#xff0c;前言 这是一个关于从电脑安装深度学习环境到实现YOLOv8目标检测在RK3588上部署的全过程。 本人配置&#xff1a; 1&#xff0c;一台笔记本 2&#xff0c;一个香橙派5s 二&#xff0c;深度学习环境配置 2.1 安装anaconda 使用清华镜像源下载https://mirror…

内容营销专家刘鑫炜:说真的,我写文章可不是为了流量!

今天&#xff0c;打开某号后台&#xff0c;发现我的号一夜之间加了几十个粉丝&#xff0c;确实有些意外。 说真的&#xff0c;我写文章的目的真不是为了流量。 在这个被大数据和算法统治的时代&#xff0c;我们常常听到这样的声音&#xff1a;“流量就是一切”。然而&#xff…

对原生input加上:当前输入字数/最大输入字数

源码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>/* 样式…

【解释】i.MX6ULL_IO_电气属性说明

【解释】i.MX6ULL_IO_电气属性说明 文章目录 1 Hyst1.1 迟滞&#xff08;Hysteresis&#xff09;是什么&#xff1f;1.2 GPIO的Hyst. Enable Field 参数1.3 应用场景 2 Pull / Keep Select Field2.1 PUE_0_Keeper — Keeper2.2 PUE_1_Pull — Pull2.3 选择Keeper还是Pull 3 Dr…

深圳制作网站价格怎么样

深圳是中国的经济特区&#xff0c;也是中国最具活力和创新情商的城市之一。随着互联网的普及&#xff0c;越来越多的企业和个人开始意识到拥有一个优质的网站对于提升品牌形象、销售产品和服务、吸引客户等方面的重要性。因此&#xff0c;制作网站成为了一项必不可少的工作。 深…

Linux常见操作问题

1、登录刚创建的用户&#xff0c;无法操作。 注&#xff1a;etc/passwd文件是Linux操作系统中存储用户账户信息的文本文件&#xff0c;包含了系统中所有用户的基本信息&#xff0c;比如用户名、用户ID、用户组ID、用户家目录路径。 注&#xff1a;etc: 这个目录存放所有的系统…

网络----头部

TCP IP UDP udp头部 帧 http 连接 MSS和MTU 图片来源

99 - Apifox使用

子曰&#xff1a;“学而时习之&#xff0c;不亦说乎&#xff1f;有朋自远方来&#xff0c;不亦乐乎&#xff1f;人不知而不愠&#xff0c;不亦君子乎?” 1. 基本介绍 集API 文档、API 调试、API Mock、API 自动化测试于一体。 Apifox Postman Swagger Mock JMeter。 官…

同元软控受邀出席2024年工业软件与新质生产力创新发展论坛

近日&#xff0c;由广东省工业软件学会主办的“2024年工业软件与新质生产力创新发展论坛”在广州成功举办。同元软控深圳子公司副总经理周胜受邀出席&#xff0c;并作《数智驱动创新&#xff0c;科学计算与系统建模仿真加速新质生产力进化》主题演讲。 本次论坛集结工业软件界…

【C++】using namespace std 到底什么意思

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文作为 JohnKi 的学习笔记&#xff0c;引用了部分大佬的案例 &#x1f4e2;未来很长&a…

【python】python企业财务能力数据分析可视化(源码+报告+数据集)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

【windows】电脑如何关闭Bitlocker硬盘锁

如果你的硬盘显示这样的一把锁&#xff0c;说明开启了Bitlocker硬盘加密。 Bitlocker硬盘锁&#xff0c;可以保护硬盘被盗&#xff0c;加密防止打开查看数据。 方法一&#xff1a;进入“控制面板->BitLocker 驱动器加密”进行设置。或者“控制面板\系统和安全->BitLocke…

x-api-eid-token参数分析与加密算法还原

文章目录 1. 写在前面2. 接口分析3. 算法实现 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python…

面试框架一些小结

springcloud的⼯作原理 springcloud由以下⼏个核⼼组件构成&#xff1a; Eureka&#xff1a;各个服务启动时&#xff0c;Eureka Client都会将服务注册到Eureka Server&#xff0c;并且Eureka Client还可以反过来从Eureka Server拉取注册表&#xff0c; 从⽽知道其他服务在哪⾥ …

【嵌入式DIY实例】-LCD ST7735显示网络时间

LCD ST7735显示网络时间 文章目录 LCD ST7735显示网络时间1、硬件准备2、代码实现本文将介绍如何使用 ESP8266 NodeMCU Wi-Fi 板实现互联网时钟,其中时间和日期显示在 ST7735 TFT 显示屏上。 ST7735 TFT是一款分辨率为128160像素的彩色显示屏,采用SPI协议与主控设备通信。 1…

管理上的一些思考

1 前言 管理可分为自我管理、平级管理、向下管理和向上管理。 顾名思义&#xff0c;自我管理就是对自己工作、生活等各方面的规划和执行&#xff0c;不涉及与其他人互动、配合等。我们设定人生目标、年度计划、月计划等&#xff0c;都可以认为是自我管理。《增广贤文》有段很…

数据结构与算法笔记:实战篇 - 剖析Redis常用数据类型对应的数据结构

概述 从本章开始&#xff0c;就进入实战篇的部分。这部分主要通过一些开源醒目、经典系统&#xff0c;真枪实弹地教你&#xff0c;如何将数据结构和算法应用到项目中。所以这部分的内容&#xff0c;更多的是知识点的回顾&#xff0c;相对于基础篇和高级篇&#xff0c;其实这部…

找不到mfc100.dll文件怎么办?推荐这7个解决方法快速解决mfc100.dll丢失问题

使用电脑中&#xff0c;会遇到各种各样的问题&#xff0c;比如找不到mfc100.dll&#xff0c;或mfc100.dll丢失导致软件程序无法继续运行&#xff0c;就是日常中比较常见的问题之一&#xff0c;今天我教大家遇到这个mfc100.dll丢失问题时候&#xff0c;要怎么解决&#xff0c;以…