【Linux C | 进程】进程间通信 | 信号 (带C语言例子,8352字详细讲解)_linux c进程间通信-程序员宅基地

技术标签: Linux系统编程  signal函数  进程间通信  linux  kill函数  sigprocmask函数  信号  

博客主页:https://blog.csdn.net/wkd_007
博客内容:嵌入式开发、Linux、C语言、C++、数据结构、音视频
本文内容:介绍进程间通信的信号
金句分享:你不能选择最好的,但最好的会来选择你——泰戈尔
发布时间:2024-01-24 09:17:56
复习时间:2024-02-01 17:00:12

本文未经允许,不得转发!!!


在这里插入图片描述

一、信号概述

信号是软件中断,是异步通信的进程间通信机制,是在软件层对中断机制的模拟。中断就是让程序停止当前正在执行的代码,转而执行其他的代码的过程。软件中断就是用软件的方式产生中断。

什么是信号?
信号本质是一个数字,编程时一般不直接该信号对应的数字,而是使用该信号对应的宏。宏名称一般以SIG开头,使用宏可以避免不同系统对信号编码的差异。在linux系统中,1 ~ 31是不可靠信号,是早期的信号,不支持排队,因此可能丢失。 34 ~ 64是可靠信号,支持排队,不可能丢失。

信号是如何产生的?

  • 键盘发送(部分信号)
    ctrl + c 2
    ctrl + \ 3
  • 程序出错 (部分信号)
    段错误
    总线错误
    除0
  • kill命令(所有信号)
  • 系统函数 (所有信号)
    kill()

信号如何处理

  • 1、默认处理,系统提供的, 多半是退出进程
  • 2、忽略信号,信号不做处理
  • 3、自定义处理函数,信号按程序员的意图执行相关代码

另外, 信号9(SIGKILL) 不能忽略,也不能自定义处理函数。

在Linux的shell终端, 执行kill-l, 可以看到所有的信号:

$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

在这里插入图片描述

二、信号处理:signal 函数

函数原型:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

Linux系统man手册说,signal的行为因UNIX版本而异,历史上也因Linux的不同版本而异。避免使用它:改为使用sigaction函数。

signal将信号signum的处理设置为handlerhandler可以是SIG_IGNSIG_DFL自定义的函数(“信号处理程序”)的地址。

如果signal函数设置的信号signum被传递到进程,则会发生以下情况之一:

  • 如果配置设置为SIG_IGN,则忽略该信号。
  • 如果配置设置为SIG_DFL,则发生与该信号相关联的默认动作。
  • 如果将handler设置为函数,则调用该函数。

不能捕获或忽略信号SIGKILLSIGSTOP

看例子:

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

void fa(int signo){
    //系统会把信号值传过来
  printf("捕获了信号%d\n",signo);
}

int main()
{
    
	printf("pid=%d\n",getpid());
    signal(SIGINT, fa);//注册一个信号处理函数  ctrl + c
    signal(SIGQUIT, fa);//注册一个信号处理函数 ctrl + \
    //9 既不能忽略也不能改变默认处理方式,下面语句无效
    signal(SIGKILL, fa);
	while(1) // while 等待,按 ctrl + z,退出
		sleep(1);
	return 0;
}

运行结果,即使按ctrl+c(SIGINT)也无法退出进程,按ctrl + \发送信号SIGQUIT,只能按ctrl+zkill -9 pid退出。
在这里插入图片描述

在这里插入图片描述

三、信号发送:kill、raise、alarm 函数

在命令行中,可以使用kill命令给进程发送信号,例如:kill -9 2845就是给进程ID为2845的进程发送信号9。

在编程的情况下,可以使用 kill、raise、alarm 函数来发送信号。

3.1 kill 函数

函数原型:

#include <signal.h>
int kill(pid_t pid, int sig);
// 成功返回0,失败返回-1,errno会被设置

kill 函数将信号发送给进程或进程组。

kill的pid参数有4种不同的情况:

  • 1、pid>0:将该信号发送给进程ID为pid的进程。
  • 2、pid==0:将该信号发送给与发送进程属于同一进程组的所有进程(这些进程的进程组ID等于发送进程的进程组ID),而且发送进程具有向这些进程发送信号的权限。注意,这里用的术语“所有进程”不包括实现定义的系统进程集。对于大多数UNIX系统,系统进程集包括内核进程以及init(pid 1)。
  • 3、pid <0:将该信号发送给其进程组ID等于pid的绝对值,而且发送进程具有向其发送信号的权限。如上所述,“所有进程集”并不包括某些系统进程。
  • 4、pid==-1:将该信号发送给发送进程有权限向它们发送信号的系统上的所有进程。如上所述,“进程集”不包括某些系统进程。

看例子:

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

void fa(int signo){
    //系统会把信号值传过来
  printf("进程[%d]捕获了信号%d\n",getpid(),signo);
}

int main()
{
    
	pid_t pid = fork();
	if(pid==0)//子进程
	{
    
		sleep(2);
		printf("子进程[%d]运行,给父进程发送信号2\n",getpid());
		kill(getppid(),SIGINT);
		printf("子进程[%d]终止\n",getpid());
		return 0;
	}
	else// 父进程
	{
    
		printf("父进程[%d]运行,注册一个信号处理函数\n",getpid());
		signal(SIGINT,fa);// 注册一个信号处理函数
		while(1); // 死循环,按 ctrl+z 终止进程
	}
	return 0;
}

运行结果:
在这里插入图片描述

3.2 raise 函数

函数原型:

#include <signal.h>
int raise(int sig);
// 成功返回0,失败返回非0

raise 函数的作用是向调用进程或线程发送一个信号。在单线程程序中,它等效于

kill(getpid(), sig);

在多线程程序中,它等效于

pthread_kill(pthread_self(), sig);

如果信号导致调用处理程序,则只有在信号处理程序返回后,raise才会返回。

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

void fa(int signo){
    //系统会把信号值传过来
  printf("进程[%d]捕获了信号%d\n",getpid(),signo);
}

int main()
{
    
	printf("进程[%d]运行,注册一个信号处理函数\n",getpid());
	signal(SIGINT,fa);// 注册一个信号处理函数
	printf("进程[%d]睡眠3秒\n",getpid());
	sleep(3);
	printf("进程[%d]给自己发送 SIGINT\n",getpid());
	raise(SIGINT);
	printf("raise 返回\n");
	return 0;
}

运行结果:
在这里插入图片描述

3.3 alarm 函数

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

使用alarm函数可以设置在seconds秒内给调用进程发送SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止调用该alarm函数的进程。

返回值:0 或之前调用的alarm函数的剩余秒数。

每个进程只能调用一个闹钟时钟。如果在调用alarm`时,以前已为该进程设置过闹钟时钟,而且它还没有超时,则将该闹钟时钟的余留值作为本次alarm函数调用的值返回。以前登记的闹钟时钟则被新值代替。

看例子:

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

void fa(int signo){
    //系统会把信号值传过来
  printf("进程[%d]捕获了信号%d\n",getpid(),signo);
}

int main()
{
    
	printf("进程[%d]运行,注册一个信号处理函数\n",getpid());
	signal(SIGALRM,fa);// 注册一个信号处理函数
	printf("进程[%d]设置时钟闹钟3秒\n",getpid());
	alarm(3);
	printf("进程[%d]调用完alarm, while 等待\n",getpid());
	while(1);
	return 0;
}

运行结果:
在这里插入图片描述
如果将signal(SIGALRM,fa); 注释掉,则运行结果如下,进程终止:
在这里插入图片描述

在这里插入图片描述

四、信号屏蔽:sigprocmask 函数

4.1 信号集

信号集:就是多个信号的意思。为此,我们需要一个能够表示多个信号的数据类型。POSIX.1定义了数据类型sigset_t以包含一个信号集,并且定义了下列五个处理信号集的函数。

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
// 前面4个函数成功返回 0, 失败返回 -1

int sigismember(const sigset_t *set, int signum);
// sigismember 结果为真返回 1,结果为假返回 0,出错返回 -1。
  • sigemptyset初始化由set指向的信号集,清除其中所有信号。
  • sigfillset初始化由set指向的信号集,使其包括所有信号。
  • sigaddset将一个信号添加到现有集中.
  • sigdelset则从信号集中删除一个信号。

注意:所有应用程序在使用信号集前,要对该信号集调用sigemptysetsigfillset一次。这是因为C编译器将把未赋初值的外部和静态变量都初始化为0,而这是否与给定系统上信号集的实现相对应却并不清楚。

看例子:

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

int main(){
    
	sigset_t set;
	printf("size=%ld\n",sizeof(set));

    sigfillset(&set);//置1
    sigemptyset(&set); //清0

    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);
    sigdelset(&set, SIGQUIT);
    if(sigismember(&set, SIGINT))
    {
    
        printf("SIGINT is here\n");
    }
    else
    {
    
        printf("SIGINT is not here\n");
    }
    if(sigismember(&set, SIGQUIT))
    {
    
        printf("SIGQUIT is here\n");
    }
    else
    {
    
        printf("SIGQUIT is not here\n");
    }
    return 0;
}

运行结果:
在这里插入图片描述

4.2 sigprocmask 函数

函数原型

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
// 成功返回 0, 出错返回 -1.

进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。sigprocmask用于获取或更改调用进程的信号屏蔽字,或同时获取、更改。

首先,若oldset是非空指针,那么进程的当前信号屏蔽字通过oldset返回。
其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字

  • SIG_BLOCK:该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号;
    可以理解为:之前的屏蔽字+new屏蔽字
    A B C + C D E  -> A B C D E
    
  • SIG_UNBLOCK:该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集补集的交集。set包含了我们希望解除阻塞的信号;
    可以理解为:之前的屏蔽字-new屏蔽字
    A B C - C D E  -> A B
    
  • SIG_SETMASK:该进程新的信号屏蔽字将被set指向的信号集的值代替。

看例子:

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

void sig_fun(int signo)
{
    
    printf("进程[%d]捕获到信号%d\n", getpid(), signo);
}

int main()
{
    
	printf("进程[%d]开始运行,并注册三个信号\n", getpid());
	signal(SIGINT, sig_fun);
    signal(SIGQUIT, sig_fun);
    signal(50, sig_fun);
	
	printf("睡眠10秒,这期间可以捕获信号\n");
	sleep(1000000); // 休眠 1000000 秒,或者捕获到信号并从信号处理函数返回,就停止休眠
	sleep(50);	// 又休眠 50 秒,或者捕获到信号,就停止休眠
    
    printf("准备屏蔽部分信号10秒,这期间捕获不到2、3、50信号...\n");
    /*准备信号集*/
    sigset_t new;
    sigset_t old;
    sigemptyset(&new);
    sigaddset(&new, SIGINT);
    sigaddset(&new, SIGQUIT);
    sigaddset(&new, 50);
    /*屏蔽 2 3 50信号*/
    sigprocmask(SIG_SETMASK, &new, &old);
    printf("面试过程中....\n");
    sleep(10);
	
    printf("恢复原先屏蔽的信号, 可以捕获信号\n");
    //恢复原先屏蔽的信号
    sigprocmask(SIG_SETMASK, &old, NULL);
	//while(1); // 死循环,按ctrl+z退出进程
    return 0;
}

运行结果:
在这里插入图片描述

在这里插入图片描述

五、其他信号相关函数:pause、sleep、abort 函数

#include <unistd.h>
int pause(void);
返回值:-1,并将errno设置为EINTR

pause 函数使调用进程挂起直至捕捉到一个信号。只有执行了一个信号处理程序并从其返回时,pause才返回。在这种情况下,pause返回-1,并将errno设置为EINTR。


#include <unistd.h>
unsigned int sleep(unsigned int seconds);
返回值:0,或未休眠的秒数

sleep 函数使调用线程处于睡眠状态,直到过了指定的seconds之后,或者调用线程捕捉到一个信号并从信号处理程序返回。


#include <stdlib.h>
void abort(void);
返回值:0,或未休眠的秒数

abort 函数将SIGABRT信号发送给调用进程(进程不应忽略此信号)。ISOC规定,调用abort将向主机环境递送一个未成功终止的通知,其方法是调用raise (SIGABRT)函数。

在这里插入图片描述

六、总结

本文介绍进程间通信的信号,显示介绍信号的概念,然后介绍信号处理:signal 函数,信号发送:kill、raise、alarm 函数,信号屏蔽:sigprocmask 函数,最后简单介绍其他信号相关函数:pause、sleep、abort 函数,每个函数都举例说明。

在这里插入图片描述
如果文章有帮助的话,点赞、收藏,支持一波,谢谢

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wkd_007/article/details/135625735

智能推荐

hdu 1229 还是A+B(水)-程序员宅基地

文章浏览阅读122次。还是A+BTime Limit: 2000/1000 MS (Java/Others)Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 24568Accepted Submission(s): 11729Problem Description读入两个小于10000的正整数A和B,计算A+B。...

http客户端Feign——日志配置_feign 日志设置-程序员宅基地

文章浏览阅读419次。HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息。FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。BASIC:仅记录请求的方法,URL以及响应状态码和执行时间。NONE:不记录任何日志信息,这是默认值。配置Feign日志有两种方式;方式二:java代码实现。注解中声明则代表某服务。方式一:配置文件方式。_feign 日志设置

[转载]将容器管理的持久性 Bean 用于面向服务的体系结构-程序员宅基地

文章浏览阅读155次。将容器管理的持久性 Bean 用于面向服务的体系结构本文将介绍如何使用 IBM WebSphere Process Server 对容器管理的持久性 (CMP) Bean的连接和持久性逻辑加以控制,使其可以存储在非关系数据库..._javax.ejb.objectnotfoundexception: no such entity!

基础java练习题(递归)_java 递归例题-程序员宅基地

文章浏览阅读1.5k次。基础java练习题一、递归实现跳台阶从第一级跳到第n级,有多少种跳法一次可跳一级,也可跳两级。还能跳三级import java.math.BigDecimal;import java.util.Scanner;public class Main{ public static void main(String[]args){ Scanner reader=new Scanner(System.in); while(reader.hasNext()){ _java 递归例题

面向对象程序设计(荣誉)实验一 String_对存储在string数组内的所有以字符‘a’开始并以字符‘e’结尾的单词做加密处理。-程序员宅基地

文章浏览阅读1.5k次,点赞6次,收藏6次。目录1.串应用- 计算一个串的最长的真前后缀题目描述输入输出样例输入样例输出题解2.字符串替换(string)题目描述输入输出样例输入样例输出题解3.可重叠子串 (Ver. I)题目描述输入输出样例输入样例输出题解4.字符串操作(string)题目描述输入输出样例输入样例输出题解1.串应用- 计算一个串的最长的真前后缀题目描述给定一个串,如ABCDAB,则ABCDAB的真前缀有:{ A, AB,ABC, ABCD, ABCDA }ABCDAB的真后缀有:{ B, AB,DAB, CDAB, BCDAB_对存储在string数组内的所有以字符‘a’开始并以字符‘e’结尾的单词做加密处理。

算法设计与问题求解/西安交通大学本科课程MOOC/C_算法设计与问题求解西安交通大学-程序员宅基地

文章浏览阅读68次。西安交通大学/算法设计与问题求解/树与二叉树/MOOC_算法设计与问题求解西安交通大学

随便推点

[Vue warn]: Computed property “totalPrice“ was assigned to but it has no setter._computed property "totalprice" was assigned to but-程序员宅基地

文章浏览阅读1.6k次。问题:在Vue项目中出现如下错误提示:[Vue warn]: Computed property "totalPrice" was assigned to but it has no setter. (found in <Anonymous>)代码:<input v-model="totalPrice"/>原因:v-model命令,因Vue 的双向数据绑定原理 , 会自动操作 totalPrice, 对其进行set 操作而 totalPrice 作为计..._computed property "totalprice" was assigned to but it has no setter.

basic1003-我要通过!13行搞定:也许是全网最奇葩解法_basic 1003 case 1-程序员宅基地

文章浏览阅读60次。十分暴力而简洁的解决方式:读取P和T的位置并自动生成唯一正确答案,将题给测点与之对比,不一样就给我爬!_basic 1003 case 1

服务器浏览war文件,详解将Web项目War包部署到Tomcat服务器基本步骤-程序员宅基地

文章浏览阅读422次。原标题:详解将Web项目War包部署到Tomcat服务器基本步骤详解将Web项目War包部署到Tomcat服务器基本步骤1 War包War包一般是在进行Web开发时,通常是一个网站Project下的所有源码的集合,里面包含前台HTML/CSS/JS的代码,也包含Java的代码。当开发人员在自己的开发机器上调试所有代码并通过后,为了交给测试人员测试和未来进行产品发布,都需要将开发人员的源码打包成Wa..._/opt/bosssoft/war/medical-web.war/web-inf/web.xml of module medical-web.war.

python组成三位无重复数字_python组合无重复三位数的实例-程序员宅基地

文章浏览阅读3k次,点赞3次,收藏13次。# -*- coding: utf-8 -*-# 简述:这里有四个数字,分别是:1、2、3、4#提问:能组成多少个互不相同且无重复数字的三位数?各是多少?def f(n):list=[]count=0for i in range(1,n+1):for j in range(1, n+1):for k in range(1, n+1):if i!=j and j!=k and i!=k:list.a..._python求从0到9任意组合成三位数数字不能重复并输出

ElementUl中的el-table怎样吧0和1改变为男和女_elementui table 性别-程序员宅基地

文章浏览阅读1k次,点赞3次,收藏2次。<el-table-column prop="studentSex" label="性别" :formatter="sex"></el-table-column>然后就在vue的methods中写方法就OK了methods: { sex(row,index){ if(row.studentSex == 1){ return '男'; }else{ return '女'; }..._elementui table 性别

java文件操作之移动文件到指定的目录_java中怎么将pro.txt移动到design_mode_code根目录下-程序员宅基地

文章浏览阅读1.1k次。java文件操作之移动文件到指定的目录_java中怎么将pro.txt移动到design_mode_code根目录下

推荐文章

热门文章

相关标签