[linux] 进程间信号

Posted 一个正直的男孩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[linux] 进程间信号相关的知识,希望对你有一定的参考价值。

信号在生活中无处不在,上下课铃,红绿灯,闹钟等,这一切都是信号,可是我们是如何识别信号的呢?无非就是有人定义将信号与意义绑定,并灌输。在操作系统中也存在信号,那么我们一起来看看吧


文章目录

1 理解信号

信号不管是在生活中还是在操作系统中其实都可以简单看为三部分

  1. 是什么
  2. 为什么
  3. 怎么处理

而在操作系统层面来看并不需要知道为什么(毕竟信号也是工程师定义的),他只需要知道是啥信号和收到信号后要如何处理,其实生活中收到信号也是如此,过马路红灯停,绿灯行,并不会有人问为啥。


1.1 见识Linux中的信号

在linux中查看信号需要用到命令 kill -l,一共62个信号,1~31为默认信号,24~64为实时信号

浅谈: 默认信号 VS 实时信号

普通信号会丢失,实时信号不会丢失,也就是是否要存储信号,后文会提

在Linux下的全部信号都是宏,也就是一个标志位,这样做的目的节省空间,用一个bit位标识一种状态,默认信号存放在该路径下/usr/include/bits/signum.h

1.2 如何产生信号

一切事情都是有前因后果的,当然信号也是,先要如何产生才可以知道如何处理

  1. 键盘产生
  2. 命令行向进程发送信号
  3. 调用函数向os发送请求
  4. 代码出错(访问硬件出错)

键盘产生

ctrl+c 终止进程 ctrl+\\ 终止进程并保存core文件 ,ctrl+z挂起一个文件,他们其实和信信号列表一一对应,后文信号捕捉验证


命令行向进程发送信号

使用命令 kill -信号(数字or宏名) 进程pid

调用函数向os发送请求

其实就是和os说快给我发信号,C语言接口alarm(延迟xx秒让os发送14号信号给该进程)abort(让os给当前发6号信号)raise(指定让os发送x号信号)kill(给别的进程发送信号)

alarm
代码:

int main()

  alarm(5);
  int count=0;
  while(1)
  

    cout<<count++<<" "<<getpid()<<endl;
    sleep(1);
  
  return 0;

结果:

kill

代码:

#include<iostream>
#include<csignal>
#include<unistd.h>
#include<sys/types.h>
using namespace std;


int main()

  kill(1213,1);

结果:

代码出错(访问硬件出错)

子进程退出的时候可以用wait函数等待回收他的资源,且也可以知道他的退出码,core dump ,信号 。

代码:

#include<iostream>
#include<csignal>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

using namespace std;


int main()

  pid_t id=fork();
  while(id==0)
  
    //child
    int count=0;
    while(count!=5)
    
      count++;
      cout<<"I am child proc"<<endl;
      sleep(1);
    
    //除0错误
	//int a =1;
    //int b=0;
    //a/=b;

    //野指针
    int *c=0x000000;
    *c=1;
  
  int status=0;
  waitpid(id,&status,0);
  cout<<(status&0x7f)<<endl;
  return 0;

结果:

os是如何知道代码出错而发对应的信号呢?

除0对应的硬件 == cup中的状态寄存器
野指针 == 内存/页表MMU
os是软硬件的管理者,自然就知道哪里出错,在根据硬件在对进程发送信号

core dump


1.3 信号的处理

在现实中我们处理信号一般有三种方式

  1. 默认(识别信号马上处理 , 红绿灯)
  2. 忽略 (识别信号不处理,打游戏妈妈叫吃饭)
  3. 捕捉自定义 (识别后用自己的方法,听到闹钟声跳个舞🐶🐶)

在操作系统中也是如此处理信号也是以上三种方式,且也知道一个进程收到信号不一定马上处理那么不处理就要保存起来(该进程的PCB中)。按照老的套路先描述在组织,这里的组织方式就是一个位图

当os发送信号给进程时,看信号是xx号并在对应的pending位图中,把xx号位置设为1 ,再看看block位图xx号位置是否设为1,如果为1屏蔽信号,不然直接执行方法。其实信号被屏蔽还有一个专业的名词(未决),信号没有被屏蔽执行的方法(递达)

递达信号流程图


1.3.1 屏蔽信号与获取pending

在C语言中有一个类型sigset_t 可以表示一个每个信号的有效与无效,也就是一个位图,我们可以用它和信号集操作函数等来告诉os屏蔽那些信号

信号集操作函数

 #include <signal.h>
int sigemptyset(sigset_t *set);//初始化 ”位图“ 为0
int sigfillset(sigset_t *set);//初始化 ”位图“ 为1
int sigaddset (sigset_t *set, int signo);//给 “位图”指定位置为1
int sigdelset(sigset_t *set, int signo);//给 “位图”指定位置为0
int sigismember(const sigset_t *set, int signo);//判断 ”位图“指定位置是否为1

上述的接口都是语言接口,也就说明,我们还没有告诉os要屏蔽那个信号,那么就要用到系统接口,sigprocmask

sigprocmask使用介绍

使用这个接口就可以让os吧pcb中block变成自己所设置位图的样子,屏蔽信号

sigpending使用介绍


输入形参数,获取pending当前状态

代码
实现屏蔽信号获取pending,删除屏蔽,抵达信号

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<unistd.h>
using namespace std;


void showpending(sigset_t * set)

	for(int i =1; i<=31;i++)
	
		if(sigismember(set,i))
		
			cout<<'1';
		
		else
		
			cout<<'0';
		
	
	cout<<endl;

int main()

	sigset_t set,oset;
	sigemptyset(&set);
	sigemptyset(&oset);


	sigaddset(&set,2);
	sigprocmask(SIG_SETMASK,&set,&oset);

	int count=0;
	while(1)
	
		sigpending(&set);
		showpending(&set);
		sleep(1);
    count++;
		if(count==7)
		
			break;
		
	


	return 0;

结果:



1.3.2 信号捕捉(自定义处理)

上述说过信号的处理方式, 默认,忽略,捕捉,其实捕捉就是自定义信号(递达),也就是修改pcb中的hander函数数组中的方法,系统接口signal ,sigaction

接口介绍:

接口使用

signal

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<unistd.h>
using namespace std;


void hander(int signo)

  cout<<"signo: "<<signo<<endl;

int main()

  signal(2,hander);
  int count=0;
  while(1)
  
    cout<<count++<<endl;
    sleep(1);
  



  return 0;

结果:


sigaction

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<unistd.h>
using namespace std;


void hander(int signo)

  cout<<"signo: "<<signo<<endl;

int main()


  //signal(2,hander);
   struct sigaction act;
   struct sigaction oact;
  act.sa_handler=hander;
  sigaction(2,&act,&oact);
  int count=0;
  while(1)
  
    cout<<count++<<endl;
    sleep(1);
  



  return 0;

结果:

既然可以屏蔽捕捉信号,那我全部屏蔽我这个进程不就杀不死了吗?

既然你可以想到,没错工程师也想到了,在linux下有SIGKILL与SIGTOP信号是无法捕捉的和屏蔽,专门防止你小心思


问信号时如何捕捉的呢?

抽象理解,捕捉函数是一个地雷,而触发的条件就是信号。

回顾进程虚拟地中空间:

之前说每个进程都有自己的一张独有的页表,其实还不太准确,其实有俩张一张是用户级页表,一张是系统级页表(每个进程共享),在执行代码时,没有遇到系统接口前用的都是用户级页表,当执行到系统接口时转换为系统页表

图解捕捉捕捉的流程图:
捕捉的过程其实很像数学中的一个符号


1.4 SIGCHILD

之前说过子进程退出的时候父进程会回收子进程,一方面是防止内存泄漏,一方面是获取子进程的退出结果。但等待,父进程就在阻塞等待里(or非阻塞轮询),着都让父进程无法一心一意的执行自己的代码?那么如何解决呢?

解决方案

子进退出的时候会给父进程发送一个信号SIGCHILD,🙉🙉🙉,那配合signal不就…………子进程退出发送信号,捕捉,改变SIGCHILD的函数实现方法

代码:

using namespace std;

void hander(int signo)

	cout<<signo<<endl;
  pid_t id;
	while((id=wait(NULL))>0)
	
		printf("wait pid %d success",id);
	



int main()


	signal(17,hander);
	pid_t id =fork();
	if(id==0)
	
		//child
		int count=4;
		while(count>0)
		
			count--;
			cout<<count<<" "<<getpid()<<endl;
			sleep(1);
		
		exit(1);
	

	while(true)
	
		cout<<"I am father proce"<<endl;
		sleep(1);
	

	return 0;

结果:

以上是关于[linux] 进程间信号的主要内容,如果未能解决你的问题,请参考以下文章

Linux进程间通信 -- 信号集函数 sigemptyset()sigprocmask()sigpending()sigsuspend()

linux进程间通信--信号量

Linux进程间通信之管道

Linux进程间通信-信号量

Linux进程间通信--进程,信号,管道,消息队列,信号量,共享内存

linux信号