进程间的通信

引言:

  在一个操作系统中,我们可以创建非常多的进程,但是进程具有独立性,不同的进程之间是非常独立的,即使是父子进程,由于写时拷贝的存在,在大部分的情况下,他们之间的独立性也非常高。所以,如果我们想要实现进程之间的通信,应该通过怎样的方式呢?

管道

管道其实就是文件,我们想要实现进程之间的通信,那么我们就要找到不同进程之间公共的部分,不同的进程共享文件系统,意思就是不同的进程可以访问同一个文件,在这种情况下,如果我们一个进程向一个文件写入信息,另一个进程读取这个文件的信息,这样其实就是实现了进程之间的通信。

匿名管道

匿名管道:匿名管道是UNIX系统进程间通信(IPC)的一种基本形式,它允许具有血缘关系的进程之间进行数据传输。在Linux环境下,匿名管道是通过内核中的缓冲区实现的,这个缓冲区的大小是有限的,通常由操作系统决定。匿名管道是半双工的通信方式,即数据只能单向流动,要么是父进程向子进程发送数据,要么是子进程向父进程发送数据。

简而言之:匿名管道就是父进程以读和写的方式打开一个文件,然后创建子进程,子进程继承了父进程的读取和写入的权限,通过我们合理的设计(比如关闭父进程的读端,关闭子进程的写端),这样就实现了进程之间的通信。

创建匿名管道的方式

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
const int gbuffer_size = 1024;
bool createPipe()
{
    int pipefd[2] = { 0 };
    int ret = ::pipe(pipefd);
    
    if(ret == -1)
    {
        std::cerr << "创建管道失败" << std::endl;
        return false;
    }

    pid_t pid = ::fork();

    if(pid == -1)
    {
        std::cerr << "创建子进程失败" << std::endl;
        return false;
    }

    if(pid == 0)
    {
        // 子进程
        // 关闭子进程的写端
        ::close(pipefd[1]);
        char buffer[gbuffer_size] = { 0 };
        int n = ::read(pipefd[0],buffer,sizeof(buffer) - 1);
        buffer[n] = 0; // 将字符流转换成字符串
        std::cout << "子进程读到了:" << buffer << std::endl;
        exit(0);
    }
    else
    {
        //父进程
        ::close(pipefd[0]);
        char buffer[gbuffer_size] = "你读到了吗?";
        ::write(pipefd[1],buffer,sizeof(buffer));
        // 回收子进程
        pid_t rid = ::waitpid(pid,nullptr,0);
        if(rid > 0) std::cout << "等待成功" << std::endl;
    }
    return true;
}
int main()
{
    
    createPipe();
    // 创建管道的方式
    return 0;
}

总的来说,创建匿名管道的方式其实本质就是子进程继承了父进程的读写端,通过文件系统实现进程之间的通信。

有名管道

我们刚才所说的匿名管道是管道的一种情况,他并不具有泛用性。他只能针对于父子继承之间进行通信,但是如果是两个陌生的进程应该如何进行通信呢?

这里使用的就是我们的有名管道

创建有名管道

创建有名管道有系统函数和命令行两种方式,其实操作方式大差不差。

创建一个有名管道后,一个进程通过读端进行访问,一个进程通过写端进行访问。

管道的深度理解

这里我们在理解的时候,我们可以将创建管道的过程理解成创建文件的过程,但是并不完全正确,试想如果创建管道后都是直接写入文件,我们写入文件的时候调用系统调用访问磁盘这样不会太过耗时吗? 所以我们的内核设计者同样考虑了这一点,管道看似是文件,其实只是拥有文件描述符的缓冲区而已。

现在我们直接给一段通过管道实现进程之间通信的示例代码。

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>

const std::string gpathname = "fifo";
const int gbuffersize = 1024;
void createNewFifo()
{
    int n = ::mkfifo(gpathname.c_str(),0644);
    if(n > 0) std::cout << "create fifo fail!" << std::endl;
    if(n < 0){
        std::cerr << "make fifo fail!" << std::endl;
        ::exit(1);
    }
}
int main()
{
    createNewFifo();
    int fd = open(gpathname.c_str(),O_WRONLY);
    char buffer[gbuffersize] = "我是Client进程";
    ::write(fd,buffer,sizeof(buffer) - 1);
    while(true);
    return 0;
}
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>

const std::string gpathname = "fifo";
const int gbuffersize = 1024;
int main()
{
    int fd = ::open(gpathname.c_str(),O_RDONLY | O_NONBLOCK);
    char buffer[gbuffersize] = { 0 };
    int n = ::read(fd,buffer,sizeof(buffer) - 1);
    buffer[n] = 0;
    std::cout << "I am Server,我读到了:" << buffer << std::endl;
    return 0;
}

我们通过这样的代码就可以类似的实现进程之间的通信

system V共享内存

共享内存区是最快的IPC形式。⼀旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递 不再涉及到内核,换句话说是进程不再通过执⾏进⼊内核的系统调⽤来传递

共享内存是什么意思内,我们每个进程都有着自己独立的虚拟地址空间,然后通过页表映射到具体的物理地址空间,具体是什么意思呢?就是说,A进程中有个变量的地址是0xFFFFFFFF,B进程中也有个变量的地址的0xFFFFFFFF,但是呢在内存中这两个变量的存储位置显然是不同的,这就是涉及到虚拟内存和物理内存通过页表进行映射。

所以,我们的内核的设计者设计了一个系统调用,让我们的不同的进程映射同一块物理地址空间,然后通过这段物理地址空间实现进程之间的通信。

这种进程间通信的方式就被我们称作共享内存。

创建共享内存的系统级接口

shmget() 创建共享内存

这里的shmflg的原理和::open中的flag差不多

shmat()

将我们的进程链接到对应的共享内存上,返回的虚拟起始地址。类似于malloc的返回值。

shmdt()

shmctl

实现包括关闭共享内存的多重功能的系统级调用

共享内存的特点

IPC资源必须删除,否则不会⾃动清除,除⾮重启,所以system V ipc的资源必须进行手动的回收,并且我们称他的生命周期是随内核的。

system V信号量

当我们的多个执行流同时访问同一块内存空间的时候,同步和互斥使我们必须面对的问题,所以,共享内存可以让多个进程进行挂接,因此,同步和互斥就是共享内存需要处理的问题。

信号量

信号量的本质是锁和计数器形成的结构体,锁的作用是为了保证我们不同执行流之间的互斥的关系,避免不同的执行流在同一时间访问到同样的内存。计数器的作用是为了保证同步的关系。在生产者-消费者模型中,只有生产者创建了数据,消费者才能获取,因此,这里引入计数器,为了方便告知我们的消费者是否还有数据。

把这些内容进行的统一的封装我们就叫做信号量。

申请资源,计数器--,P操作

释放资源,计数器++,V操作

总结:

这里只是本地状态下实现的进程之间的通信的方式,在计算机网络中,我们其实也可以有着其他的方式访问实现本地的进程间的通信。

世界地图矢量数据可以通过多种网站进行下载。以下是一些提供免费下载世界地图矢量数据的网站: 1. Open Street Map (https://www.openstreetmap.org/): 这个网站可以根据输入的经纬度或手动选定范围来导出目标区域的矢量图。导出的数据格式为osm格式,但只支持矩形范围的地图下载。 2. Geofabrik (http://download.geofabrik.de/): Geofabrik提供按洲际和国家快速下载全国范围的地图数据数据格式支持shape文件格式,包含多个独立图层,如道路、建筑、水域、交通、土地利用分类、自然景观等。数据每天更新一次。 3. bbbike (https://download.bbbike.org/osm/): bbbike提供全球主要的200多个城市的地图数据下载,也可以按照bbox进行下载。该网站还提供全球数据数据格式种类齐全,包括geojson、shp等。 4. GADM (https://gadm.org/index.html): GADM提供按国家或全球下载地图数据的服务。该网站提供多种格式的数据下载。 5. L7 AntV (https://l7.antv.antgroup.com/custom/tools/worldmap): L7 AntV是一个提供标准世界地图矢量数据免费下载的网站。支持多种数据格式下载,包括GeoJSON、KML、JSON、TopJSON、CSV和高清SVG格式等。可以下载中国省、市、县的矢量边界和世界各个国家的矢量边界数据。 以上这些网站都提供了世界地图矢量数据免费下载服务,你可以根据自己的需求选择合适的网站进行下载
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值