进程的通信1:管道 信号

进程的通信方式

进程的通信方式有很多种,今天我就为大家介绍各种通讯方式,例如:管道,信号,消息队列,共享内存,信号量

1.管道

1.1 管道的简介:

管道分为无名管道有名管道

  • 无名管道:无名管道用于父子进程之间通讯
    在这里插入图片描述

  • 有名管道:有名管道用于任意进程之间的通讯
    在这里插入图片描述

1.2 管道的本质:

管道的本质是在内存建立一段缓冲区,由操作系统内核来负责创建与管理。具体通讯模型如下:
在这里插入图片描述

1.3 无名管道(详细介绍):

1.3.1有关无名管道的详情:
无名管道的特点:
  • 无名管道属于单向通讯
  • 无名管道只能用于父子进程通讯
  • 无名管道发送端叫做写端,接收端叫做读端
  • 无名管道将读端与写端抽象成两个文件进行操作,在无名管道创建成功之后,则会返回将读端与写端的文件描述符存入数组
    在这里插入图片描述
1.3.2 无名管道的创建函数:
1.功能
管道创建之后,内核会将文件描述符存储到数函数数组
2.头文件
#include <unistd.h>
3.函数原型
int pipe(int pipefd[2]);
4.函数参数
pipefd:用于存储无名管道读端与写端的文件描述符的数组
pipefd[0]:读端文件描述符
pipefd[1]:写端文件描述符**
5.函数返回值:
成功:0
失败:-1,设置 errno

示例代码(cpp代码实现):

sing namespace std;
#include <iostream>
#include <string> 
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstring>
int main(){
	int pipe_value[2];//0编号代表读端文件描述符号,1编号代表写端文件描述符号
	int ret = pipe(pipe_value);
	if(ret == -1){
		cout << "无名管道创建失败" << endl;
		exit(EXIT_FAILURE);
	}
	pid_t pid = fork();
	if(pid == -1){
		cout << "创建失败"  << endl;
		exit(EXIT_FAILURE);
	}else if(pid == 0){
		sleep(2);//休眠两秒,以防子进程先抢到
		close(pipe_value[1]);//当读取时,要关闭写端
		char buff1[120] = { 0 };
		ssize_t rbytes = read(pipe_value[0],buff1,sizeof(buff1));
		if(rbytes == -1){
			cout << "pipe failure" << endl;
			close(pipe_value[0]);
			exit(EXIT_FAILURE);
		}
		cout << "输入成功,你要接收字符串是:"; 
	 	printf("%s\n",buff1);
		close(pipe_value[0]);
		exit(EXIT_FAILURE); 
	}else if(pid > 0){
		close(pipe_value[0]);//当写入时,要关闭读端
		string temp;
		cout << "请输入你要写入的字符串:";
		char buff[128] = { 0 };
		fgets(buff,sizeof(buff) - 1,stdin);
		buff[strlen(buff)] = '\0';
		ssize_t wbytes = write(pipe_value[1],buff,sizeof(buff));
		if(wbytes == -1){ 
			cout << "写入失败" << endl;
			close(pipe_value[1]);
			exit(EXIT_FAILURE);
		}
		waitpid(-1,NULL,0);
		close(pipe_value[1]);
	} 
	return 0;
}

题目答案:
请添加图片描述
上面代码的模型图:
在这里插入图片描述

1.4 有名管道(详情介绍):

1.4.1有名管道:

有名管道是文件系统中可见的文件,但是不占用磁盘空间,仍然在内存中。可以通过 mkfifo命令创建有名管道(在共享目录不能使用mkfifo)
有名管道与无名管道一样,在应用层是基于文件接口进行操作
有名管道用于任意进程之间的通讯,当管道为空时,读进程会阻塞

在这里插入图片描述
注意,此时管道应该类似于文件.

1.4.2有名管道的创建:

创建函数:

1.函数头文件
#include <sys/types.h>
#include <sys/stat.h>
2.函数原型:
int mkfifo(const char *pathname, mode_t mode);
3.函数参数:
pathname:有名管道路径名
mode:有名管道文件访问权限, 常用0644
4.函数返回值:
成功:返回0
失败:返回-1,并设置errno

示例:
write.cpp :

using namespace std;
#include <iostream>
#include <string> 
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#define PATH "/home/linuxfu/pipe"
int main(){
	int ret = access(PATH,F_OK);
	if(ret == -1){
		mkfifo(PATH,0644);
	}
	int fd = open(PATH,O_WRONLY);
	if(fd == -1){
		cout << "open failed" << endl;
		exit(EXIT_FAILURE);
	}
	char buf[128] = { 0 };
	cout << "请输入你想要输入的字符串:";
	fgets(buf,sizeof(buf) - 1,stdin);
	buf[strlen(buf) - 1] = '\0';
	ssize_t wtypes = write(fd,buf,size(buf));
	if(wtypes == -1){
		cout << "write failed" << endl;
		close(fd);
		exit(EXIT_FAILURE); 
	}
	return 0;
}

read.cpp:

using namespace std;
#include <iostream>
#include <string> 
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#define PATH "/home/linuxfu/pipe"
int main(){
	int ret = access(PATH,F_OK);
	if(ret == -1){
		mkfifo(PATH,0644);
	}
	int fd = open(PATH,O_RDONLY);
	if(fd == -1){
		cout << "open failed" << endl;
		exit(EXIT_FAILURE);
	}
	char buf[128] = { 0 };
	ssize_t rtypes = read(fd,buf,size(buf));
	if(rtypes == -1){
		cout << "read failed" << endl;
		close(fd);
		exit(EXIT_FAILURE); 
	}
	cout << "读取成功,字符串为:";
	printf("%s\n",buf);
	return 0;
}

请添加图片描述

  • 如果有名管道的一端以只读方式打开,它会阻塞到另一端以写的方式(只写,读写)
  • 如果有名管道的一端以只写方式打开,它会阻塞到另一端以读的方式(只读,读写)

2.信号

2.1 信号的定义:

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

  • 进程在运行过程中,随时可能被各种信号打断
  • 进程可以忽略或者去调用相应的函数去处理信号
  • 进程无法预测信号到达的精准时间

2.2 信号的来源:

在linux中信号一般来源如下:

  • 程序执行错误,如内存访问越界,数学运算除0
  • 由其他进程发送
  • 通过控制终端发送,如ctrl + c
  • 子进程结束时向父进程发送的SIGCHLD信号
  • 程序中设定的定时器产生的SIGALRM信号

2.3信号的常用种类:

1.SIGINT 该信号在用户键入INTR字符(通常是Ctrl-C)时发出,终端驱动程序发送此信号并送到前台进
程中的每一个进程。
2.SIGQUIT 该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-)来控制。
3.SIGILL 该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发出。
4.SIGFPE 该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0
等其它所有的算术的错误。
5.SIGKILL 该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。//非常常见的信号,常用来杀死进程 
6.SIGALRM 该信号当一个定时器到时的时候发出。
7.SIGSTOP 该信号用于暂停一个进程,且不能被阻塞、处理或忽略。//这个也是常用信号,用来暂停信号
8.SIGTSTP 该信号用于交互停止进程,用户可键入SUSP字符时(通常是Ctrl-Z)发出这个信号。
9.SIGCHLD 子进程改变状态时,父进程会收到这个信号
10.SIGABRT 进程异常中止

2.4信号的处理:

信号处理流程包含以下两个方面:

  • 信号的发送 :可以由进程直接发送
  • 信号投递与处理 : 由内核进行投递给具体的进程并处理

在 Linux 中对信号的处理方式如下:

  • 忽略信号,即对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。
  • 捕捉信号,定义信号处理函数,当信号发生时,执行相应的处理函数。
  • 执行缺省操作,Linux对每种信号都规定了默认操作

(注意!注意!注意!)信号处理方式用的最多的是缺省操作,和自定义信号处理函数,一定要记住!!!

这是信号处理流程图:
在这里插入图片描述

2.5 信号的发送

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

2.5 .1 kill函数:
1.函数头文件
#include<sys/types.h>
#include<signal.h>

2.函数原型
int kill(pid_t pid,int sig);

3.函数功能
向指定的进程发送一个信号

4.函数参数
pid:进程的id 
sig:信号的id

5.函数返回值
成功返回0;
失败返回-1
2.5.2 raise函数
1.函数头文件
#include <sys/types.h>
#include <signal.h>

2.函数原型
int raise(int sig)

3.函数功能
send a signal to the caller

4.函数参数
sig:信号编号

5.函数返回值:
成功返回0
失败返回-1
2.5.3等待函数
  • 在进程没有结束时,进程在任何时间点都可以接受到信号
  • 需要阻塞等待信号时,则可以调用 pause() 函数
1.函数头文件
#include <unistd.h>
2.函数原型
int pause(void);
3.函数功能
阻塞进程,直到收到信号后唤醒
4.函数返回值
成功:返回 0
失败:返回 -1,并设置 errno
2.5.4 代码实现(cpp):
using namespace std;
#include <iostream>
#include <string> 
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

int main(){
	pid_t son1 = fork();
	if(son1 == -1){
		cout << "son1 create failed" << endl;
		exit(EXIT_FAILURE);
	}else if(son1 == 0){
		cout << "son1 start" << endl;
		pause();//暂停,等到信号再开始
		cout << "son1 end" << endl;  
	}else{
		pid_t son2 = fork();
		if(son2 == -1){
			cout << "son2 create failed" << endl;
			exit(EXIT_FAILURE);
		}else if(son2 == 0){
			cout << "son2 start" << endl;
			pause();//暂停,等到信号再开始
			cout << "son2 end" << endl;
		}else{
			sleep(2); //让主线程睡眠,让其他线程抢资源
			int pid = 0;
			int ret = kill(son1,SIGKILL);//给son1发送信号
			if(ret == -1){
				perror("ret");
				exit(EXIT_FAILURE);
			}
			cout << "杀死" << son1 << "进程" << "成功" << endl;
			int ret1 = kill(son2,SIGKILL);//给son2发送信号
			if(ret1 == -1){
				perror("ret1");
				exit(EXIT_FAILURE);
			}
			cout << "杀死" << son2 << "进程" << "成功" << endl;
		}
	}
	return 0;
}

运行图片:
请添加图片描述

2.6 自定义信号的处理

首先我们再复习一遍信号处理方式:
信号是由操作系统内核发送给指定进程,进程收到信号后则需要进行处理
处理信号有三种方式:

  • 忽略 : 不进行处理
  • 默认 : 按照信号的默认方式处理
  • 用户自定义 : 通过用户实现自定义处理函数来处理,由内核来进行调用
    每种信号都有相应的默认处理方式

每种信号都有默认处理方式:

1.进程退出
SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
2.进程忽略
SIGCHLD,SIGPWR,SIGURG,SIGWINCH
3.进程暂停
SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU

!!!然后这里我们会着重学习自定义处理函数
1.实现用户自定义处理函数,必须按照下列格式:

typedef void (*sighandler_t)(int);
typedef void (*)(int) sighandler_t

2.设置信号处理处理方式
通过 signal 函数设置信号处理方式

函数头文件
#include <signal.h>
函数原型
sighandler_t signal(int signum, sighandler_t handler);
函数功能
设置信号的处理方式,如果是自定义处理方式,提供函数地址,注册到内核中
函数参数
signum:信号编号
	handler:信号处理方式
			SIG_IGN---->忽略信号
			SIG_DFL---->按照默认方式处理
			自定义处理函数的地址
函数返回值
成功:返回信号处理函数地址
失败:返回SIG_ERR,并设置errno
注意点:三种信号处理方式互斥,一般选择一种即可

此函数案列代码(cpp) :

using namespace std;
#include <iostream>
#include <string> 
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
typedef void (*sighandler_t)(int);
void sighandler_fun(int signum){
	cout << signum << "信号处理自定义成功"<< endl;
}
int main(){
	pid_t son1 = fork();
	if(son1 == -1){
		cout << "son1 create failed" << endl;
		exit(EXIT_FAILURE);
	}else if(son1 == 0){
		cout << "son1 start" << endl;
		sighandler_t ret = signal(SIGUSR1,SIG_DFL);//son1自定义默认信号
		if(ret == SIG_ERR){
			perror("ret");
			exit(EXIT_FAILURE);
		}
		cout << "son1 end" << endl;  
	}else{
		pid_t son2 = fork();
		if(son2 == -1){
			cout << "son2 create failed" << endl;
			exit(EXIT_FAILURE);
		}else if(son2 == 0){
			cout << "son2 start" << endl;
			sighandler_t ret1 = signal(SIGUSR2,sighandler_fun);//son2自定义信号
			if(ret1 == SIG_ERR){
				perror("ret1");
				exit(EXIT_FAILURE);
			}
			cout << "son2 end" << endl;
		}else{
			int val = kill(son1,SIGUSR1);
			if(val == -1){
				perror("val");
				exit(EXIT_FAILURE);
			}
			cout << son1 << "successful" << endl;
			int val2 = kill(son2,SIGUSR2);//给son2发送自定义信号
			if(val2 == -1){
				perror("val2");
				exit(EXIT_FAILURE);
			}
			cout << son2 << "successful" << endl;
			int pid=0;
			while((pid = waitpid(-1,NULL,0)) == 0);
		}
	}
	return 0;
}

2.7补充:

2.7.1定时器信号

在Linux系统中提供了 alarm 函数,用于设置定时器

1.函数头文件
#include <unistd.h>
2.函数原型
unsigned int alarm(unsigned int seconds);
3.函数功能
设置定时器的秒数
4.函数参数
seconds:定时的时间秒数
5.函数返回值
返回上一次进程设置定时器剩余的秒数,如果进程上一次没有设置定时器,则返回0

tips

  • 定时器的定时任务由内核完成的,alarm 函数负责设置定时时间,并告诉内核启动定时器
  • 当定时时间超时后,内核会向进程发出 SIGALRM 信号
2.7.2 子进程退出信号

在使用 wait() 函数时,由于阻塞或者非阻塞都非常消耗资源。并且在阻塞情况下,父进程不能执行其他逻辑。
如何解决?
子进程退出是异步事件,可以利用在子进程退出时,会自动给父进程发送 SIGCHLD 信号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值