[Linux] 进程间通信基础

news2024/11/17 6:46:51
在这里插入图片描述

💻文章目录

  • 📄前言
  • 进程间通信基础
    • 概念
  • 管道
    • 概念
    • 管道的工作原理
    • 模拟实现shell中的管道
  • 共享内存
    • 概念
    • 接口的介绍
    • 共享内存的使用
  • 📓总结


📄前言

你是否了解进程间是如何通信的呢?你是否知道管道的工作原理呢?管道是Linux中最基本的也是最常用的进程间通信手段,----(Todo)

进程间通信基础

概念

进程间通信(Inter-Process Communication)简称IPC,是不同进程之间传递数据的手段、接口。它是多进程间协同工作的核心机制。

Linux 主要的IPC接口有:

  1. 管道(pipe)
  2. 信号(Signal)
  3. 共享内存(Shared Memory)
  4. 信号量(Semaphore)
  5. 消息队列(Message Queue)
  6. 套接字(Socket)

本文将着重介绍管道与共享内存两种方式。

管道

概念

管道大概是最多Linux用户使用过的一种的IPC接口了吧,只要有学过控制台代码,你就一定会认识它。它是一种先进先出的结构,数据的流向是单向的,最简单的使用管道的方法就是在 shell 中使用 “ | ”.

echo "管道的使用方法" | grep "test.c"
# 其功能就像它的名字,将进程数据通过一个管子传到另一个进程
# [进程A] ---> |管道| ---> [进程B]

管道的类型有两种

  • 匿名管道: 主要用于父子进程间的通信,在文件系统中没有一个实际的名称,进程结束便消失。

  • 命名管道: 可用于不同进程之间的通信,与匿名管道不通命名管道在文件系统中有一个实际名称,可长久存在,并且可以通过两个管道来实现双向通信。

管道的工作原理

管道在实质上可以看成缓冲区,一端用于写,一端用于读。在进程将数据写入管道时,数据被储存在系统内核的缓冲区中,而不是直接存到管道文件中,管道文件只是作为一个标识符用于不同进程打开管道。正所谓实践出真知,其他的原理,让我们通过 Coding 来知晓吧。

  • 匿名管道的使用:
// 匿名管道与命名管道的使用

// 头文件 unistd.h
// int pipe(int pipefd[2]);
// pipefd[0]被设为管道的读端,[1]为写端

const char *msg = "i like linux!";

void test_1()
{
    char buf[1024] = {0};
    int fd[2];
    pipe(fd);
    pid_t id = fork();	//生成子进程
    if(id < 0)  exit(1);
    else if(id == 0)	// 子进程入口
    {
        close(fd[0]);
        write(fd[1], msg, strlen(msg));
        close(fd[1]);
        exit(0);
    }
    // 父进程
    close(fd[1]);   //关闭读端
    read(fd[0], buf, sizeof(buf));
    printf("%s\n", buf);	
    close(fd[0]);
    wait(NULL);	//等待子进程结束
}

//  	  父进程                          子进程
//  +----------------+             +----------------+
//  |                |   系统缓冲区  |                |
//  |   写入 fd[1]    |------------>|   读取 fd[0]   |
//  |                |     管道     |                |
//  +----------------+             +----------------+
//		写入端						    读入端

// 你可能会有些疑问,为什么要用这么多个close。
// 关闭不需要用的文件描述符是个良好的编程习惯
// 因为文件描述符是有限的资源,而且不关闭会在某些情况下,导致进程阻塞。
  • 命名管道的使用:
// 头文件 sys/types.h sys/stat.h
// int mkfifo(const char* pathname, mode_t mode) 创建管道文件
// pathname:管道的名称    mode:设置文件的权限。

// server.cpp
int main()
{
    int ret = mkfifo("fifo", 0644);
	const char* msg = "server:i love linux"
    int fd = open("fifo", O_WRONLY);	// O_WRONLY:只写模式

    for (int i = 0; i < 10; ++i)
    {
        write(fd, msg, strlen(msg));	//读端没有打开,进程则会阻塞
        sleep(1);
    }

    return 0;
}

// client.cpp
int main()
{
    int fd = open("fifo", O_RDONLY);  // O_RDONLY:只读模式
    char buf[1024];

    while (1)
    {	// 如果管道写端没有打开,进程则会阻塞
        ssize_t n = read(fd, buf, sizeof(buf));
        buf[n] = '\n';
        if (n == 0)	// 读端断开连接,返回0
        {
            printf("process exit\n");
            return 0;
        }
        else if (n > 0)
        {
            write(1, buf, n + 1);	// 向标准输出打印
        }
    }

    return 0;
}

管道的读写规则:

  • 当管道读写端任意一端未打开,则进程会阻塞等待。
  • 管道写满了,尝试写入的进程会阻塞
  • 管道无数据可读,如果写端关闭则返回0,写端没有关闭则阻塞进程。

模拟实现shell中的管道

int main()
{
    int pipefd[2];
    
    if(pipe(pipefd) == -1 && errno != EEXIST)
    {
        perror("pipe");
        exit(1);
    }

    pid_t pid = fork();
    if(pid == 0)
    {	//子进程
        dup2(pipefd[1], STDOUT_FILENO);	
        //dup = duplicate(复制),将pipefd[1]复制到标准输出(1号)
        // 像标准输出打印的数据都讲传入管道写端
        close(pipefd[0]);	
        close(pipefd[1]);	// 已经将其复制到标准输出,可以关闭
        char* argv[] = {"ls", "-l", nullptr};
        execvp(argv[0], argv);	//执行命令
        exit(0);
    }

    pid_t pid2 = fork();
    if(pid2 == 0)
    {	//子进程
        dup2(pipefd[0], STDIN_FILENO);	//将fd[0]复制到标准输入
        close(pipefd[0]);
        close(pipefd[1]);

        char* argv[] = {"grep", (char*)"test", nullptr};
        execvp(argv[0], argv);
        exit(0);
    }

    close(pipefd[0]);
    close(pipefd[1]);
    wait(nullptr);
    wait(nullptr);

    return 0;
}

共享内存

概念

共享内存是所有IPC机制中最快的一种机制,它能使得多个进程访问同一块内存区域,而不用在进程间复制拷贝。 例如管道,它实际就是系统中的一块缓存区,两个进程间交流就必须将缓冲区的数据拷贝的自己内存中。共享内存则是在物理内存中开辟一段空间,然后通过页表将其映射到程序的共享区,进程直接对内存进行读写。

+---------+         +-------------------+        +---------+
|         |  ---->  |                   |  <---- |         |
| Process |         |   Shared Memory   |        | Process |
|    A    |  <----  |       Segment     |  ----> |    B    |
|         |         |                   |        |         |
+---------+         +-------------------+        +---------+
     ^                                               ^
     |                                               |
    Read                                           Write

接口的介绍

在使用共享内存前,必须先简单介绍一下System V 与 POSIX,它们是UNIX系统的两种不同系统标准,而在Linux上他们两者的接口都有兼容,接下来我们要使用的共享内存属于System V的。

要使用共享内存就得先知道如何检查系统中的共享内存,以及如果程序遇到异常时,如何删除共享内存。

#显示共享内存
ipcs -m 
# 使用 ipcs 命令来检查system V的通信信息,默认情况下显示所有的资源。

# 删除共享内存
ipcrm [shm|msg|sem] ID ... 
# 选项
# -m 根据共享内存的shmid来删除
# -M 根据共享内存的shmeky来删除

共享内存的接口:

// 接口介绍
// 涉及头文件: <sys/ipc.h> <sys/shm.h>

key_t ftok(const char* pathname, int proj_id);
//使用ftok来生成唯一的key值,参数为路径名与项目ID

int shmget(key_t key, size_t size, int shmflg);	
// 返回值为shmid
// 根据key来生成共享,size为共享内存的字节数只能为1024的倍数。
// shmflg:共享内存的权限,一般使用像 0644,IPC_CREAT、IPC_EXCL等。

// 挂载共享内存
void* shmat(int shmid, const void* shmaddr, int shmflg);
// shmid 为shmget的返回值
// shmaddr:将共享内存连接到当前进程地址空间的特定地址
// shmflg:一般为0或者SHM_RDONLY(只读)

//取消挂载共享内存
int shmdt(const void* shmaddr);
// shmaddr :共享内存的地址

//控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
// shmid:共享内存的id号
// cmd:控制命令,一般为IPC_STAT(获取共享内存的状态)、IPC_SET(设置共享内存的参数)、IPC_RMID(删除共享内存)
// buf:指向shmid_ds 结构的指针,用于设置共享内存或存储共享内存的状态。

共享内存的使用

共享内存主要用于两个不同进程数据的交流,这里将使用两个不同进程,一个用于写数据,一个用于读数据。因为共享内存的读写是无法预测的(对方不知道你何时写了),所以需要用到命名管道来辅助。

server.cpp:

// server.cpp
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <string>
#include <unistd.h>

const char* pathname = "/home/catianri/code";

const int project_id = 0x11223344;

int main() {
    key_t key = ftok(pathname, project_id); // 创建唯一的key
    int cnt = 0, code = 0;	// code用于通知对方进程,已经开始写入。
    int shmid = shmget(key, 1024, 0666|IPC_CREAT); // 创建共享内存

    mkfifo(".fifo", 0666);	//创建命名管道
    int fd = open(".fifo", O_WRONLY);	

    int* arr = static_cast<int*>(shmat(shmid, nullptr, 0)); // 将共享内存附加到进程的地址空间
    while(cnt < 10)
    {
        arr[cnt++] = cnt;	// 向共享内存写入数据 
        write(fd, &code, sizeof(int));	// 通知另一个进程
        sleep(1);
    }

    shmdt(arr); // 断开共享内存连接

    shmctl(shmid, IPC_RMID, NULL); // 销毁共享内存
    close(fd);

    return 0;
}

client.cpp:

#include <iostream>
#include <cstdio>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>

const char* pathname = "/home/catianri/code";

const int project_id = 0x11223344;

int main() {
    key_t key = ftok(pathname, project_id); // 使用相同的文件和项目ID来创建key
    int shmid = shmget(key, 1024, 0666|IPC_CREAT); // 连接到共享内存

    int *arr = static_cast<int*>(shmat(shmid, nullptr, 0)); // 将共享内存附加到进程的地址空间
    mkfifo(".fifo", 0666);
    int fd = open(".fifo", O_RDONLY);
    int* code, cnt = 0;

    while(1)
    {
        ssize_t n = read(fd, &code, sizeof(int));
        if(n == 0)	//server已经断开写端
        {
            close(fd);
			std::cout << "process exit" << std::endl;
        }
        else 
        {	//拿取共享内存中的数据。
            std::cout << arr[cnt++] << " ";
            fflush(stdout);	//刷新缓冲区。
        }
    }

    shmdt(arr); // 断开共享内存连接

    return 0;
}

共享内存的特点:

  • 高效性: 共享内存是最快的IPC方式,它允许进程直接访问共享数据,避免了拷贝操作。
  • 灵活性: 共享内存提供了高度的灵活性,开发者可以根据需要自定义共享数据的结构和管理方式。
  • 手动同步: 由于可以多进程同时访问,所以需要外部同步机制。
  • 复杂性: 与其他IPC机制(如管道和消息队列)相比,共享内存的使用和管理更为复杂。

📓总结

特性管道System V共享内存
性能和效率数据需要在进程间复制,存在额外的CPU开销和延迟。允许多个进程直接访问同一内存区,减少了数据复制,提高了效率。
使用场景适用于顺序数据流通信,常用于父子进程或紧密相关进程间。适用于性能要求高的场景,如大数据处理、实时系统,因为它几乎无延迟地实现数据共享。
同步机制自带同步机制,写入端和读取端会在必要时阻塞,直到对方准备好。需要额外的同步机制(如信号量、互斥锁)来防止数据竞争和保证一致性。
容错性和可靠性相对简单可靠,但需要正确处理EOF和管道破裂等情况。需要仔细管理资源和同步,避免竞态条件、死锁或数据损坏。
易用性API简单,容易实现和维护,但功能有限。提供了一套功能丰富的API,但相较于管道,使用和维护更加复杂。

📜博客主页:主页
📫我的专栏:C++
📱我的github:github

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

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

相关文章

LeetCode142:环形链表

题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内…

TSINGSEE青犀智能分析网关V4有限空间作业监护AI算法介绍及应用

在现代工业生产中&#xff0c;有限空间作业是一种常见的生产方式。然而&#xff0c;这种作业环境由于空间有限、出入口较为狭窄、人员操作复杂等因素&#xff0c;往往存在着较高的安全风险&#xff0c;因为自然通风不良&#xff0c;作业人员不能长时间在内工作。为了保障作业人…

linux环境基础开发工具2(gcc/g++ 、 make/ Makefile)

目录 Linux编译器-gcc/g使用 Linux项目自动化构建工具-make/Makefile Linux编译器-gcc/g使用 1. 背景知识 1.2 gcc/g -v 查看当前服务器的编译器版本 1.3 安装gcc / g sudo yum install -y gcc-c 1.4 预处理 -> 编译 -> 汇编 -> 链接 [wxqVM-4-9-centos Test]$ to…

如何在Windows搭建WebDav服务,并外网可访问

目录 1. 安装IIS必要WebDav组件 2. 客户端测试 3. 使用cpolar内网穿透&#xff0c;将WebDav服务暴露在公网 3.1 打开Web-UI管理界面 3.2 创建隧道 3.3 查看在线隧道列表 4. 公网远程访问 4.1 浏览器访问测试 4.2 映射本地盘符访问 4.3 安装Raidrive客户端 总结&…

Could not use APOC procedures

报错内容&#xff1a; Traceback (most recent call last):File "/root/anaconda3/envs/sakura/lib/python3.9/site-packages/langchain_community/graphs/neo4j_graph.py", line 205, in __init__self.refresh_schema()File "/root/anaconda3/envs/sakura/lib…

基于ACM32 MCU的两轮车充电桩方案介绍,有利于打造高效安全的电池管理

随着城市化进程的加快、人们生活水平的提高和节能环保理念的普及&#xff0c;越来越多的人选择了电动车作为代步工具&#xff0c;而两轮电动车的出行半径较短&#xff0c;需要频繁充电&#xff0c;因此在城市中设置两轮车充电桩就非常有必要了。城市中的充电桩不仅能解决两轮车…

C语言--sprintf()函数的用法

一.sprintf()语法 sprintf&#xff08;&#xff09; 是一个 C 语言中的函数&#xff0c;用于将格式化的数据写入一个字符串中。它的用法与 printf() 函数相似&#xff0c;printf&#xff08;&#xff09;函数是将内容输出到屏幕上&#xff0c;而sprintf()函数是将格式化的内容输…

基于大模型和向量数据库的 RAG 示例

1 RAG 介绍 RAG是一种先进的自然语言处理方法&#xff0c;它结合了信息检索和文本生成技术&#xff0c;用于提高问答系统、聊天机器人等应用的性能。 2 RAG 的工作流程 文档加载&#xff08;Document Loading&#xff09; 从各种来源加载大量文档数据。这些文档…

基于微信小程序的校园跑腿小程序,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

SpringBoot(接受参数相关注解)

文章目录 1.基本介绍2.PathVariable 路径参数获取信息1.代码实例1.index.html2.ParameterController.java3.测试 2.细节说明 3.RequestHeader 请求头获取信息1.代码实例1.index.html2.ParameterController.java3.测试 2.细节说明 4.RequestParameter 请求获取参数信息1.代码实例…

08-java基础-锁之AQSReentrantLockBlockingQueueCountDownLatchSemapho

文章目录 0&#xff1a;AQS简介-常见面试题AQS具备特性state表示资源的可用状态AQS定义两种资源共享方式AQS定义两种队列自定义同步器实现时主要实现以下几种方法&#xff1a;同步等待队列条件等待队列 1&#xff1a;AQS应用之ReentrantLockReentrantLock如何实现synchronized不…

如何选购自助咖啡机?这三点是关键!

在竞争激烈的咖啡市场中&#xff0c;既有知名咖啡连锁品牌&#xff0c;也有众多咖啡馆和小型连锁店&#xff0c;无人智能饮品机要想在激烈的竞争中脱颖而出&#xff0c;发挥自身优势&#xff1a;快速便捷是关键&#xff0c;同时好的口感才能留客&#xff0c;因此饮品机的选购成…

MES管理系统中电子看板都有哪些类型?

随着工业信息化和智能制造的不断发展&#xff0c;MES管理系统已经成为现代制造业不可或缺的重要工具。MES管理系统通过集成和优化生产过程中的各个环节&#xff0c;实现对生产过程的实时监控、调度和管理&#xff0c;提高生产效率和质量。 在生产制造过程中&#xff0c;看板管…

第七届国际通信与网络工程会议(ICCNE 2024)即将召开!

2024年第七届国际通信与网络工程会议&#xff08;ICCNE 2024&#xff09;将于7月26-28日在越南岘港召开。本次会议由维新大学主办&#xff0c;岘港大学、胡志明市科技大学协办。ICCNE 2024旨在为来自行业和学术界的研究人员、从业者和专业人士提供论坛&#xff0c;分享其最新研…

vid2vid(Video-to-Video Synthesis)论文详读和理解

论文&#xff1a;https://arxiv.org/abs/1808.06601 代码&#xff1a;https://github.com/NVIDIA/vid2vid

Linux系统部署Swagger Editor结合内网穿透实现公网管理本地接口文档

文章目录 Swagger Editor本地接口文档公网远程访问1. 部署Swagger Editor2. Linux安装Cpolar3. 配置Swagger Editor公网地址4. 远程访问Swagger Editor5. 固定Swagger Editor公网地址 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xf…

Win7安装 .NET Framework 4.6.2 产生阻滞的问题

如图&#xff1a; 解决方案&#xff1a;安装补丁 windows6.1-KB976932-X64 补丁下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1_pIflWRKriAheAweJVkXFQ 提取码&#xff1a;6a1q

如何在Linux部署Docker Registry本地镜像仓库并实现无公网IP远程连接

文章目录 1. 部署Docker Registry2. 本地测试推送镜像3. Linux 安装cpolar4. 配置Docker Registry公网访问地址5. 公网远程推送Docker Registry6. 固定Docker Registry公网地址 Docker Registry 本地镜像仓库,简单几步结合cpolar内网穿透工具实现远程pull or push (拉取和推送)…

HTML静态网页成品作业(HTML+CSS)——电影加勒比海盗介绍设计制作(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

供应IMX290LQR-C芯片现货

长期供应各品牌芯片现货&#xff0c;SONY索尼SONY索尼CMOS/CCD芯片全系列全新现货优势出&#xff1a; IMX225LQR-C IMX415-AAQR-C IMX290LQR-C imx273llr-C IMX397CLN-C IMX637-AAMJ-C IMX647-AAMJ-C IMX991-A***-C IMX991-AABJ-C IMX287LLR-C IMX287LQR-C IMX297L…