[Linux]:进程间通信(上)

news2024/12/26 13:56:14

img

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:Linux学习
贝蒂的主页:Betty’s blog

1. 进程间通信介绍

1.1 进程间通信的概念

进程间通信简称IPC(Interprocess communication),进程间通信就是在不同进程之间传播或交换信息。

因为进程之间具有独立性,所以一个进程是无法与另一个进程进行交流的,但是有些情况下我们一个进程必须要

进程接受一个进程的信息,所以操作系统为其提供了特定的方式。

1.1 进程间通信的目的

  1. 数据传输:一个进程要把自己的数据交给另一个进程,让其继续进行处理。
  2. 资源共享:多个进程之间共享同样的资源。
  3. 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  4. 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

1.1 进程间通信的本质

在操作系统中,运行的进程具有独立性,主要体现在数据层面,代码逻辑则有私有和公有情况(父子进程)。这使得进程间通信颇具难度。为实现通信,进程需借助第三方资源,即操作系统提供的一段内存区域。

画板

所以本质上,进程间通信就是让不同进程看到同一份资源,如内存或文件内核缓冲等。因资源可由操作系统不同模块提供,所以就产生了多种进程间通信方式。

2. 匿名管道

2.1 管道的概念

管道Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的数据流称为一个"管道"。

比如我说我们可以通过指令who | wc -l统计当前使用服务器的用户个数,其中指令who为查看当前服务器的登录用户,指令wc -l为统计当前行数,它们运行起来会成为两个进程。但是我们通过管道|将其链接起来,让指令wc -l读取who指令打印出的数据。

画板

2.2 匿名管道

在进程间通信中,匿名管道是一种单向通信机制。它通常用于具有“血缘关系”的进程之间,并且匿名管道只能在本地机器上使用,不能用于网络通信。

其原理就是,创建一个子进程,让父子进程都指向同一个文件,最后我们就可以让父进程向文件写入/读取数据,子进程向文件读取/写入数据。

画板

那么现在我们肯定有一个疑问,那就是创建子进程时,文件描述符数组fd_array会拷贝一份,但是指向的文件为什么不需要拷贝呢?

因为这个数组是为了让该进程能知晓已经打开的文件的个数,所以文件描述符数组fd_array是属于进程的, 既然属于进程,那子进程也需要拷贝一份,因为进程具有独立性。而这个文件是由我们操作系统所管理的,并不属于我们进程,所以子进程在拷贝时并不会再创建一份文件。

并且值得注意的是:

  • 因为父子进程共用的文件资源是由操作系统来维护的,所以当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会发生写时拷贝。比如说我们父子进程打印信息是在同一个屏幕打印的,而不是分别打印在两个屏幕上。
  • 虽然管道使用的是文件,但操作系统一定不会把进程进行通信的数据刷新到磁盘当中,因为这样做有IO参与会降低效率。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是一一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在。

2.3 pipe函数

并且Linux系统中也为我们提供了创建匿名管道的函数——pipe函数,其使用方式如下:

  1. 函数原型:int pipe(int pipefd[2]);
  2. 返回值:创建成功返回0,否则返回-1。
  3. 参数:pipefd[2]是一个输出型参数,其中pipefd[0]代表的是管道读端的文件描述符,pipefd[1]代表的是管道写端的文件描述符。

然后我们就可以使用我们代码来实现我们的进程间通信,其中我们让父进程写入数据,子进程读取数据。

  1. 首先第一步父进程创建出一个管道。

画板

  1. 第二步父进程创建子进程。

画板

  1. 最后一步关闭对应读写端,实现单向通信。

画板

代码实现如下:

#include<cstdio>
#include<cstdlib>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
#define N 2
#define NUM 1024
void writer(int wfd)
{
    string str="hello child, i am father";
    pid_t id=getpid();
    int num=0;
    char buffer[NUM];
    while(true)
    {
        buffer[0] = 0; // 字符串清空,只是为了提醒阅读代码的人,我把这个数组当做字符串了
        snprintf(buffer,sizeof(buffer),"%s-%d-%d",str.c_str(),id,num);
        write(wfd,buffer,strlen(buffer));
        sleep(1);
        num++;
    }

}
void reader(int rfd)
{
    char buffer[NUM];
    while(true)
    {
        ssize_t n=read(rfd,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
        }
        cout<<"child ("<<getpid()<<") get a message:"<<buffer<<endl;
    }
}
int main()
{
    int pipefd[2]={0};
    if(pipe(pipefd) < 0)
    {
        perror("pipe:");
        return 1;
    }
    pid_t id = fork();
    if(id==0)
    {
        //child->read
        close(pipefd[1]);//关闭写端
        reader(pipefd[0]);
        close(pipefd[0]);
    }
    else if(id>0)
    {
        //father->write
        close(pipefd[0]);
        writer(pipefd[1]);
        //进程等待
        pid_t ret=waitpid(id,nullptr,0);
        if(ret<0)
        {
            perror("waitpid:");
            return 1;
        }
        close(pipefd[1]);
    }
    else
    {
        //fork error
        perror("fork:");
        return 1;
    }
    return 0;
}

3. 命名管道

3.1 命名管道

匿名管道只能用于具有"亲缘关系"的进程间通信,所以匿名管道就具有局限性,如果我们想让两个毫不相关的进程间进行通信,就需要使用我们的命名管道。

命名管道与匿名管道都是只存在于内存中的文件,并不会向磁盘刷新,唯一不同的是匿名管道是通过父子进程看到同一份资源,而命名管道是通过路径与文件名的方式找到同一份文件资源,因为我们知道路径具有唯一性。

3.2 mkfifo函数

首先我们可以在命令行通过指令mkfifo 管道名创建一个命名管道,并且我们可以直接通过其进行通信。

然后我们可以在程序中mkfifo函数进行管道创建:

  1. 函数原型:int mkfifo(const char *pathname, mode_t mode);
  2. 返回值:创建成功返回0,否则返回-1。
  3. 参数:mkfifo函数的第一个参数是pathname,表示要创建的命名管道文件。若pathname以路径的方式给出,则将命名管道文件创建在pathname路径下。若pathname以文件名的方式给出,则将命名管道文件默认创建在当前路径下。mkfifo函数的第二个参数是mode,表示创建命名管道文件的默认权限,其守默认掩码umask的约束。

比如说我们可以通过该接口实现客户端client与服务端server间的通信。

首先是需要包含的头文件,以及为了方便管理管道,我们可以将管道文件名定义为宏。

//comment.h
#pragma once
#include<stdio.h>
#include<sys/stat.h>
#include<sys/stat.h> 
#include<sys/types.h> 
#include<fcntl.h> 
#include<unistd.h> 
#define MY_FIFO "./fifo"  //管道的创建路径

以下分别为server.cclient.c的实现:

//server.c
#include"comment.h"
int main()
{
    umask(0);//将文件默认掩码设置为0
    if(mkfifo(MY_FIFO,0666)<0)  //创建管道
    {
        perror("mkfifo:");
        return 1;
    }
    int fd = open(MY_FIFO,O_RDONLY);//以读方式打开命名管道文件
    if(fd<0)
    {
        perror("open");
        return 2;
    }
    //处理业务逻辑,进行相应的读写
    while(true)
    {
        char buffer[64] = {0};
        ssize_t s =  read(fd,buffer,sizeof(buffer)-1);
        if(s > 0)
        {
            //读取成功
            buffer[s] = 0;//字符串末尾置\0
            printf("client send: %s\n",buffer);
        }
        else if(s == 0)
        {
            //写端关闭,读取数据个数为0,
            printf("client close\n");
            break;
        }
        else
        {
            //读取错误
            perror("read:");
            break;
        }
    }
    close(fd);
    return 0;
}
#include"comment.h"
#include<string.h>   
int main()
{
    int fd = open(MY_FIFO,O_WRONLY);//以写方式打开命名管道文件
    if(fd<0)
    {
        perror("open:");
        return 1;
    }
    
    //处理业务逻辑
    while(1)
    {
        char buffer[64] = {0};
        //1.先从键盘读取内容
        printf("enter Message: ");
        fflush(stdout);//刷新缓冲区
        //从键盘读数据
        ssize_t s = read(0,buffer,sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s -1]= 0;//提前置\0,把\n覆盖掉
            //向管道中写入数据
            write(fd,buffer,strlen(buffer));
        }
    }
    close(fd);
    return 0;
}

我们通过管道通信还可以实现很多功能,比如我们可以与进程替换相结合实现一个进程对另一个进程的控制等,当然我们这里就不在实现,感兴趣可以自己实现。

最后我们学习完管道相关知识之后,可能会提出以下疑惑:

命令行管道"|"究竟是匿名管道还是命名管道呢?

我们其实可以通过命令行来直接验证一下:

通过观察我们发现,三个sleep进程的PPID都是相同的,即这三个子进程都是"兄弟进程",所以我们命令行管道是匿名管道。

4. 管道的特点

管道具有以下几个特点:

  1. 自带同步与互斥机制

管道在同一时刻只允许一个进程进行写入或读取操作,属于临界资源。为避免多个进程同时操作管道导致同时读写、交叉读写及数据不一致等问题,内核会对管道操作进行同步与互斥。

  • 互斥保证一个公共资源同一时刻只能被一个进程使用,对于管道场景即两个进程不能同时操作,需相互等待。
  • 同步则要求两个或以上进程按预定先后次序运行,在管道场景中不仅不能同时操作,还需按特定次序操作。
  1. 生命周期随进程

管道本质上通过文件进行通信,依赖于文件系统。当所有打开该文件的进程都退出后,对应的管道文件也会被释放,所以管道的存在与使用进程紧密相关,其生命周期随进程。

  1. 提供流式服务

进程 A 写入管道的数据,进程 B 每次从管道读取的数据量是任意的,没有明确的数据分割,不分报文段,这种服务方式被称为流式服务。与数据报服务不同,数据报服务的数据有明确分割,按报文段进行处理。

  1. 半双工通信
  • 单工通信(Simplex Communication):单工模式的数据传输是单向的。通信双方中,一方固定为发送端,另一方固定为接收端。
  • 半双工通信(Half Duplex):半双工数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
  • 全双工通信(Full Duplex):全双工通信允许数据在两个方向上同时传输,它的能力相当于两个单工通信方式的结合。全双工可以同时(瞬时)进行信号的双向传输。
  1. 管道是面向字节流的,其大小最大为65536字节,即64KB。

5. 管道的特殊情况

在使用管道通信时,可能会出现以下四种特殊情况:

  1. 情况一:若写端进程不进行写入操作,而读端进程一直尝试读取。此时由于管道中没有数据可供读取,读端进程会被挂起,处于等待状态。只有当管道中有数据时,读端进程才会被唤醒,继续进行读取操作。
  2. 情况二:当读端进程不进行读取,而写端进程持续写入时,一旦管道被写满,写端进程就会被挂起。只有在管道中的数据被读端进程读取后,写端进程才会被唤醒,继续进行写入操作。
  3. 情况三:当写端进程将数据写完后关闭写端,读端进程在将管道中的数据全部读完后,此时read返回值为0,我们可以继续执行该进程之后的代码逻辑。
  4. 情况四:若读端进程关闭读端,而写端进程仍在持续向管道写入数据,此时操作系统会将写端进程杀掉。

前三种情况我们都可以很好理解,我们最后验证一下情况四,既然其会被操作系统杀死,我们就可以通过进程等待获取退出信息来验证:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	int fd[2] = { 0 };
	if (pipe(fd) < 0)
	{ 
		perror("pipe");
		return 1;
	}
	pid_t id = fork(); //创建子进程
	if (id == 0)
	{
		//child
		close(fd[0]); 
		//子进程向管道写入数据
		const char* msg = "hello father, I am child";
		int count = 5;
		while (count--)
		{
			write(fd[1], msg, strlen(msg));
			sleep(1);
		}
		close(fd[1]); //子进程写入完毕,关闭文件
		exit(0);
	}
	//father
	close(fd[1]); //父进程关闭写端
	close(fd[0]); //父进程直接关闭读端
	int status = 0;
	waitpid(id, &status, 0);
	printf("child get signal:%d\n", status & 0x7F); //打印子进程收到的信号
	return 0;
}

我们可以看到,这种情况进程的确会被操作系统杀死,并接受到13号信号。

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

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

相关文章

[通信原理]绪论1:信号 × 通信系统

1、消息、信号与信息 消息&#xff1a; 通信系统要传输的对象&#xff0c;是具体的、物理上存在的东西。也是信息的载体。形式多种&#xff1a; 连续消息&#xff1a;语音、温度、活动图片.离散消息&#xff1a;数据、符号、文字. 信息&#xff1a; 消息中所蕴含的内容&…

MySQL练手题--公司和部门平均工资比较(困难)

一、准备工作 Create table If Not Exists Salary (id int, employee_id int, amount int, pay_date date); Create table If Not Exists Employee (employee_id int, department_id int); Truncate table Salary; insert into Salary (id, employee_id, amount, pay_date) va…

ESP8266+httpServer+GET+POST实现网页验证密码

1. 代码 #include "esp_http_server.h" #include "esp_log.h" #include "web_server.h"// 辅助宏&#xff0c;用于计算两个数中的较小值 #define MIN(a, b) ((a) < (b) ? (a) : (b))static const char *TAG "wifi web_server";c…

游戏算法专题之PRD算法:听说你想凭运气抽中荣耀水晶?

PRD算法全称Pseudo-Random Distribution。是概率分布中的一种常见算法&#xff0c;在游戏开发领域中很常用。 PRD用于控制随机事件的触发概率&#xff0c;使其表现得更加符合预期&#xff0c;相比于传统得随机数生成&#xff0c;PRD算法可以平滑得控制随机事件的触发次数&…

cJSON-轻量级解析模块、字符串的神——编织STM32C8T6与阿里云信息传递的纽带

编写方向&#xff1a;本人就不泛泛的编写一篇什么一文学会cJSON了&#xff0c;没什么突出点&#xff0c;也就我水水字数&#xff0c;你们看来看去也不懂&#xff0c;本人是从上阿里云传信息接触的cJSON的&#xff0c;我就此写一篇针对性的文章&#xff0c;希望对大家有用&#…

通信工程学习:什么是UNI用户网络接口

UNI&#xff1a;用户网络接口 UNI&#xff08;User Network Interface&#xff09;用户网络接口&#xff0c;是网络通信中的一个重要概念&#xff0c;它连接了用户设备与智能光网络或其他类型的网络。以下是关于UNI用户网络接口的详细解释&#xff1a; 一、定义与功能 定义&am…

VSCode C++(Code Runner)+ OpenSSL开发环境搭建

本章教程,主要介绍在VSCode中配置OpenSSL环境。 一、安装 OpenSSL 首先,我们需要安装OpenSSL,并配置OpenSSL系统环境变量。 1、下载OpenSSL 下载地址:https://slproweb.com/products/Win32OpenSSL.html 如果下载慢可以通过下方网盘进行下载: 通过网盘分享的文件:Win64Op…

Geneformer AI 模型,有限数据也能解锁基因网络

目录 类似于 BERT 的单单元数据参考模型 NVIDIA Clara 工具组合用于药物研发 用于疾病建模的基础 AI 模型 Geneformer 是最近推出的 和功能强大的 AI 模型&#xff0c;可以通过从大量单细胞转录组数据中进行迁移学习来学习基因网络动力学和相互作用。借助此工具&#xff0c;…

ICPC网络赛 以及ACM训练总结

一、训练反思 关于我自己暑假期间训练的反思&#xff0c;我承认无论是因为什么原因&#xff0c;我自己浪费我整整一个暑假的时间&#xff0c;暑假期间正是我们集训的关键时期&#xff0c;这期间没有任何的事情来打扰我们学习&#xff0c;而我却熬夜&#xff0c;白天训练懈怠&a…

C++类与对象(二)超详细

目录 1.类的6个默认成员函数 2..构造函数 2.1概念 2.2 特征 3.析构函数 3.1 概念 3.2 特性 4.拷贝构造函数 4.1 概念 4.2 特征 5.赋值运算符重载函数 5.1 运算符重载&#xff08;是否重载这个运算符是看这个运算符对这个类是否有意义&#xff09; 5.2 赋值运算符重…

嵌入式单片机程序运行基本机理

1. 程序各种要素说明 大家好,今天用一个最简单的程序跟大家讲清楚程序的构成。 1.1. 概述 硬件首先要知道硬件的组成。 在前面章节我们说过,芯片包含Flash和RAM。 他们虽然不是相同的东西,但是都属于同一个地址空间,32位芯片的地址空间大小是4G。 比如ST32,FLASH通常从…

在 FlexSim 中使用 OpenUSD 分析、可视化和优化现实世界的流程

对于制造和工业企业而言&#xff0c;效率和精度至关重要。为了简化运营、降低成本和提高生产力&#xff0c;各公司正在转向数字孪生和离散事件模拟。 离散事件模拟使制造商能够通过试验不同的输入和行为来优化流程&#xff0c;这些输入和行为可以逐步进行建模和测试。 FlexSi…

基于Python实现的一个电影知识库QA系统

1. 实现效果 1. 图形展示 这是使用echarts.js 来实现的自定义页面的图谱展示&#xff0c;当然还有其他的库也能实现类似的效果&#xff0c;这里看各位的选择。 这里我在每个实体之间都实现了双层关系的绑定&#xff0c;这对于后面实现检索会有点帮助 2. 实体搜索展示 这里…

中断门+陷阱门

中断门&#xff1a; 中断描述符在IDT表里面 kd> dq idtr 80b95400 83e48e000008bfc0 83e48e000008c150 80b95410 0000850000580000 83e4ee000008c5c0 80b95420 83e4ee000008c748 83e48e000008c8a8 80b95430 83e48e000008ca1c 83e48e000008d018 80b95440 000085000050…

回溯-重新安排行程

1.排序 Collections.sort(list,(o1, o2)-> o1.get(0).compareTo(o2.get(0))); 2.返回值 3.往集合添加元素 Arrays.asList(元素) List<List<String>> list new ArrayList<>();List<String> path new ArrayList<>();// 将[["JFK"…

沉浸式体验和评测Meta最新超级大语言模型405B

2024年7月23日&#xff0c; 亚马逊云科技的AI模型托管平台Amazon Bedrock正式上线了Meta推出的超级参数量大语言模型 - Llama 3.1模型&#xff0c;小李哥也迫不及待去体验和试用了该模型&#xff0c;那这么多参数量的AI模型究竟强在哪里呢&#xff1f;Llama 3.1模型是Meta&…

idea激活页面怎么打开

打开Help------选择Register 然后就可以选择激活方式了

Vue2学习笔记(01计算属性和监视属性)

1、事件修饰符 2、计算属性-computed 要显示的数据不存在&#xff0c;要通过计算得来。在computed对象中定义计算属性。在页面中使用{{方法名}}来显示计算的结果。 3、监视属性-watch 通过vm对象的$watch()或watch配置来监视指定的属性当属性变化时,回调函数自动调用,在函数内…

Games101图形学笔记——光栅化

这里写目录标题 Rasterization光栅化屏幕空间隔行扫描三角形采样采样产生的问题反走样处理方法&#xff1a;采样前模糊 频率&#xff0c;时域傅里叶级数展开傅里叶变换 滤波高通滤波低通滤波 卷积卷积的一些定理 反走样MSAA&#xff08;Multisample Anti-Aliasing&#xff09;多…

C++_20_多态

多继承会造成 菱形继承** 使用虚继承来解决 不是给爷爷类加 也不是给子类加 是给父类加 虚基指针和虚基表 多态 概念&#xff1a; 概念&#xff1a; 一个事物的多种形态&#xff0c;简称多态 如&#xff1a; 对象的多态 ​ 张三 ​ 在对象面前 怂 ​ 在朋友面前 谄媚 ​ 在父…