首页 > 系统相关 >进程间通信(管道,共享内存)包含原理剖析

进程间通信(管道,共享内存)包含原理剖析

时间:2024-08-24 16:51:45浏览次数:15  
标签:剖析 include int 间通信 管道 key 进程 共享内存

通信的本质

因为进程具有独立性,我们要进行通信的成本一定不低,我们要先让不同的进程看到同一份资源,之后再进行通信。所以,通信的本质是:1.操作系统直接或间接给通信双方的进程提供内存空间2.要通信的进程,必须看到同一份资源!不同的通信类型的本质就是:上面所说的资源是OS中哪一个模块提供的(比如公共资源是文件系统提供的那么就是管道通信)。

匿名管道

工作原理

每个进程都有自己的task_struct结构体,其中又有一个指针,指向自己的文件描述符表,文件描述符表中又保存了所有文件描述符,通过文件描述符就可以对文件进行读写操作。有了这些前置知识,我们直接先上原理图(省去了task_struct)
第一步:在父进程中调用pipe函数,该函数会生成一个管道文件,并开启读端和写端,将读写操作符通过传入的参数进行返回。
第二步:创建子进程,子进程会继承父进程的文件描述符表,同样可以对管道文件有读写操作。
第三步:分别关闭父进程的读端和子进程的写端,使得形成只能父进程往里写,子进程从中读的半双匿名管道

代码实现

#include<iostream>
#include<unistd.h>
#include<cassert>
#include<cstdlib>
#include<cstring>
using namespace std;


int main() {
//第一步,创建管道文件,打开读写端
    int pipefd[2];
    int n = pipe(pipefd);
    assert(n == 0);
//第二步,fork
    int fd = fork();
    assert(fd >= 0);
    if (fd == 0) {
        //子进程进行写入
        int cnt = 0;
        close(pipefd[0]);//关闭读
        char buffer[1024];
        const char* s = "你好我是子进程,我正在写入";
        while (true) {
            snprintf(buffer, sizeof(buffer) - 1, "pit->%d:%s\n", getpid(), s);
            write(pipefd[1], buffer, strlen(buffer));
            cout << "count: " << ++cnt << endl;

            sleep(2);
        }
        exit(0);
    }
    close(pipefd[1]);
    char buffer[1024];

    while (true) {
        ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if (s > 0) { //读成功
            cout << "get message: " << buffer << endl;
        }
    }
    return 0;
}

读写规则

当没有数据可读时
        O_NONBLOCK disable(阻塞调用):read调用阻塞,即进程暂停执行,一直等到有数据来为止
        O_NONBLOCK enable(非阻塞调用):read调用返回-1errno值为EAGAIN
当管道满的时候
        O_NONBLOCK disable(阻塞调用):
write调用阻塞,直到有进程读走数据。
        O_NONBLOCK enable(非阻塞调用):write调用返回-1errno值为EAGAIN
如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭write操作会产生SIGPIPE信号,进而导致write进程退出

管道特性

1.管道的生命进程随进程
2.管道可以用来进行具有血缘关系的进程之间通信,常用于父与子
3.管道是面向字节流的
4.管道是半双工通信(单向通信)
5.互斥与同步机制--对共享资源进行保护的方案

命名管道

工作原理

由于匿名管道的使用有一个局限,只能应用于具有血缘关系的进程间通信。我们想在不相关进程之间交换数据,就可以使用FIFO文件来做这项工作,它经常被称为命名管道。那么命名管道又是如何让不同的进程,看到同一份资源的?答案是让不同的进程打开指定名称(路径+文件名)的同一个文件,这就是命名管道的工作原理

代码实现

命名管道代码思路更加简单,只需让通信的进程通过mkfifo函数创建一个管道文件,后续进行正常的读写就可以了。

//创建fifo文件的代码
bool creatFifo(const string& s) { //s是文件路径+文件名
    umask(0);
    int n = mkfifo(s.c_str(), 0600); //给定文件名、权限
    if (n == 0)return true;
    cout << "errno:" << errno << " string er:" << strerror(errno) << endl;
    return false;
}

void removeFifo(string& s) { //s是文件路径+文件名
    int n = unlink(s.c_str());
    assert(n == 0);
    (void)n;
}

共享内存

工作原理

通过让不同进程,看到同一个内存块的方式就是共享内存。
我们直接上图,步骤先后顺序在图中有标号

认识接口

notice:未提及的参数如想了解可自行从手册查找,在此篇文章中不重要。

#include<sys/types.h>
#include<sys/ipc.h>

ket_t ftok(const char*pathname,int projectid)

函数功能:通信的进程通过相同的位置和进程id即可获取相同的key值
返回值:用于后续创建共享内存
pathname:路径
projectid:进程id

#include<sys/ipc.h>
#include<sys/shm.h>

int shmget(ket_t key,size_t size,int shmflg)

函数功能:申请一块共享内存的空间
返回值:是数组下标,但是跟文件系统是两套,只需知道后续操作用它作标识符即可
key:通过ftok函数获取的key值
size:共享内存的大小
shmflag:位图,如:IPC_CREAT表示不存在则创建,存在则获取;IPC_EXCL无法单独使用,可以IPC_CREAT|IPC_EXCL表示不存在则创建,存在就返回错误,可以保证给用户的一定是一个新的shm

//shmid是shmget的返回值,如果将cmd设置为IPC_RMID表示释放共享内存
int shmctl(int shmid,int cmd,shmid_ds *buf)

//关联操作,返回值为地址,后续直接一端向该地址写,另一端读,即可完成通信
void* shmat(int shmid,const void*shmaddr,int shmflg)

//去关联操作,shmaddr为关联操作获取的地址,该函数失败返回-1
int shmdt(const void*shmaddr)

代码实现

//file name: comm.hpp
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<cerrno>
#include<cstring>
#include<cstdbool>
#include<unistd.h>

#define PATH_NAME "."
#define PROH_ID 0x66
using namespace std;

//通过ftok函数获取key
int getKey() {
    key_t key = ftok(PATH_NAME, PROH_ID);
    if (key < 0) {
        cerr << errno << ":" << strerror(errno) << endl;
        exit(1);
    }
    return key;
}
//创建共享内存
int creatShm(key_t key) {
    int shmid = shmget(key, 1024, IPC_CREAT | IPC_EXCL | 0600);
    if (shmid < 0) {
        cerr << errno << ":" << strerror(errno) << endl;
        exit(2);
    }
    return shmid;
}

int getShm(key_t key) {
    int shmid = shmget(key, 1024, IPC_CREAT);
    if (shmid < 0) {
        cerr << errno << ":" << strerror(errno) << endl;
        exit(2);
    }
    return shmid;
}
//关联
void* attchShm(int shmid) {
    void* ad = shmat(shmid, nullptr, 0);
    if ((long long)ad < 0) {
        cerr << errno << ":" << strerror(errno) << endl;
        exit(3);
    }

    return ad;
}
//去关联
void detchShm(void* ad) {
    int n = shmdt(ad);
    if (n < 0) {
        cerr << errno << ":" << strerror(errno) << endl;
        exit(4);
    }
}
//删除共享内存
void desShm(int shmid) {
    if (shmctl(shmid, IPC_RMID, nullptr) == -1) {
        cerr << errno << ":" << strerror(errno) << endl;
        exit(5);
    }
}

这个文件主要是对上面接口进行封装,以便服务端和客户端进行使用

#include"comm.hpp"


int main() {
    key_t key = getKey();
    cout << "获取key成功" << endl;

    int shmid = creatShm(key);
    cout << "创建共享内存成功" << endl;

    void* ad = attchShm(shmid);
    cout << "进程和共享内存挂接成功" << endl;

///发消息
    char s[1024] = "我是client我在发送消息";
    int cnt = 1;
    while (true) {

        sprintf(s, "%s:pid[%d]cnt[%d]\n", "我是client我在发送消息", getpid(),
                cnt++);
        memcpy(ad, s, sizeof(s));
        sleep(2);
    }

    detchShm(ad);
    cout << "去关联成功" << endl;


    return 0;
}

该文件为客户端的代码,就是按照共享内存的原理,通过事先规定好的路径和projectid获取到key值、通过key值创建共享内存、关联、向共享内存中发送数据、去关联、删除共享内存

#include"comm.hpp"
int main() {
    key_t key = getKey();
    cout << "获取key成功" << endl;

    int shmid = getShm(key);
    cout << "创建共享内存成功" << endl;

    void* ad = attchShm(shmid);
    cout << "进程和共享内存挂接成功" << endl;

///收消息
    while (true) {
        printf("%s", ad);
        sleep(2);
    }

    detchShm(ad);
    cout << "去关联成功" << endl;

    desShm(shmid);
    cout << "删除共享内存成功" << endl;

    return 0;
}

这个文件即为服务器的代码,通过与客户端相同的路径和projectid获取到key值、通过key值创建共享内存、关联、在共享内存中读取数据、去关联、删除共享内存
注意:运行代码时,应先运行客户端创建好共享内存,再运行服务器。

其他进程间通信方法

除了上述两种方法之外,进程间通信的方法还有,信号量、信号、socket用于不同主机间进程通信等。其中信号量在生产者消费者模型精讲-CSDN博客中有讲解和使用,另外的的方法由于篇幅问题,先不进行讲解。如有需要可以评论区留言。

标签:剖析,include,int,间通信,管道,key,进程,共享内存
From: https://blog.csdn.net/qq_55008871/article/details/141428485

相关文章

  • Android开发 - Binder 类进程间通信(IPC)的机制解析
    什么是BinderBinder是一种用于进程间通信(IPC)的机制,允许不同的进程(或者不同的组件)相互交互,提供了跨进程通信(IPC)的基础。它允许一个进程中的对象(如服务)被另一个进程中的代码(如应用组件)调用。Binder是一种特殊的对象,它能够在不同进程之间传递数据和调用方法Binder的作用进......
  • 魔兽世界服务端TrinityCore连接池源码剖析
    简介魔兽世界服务器中数据库使用mysql来存储,并且数据库模块是直接嵌入在serve中,并没有单独的DBserver在魔兽连接池中有两种连接池,一种是同步连接池,还有异步连接池连接池相关源码目录TrinityCore-master\src\server\database\Database连接池具体文件:DatabaseWorkerP......
  • 重头开始嵌入式第二十六天(Linux系统编程 进程间通信 IPC)
    目录IPC进程间通信1.管道通信管道的特性使用流程无名管道1.创建并打开管道:2.无名管道的读写:3.关闭管道: close();4.使用例子:有名管道1、创建:mkfifo2、打开有名管道 open3、管道的读写: 文件IO4、关闭管道:5、卸载管道:remove();IPC进程间通信进程间通信(In......
  • Python中共享内存对进程池的影响
    在Coding过程中,发现了共享内存会对进程池产生影响。原始程序设计的思路是,在类中使用进程池创建不同的进程,这些进程间通过共享内存的方式控制一些变量。代码如下importctypesfrommultiprocessingimportPool,ValueclassEx:def__init__(self,value):self......
  • Kubernetes: client-go 源码剖析(一)
    kubernetes:client-go 系列文章:Kubernetes:client-go源码剖析(一)Kubernetes:client-go源码剖析(二)0.前言在看 kube-scheduler 组件的过程中遇到了 kube-scheduler 对于 client-go 的调用,泛泛的理解调用过程总有种隔靴搔痒的感觉,于是调转头先把 client-go 理清楚......
  • 触摸输入故障深度剖析:tiptsf.dll修复的高级策略详解
    针对tiptsf.dll文件丢失或损坏导致的触摸输入问题,可以采取以下专业修复步骤:1.安全模式启动:首先,尝试重启计算机进入安全模式。这有助于防止加载可能干扰修复过程的第三方服务。2.系统还原点:检查是否有最近的系统还原点。通过“控制面板”->“系统”->“系统保护”->......
  • 进程间通信方式详解
    正文每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。Linux内核提供了不少进程间通信的机制,我们来一起瞧瞧有哪些?管道如果你学过Linux命令,那你肯定很熟悉「|」这个竖线。$psauxf|grep......
  • 字符串信息检测原理代码剖析
    想要用单片机识别一长串字符并执行对应指令,有两种办法:数组法和循环法错误的实例:if(RXDATE=='L') { if(RXDATE=='E') { if(RXDATE=='D') { if(RXDATE=='1') { LED1=0; } if(RXDATE......
  • IPC对象通信方式---共享内存 | 网络通信 -编程
    共享内存共享内存机制其允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中,一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取错做读出,从而实现了进程间的通信。是内核预留的内存空间,最......
  • XSI机制的进程间通信
    XSI机制的进程间通信1、XSI介绍:什么是XSI:X/Open国际联盟有限公司是一个欧洲基金会,它的建立是为了向UNIX环境提供标准,XSI是X/OpenSystemInterface的缩写,也就是X/Open设计的系统接口。X/Open的主要的目标是促进对UNIX系统、接口、网络和应用的开放式系统协议的制定。它还促......