C++项目实战-多进程(一篇文章)(三)

简介: C++项目实战-多进程(一篇文章)(三)

有名管道FIFO

FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于"有血缘关系"的进程间。但通过FIFO不相关的进程也能交换数据。

FIFO是Linux基础文件类型中的一种。但,FIFO文件磁盘上没有数据块,仅仅用来标识内核中的一条通道。实际上是在读写内存通道,这样就是实现了进程间通信。

有名管道使用注意事项

一个为只读而打开一个管道的进程会阻塞,直到另一个进程为只写打开管道

一个为只写而打开一个管道的进程会阻塞,直到另一个进程为只读打开管道

创建方式

命令:mkfifo  管道名

库函数:int mkfifo(const char* pathname,mode_t mode);

使用:

       像普通文件一样使用就可以

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
using namespace std;
int main(void)
{
    //以只读的方式打开fifo
    int fd = open("myfifo",O_RDONLY);
    if(fd == -1)
    {
        perror("open");
        exit(-1);
    }
    //不断的读数据
    char buf[256];
    while(1)
    {
        if(read(fd,buf,sizeof(buf)) == 0)
        {
            break; 
        }
        cout<<buf<<endl;
    }
    return 0;
}
//=====================================================
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
using namespace std;
int main(void)
{
    //先创建一个fifo
    int ret = mkfifo("myfifo",0664);
    //以只写的方式打开
    int fd = open("myfifo",O_WRONLY);
    if(fd == -1)
    {
        perror("open");
        exit(-1);
    }
    //向管道中写入数据
    char buf[256];
    strcpy(buf,"我是你爹...");
    while(1)
    {
        sleep(5);
        write(fd,buf,strlen(buf));
    }
    return 0;
}

文件实现IPC

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
using namespace std;
int main(void)
{
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork");
        exit(-1);
    }
    if(pid > 0)
    {
        int fd = open("a.txt",O_WRONLY|O_CREAT,0777);
        char buf[256];
        strcpy(buf,"草泥马...故事开始在哪个梦中...");
        write(fd,buf,strlen(buf));
    }
    else if(pid == 0)
    {
        int fd = open("a.txt",O_RDONLY);
        sleep(2);
        char buf[256];
        while(1)
        {
            int ret = read(fd,buf,sizeof(buf));  
            if(ret == 0)
            {
                break;
            }
            cout<<buf<<endl;
        }
    }
    return 0;
}

内存映射(存储映射IO)

       内存映射是将磁盘文件的数据映射到内存,用户通过修改内存就能够修改磁盘文件。

使用这种方法,首先应通知内核,将一个指定文件映射到存储区中。这个映射工作可以通过mmap函数实现。

       

mmap函数

       void  *mmap(void *adrr,size_t length,int prot,int flags,int fd,off_t offset);

       成功返回创建的映射区首地址,失败返回 MAP_FAILED宏

       adrr:建立映射区的首地址,传NULL

       length:欲创建映射区的大小

       prot:映射区权限(PROT_READ PROT_WRITE PORT_READ|PORT_WRITE)

       flags:标志位参数

               MAP_SHARED:会将映射区所做的操作反映到物理设备上,映射区的数据会自动和磁盘文件进行同步,进程间通信,必须要设置这个选项

               MAP_PRTVATE:不同步

       fd:用来建立映射区的文件描述符

       offset:映射文件的偏移量(4K的整数倍)

munmap函数

       释放建立的映射区

       int munmap(void **addr,size_t length);

       成功0  失败-1

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
using namespace std;
int main(void)
{
    int fd = open("a.txt",O_RDWR|O_CREAT,0777);
    if(fd == -1)
    {
        perror("open");
        exit(-1);
    }
    ftruncate(fd, 100);
    char *p = (char *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(p == MAP_FAILED)
    {
        perror("mmap");
        exit(-2);
    }
    strcpy(p,"aaa");
    munmap(p,4);
    return 0;
}

使用mmap注意事项

1.创建映射区的过程中,隐含着一次对映射文件的读操作

2.当MAP_SHARED时,要求文件具有打开去权限

3.映射区的释放与文件关闭无关。只要映射区建立成功,文件可以随时关闭

   因为我们是对映射区操作就可以了

4.当文件大小为0时,不能创建映射区。否则容易出现总线错误

5.munmap传入的地址一定是首地址

6.文件偏移量必须为4K的整数倍

7.mmap创建映射区的出错概率很高,一定要检查返回值

mmap父子进程通信

       父子等有血缘关系的进程之间可以通过mmap建立映射区来完成数据通信。fork之后父子进程间的mmap映射区是共享的。

       1.打开文件

       2.mmap建立的映射区(但必须要使用NAP_SHARED)

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
int main(void)
{
    //打开一个文件
    int fd = open("a.txt",O_RDWR);
    //父进程创建一个内存映射
    char *p = (char *)mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(p == MAP_FAILED)
    {
        perror("mmap");
        exit(-1);
    }
    //可以及时关闭这个文件了
    close(fd);
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork");
        exit(-1);
    }
    if(pid > 0)
    {
        sleep(2);
        //父读子写(这个无所谓哈)
        cout<<p<<endl;
        wait(NULL);
        munmap(p,100);
    }
    else if(pid == 0)
    {
       strcpy(p,"aaaa");
    }
    if(pid > 0)
        munmap(p,4);
    return 0;
}

 不知道你们能不能体会到这种感觉,其实内存映射很简单。及时通过文件进程通信而已,只不过这样操作会更快更方便而言,本质上与文件实现IPC一样。

  仔细一想,这样带来的函数是什么呢?每一个对比就没用伤害,频繁的在用户态和内核态切换还是蛮浪费时间的。通过文件来通信,减少了切换的频率,而且我们可以看到我们是在用户空间中建立的内存空闲区的。

   通过映射,底层应该是做了值的拷贝。我们只需要操作内存区看可以实现父子之间进行通信。就像操作数组一样方便。其根基是因为fork之后父子进程的内存映射区是一样的。

匿名映射
     
我们看内存映射,还得先打开一个文件,以文件描述符为桥梁来操作,是不是感觉太麻烦了。有没有一种方式不用打开文件就可以操作呢?通过匿名映射

       使用MAP_ANONYMOUS(或MAP_ANON)

       mmap(NULL,4,PROT_READ|PORT_WRITE,MAP_SHARED|MAP_ANON,-1,0);

#include <iostream>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
using namespace std;
int main(void)
{
    //创建匿名映射区
    char *p = (char *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
    if(p == MAP_FAILED)
    {
        perror("mmap");
        exit(-1);
    }
    //创建子进程
    pid_t pid = fork();
    if(pid == -1)
    {
        perror("fork");
        exit(-2);
    }
    if(pid > 0)
    {
        sleep(2);
        cout<<p<<endl;
        wait(NULL);
        munmap(p,4);
    }
    else if(pid == 0)
    {
        strcpy(p,"aaa");
    }
    return 0;
}

这里做一个小小的总结:

1.有关系的进程(父子进程)

       -还没有子进程的时候

               父进程先创建内存映射区

       -创建子进程

       -父子进程共享创建的内存映射区

2.没有关系的进程件通信(通过映射同一块磁盘文件)

       -准备一个IO文件(大小不能为0)

       -进程1:通过磁盘文件创建内存映射区

                     得到内存映射区首地址

       -进程2:通过磁盘文件创建内存映射区

                      得到内存映射区首地址

       -使用内存映射区通信

读端:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <string>
#include <unistd.h>
using namespace std;
#define PATHNAME "./a.txt"
int main(void)
{
    int fd = open(PATHNAME,O_RDONLY);
    if(fd == -1)
    {
        perror("open err");
        exit(-1);
    }
    char *addr = (char *)mmap(NULL,100,PROT_READ,MAP_SHARED,fd,0);
    if(addr == MAP_FAILED)
    {
        perror("mmap err");
        exit(-1);
    }
    while(1)
    {
        sleep(3);
        cout<<addr<<endl;
    }
    munmap(addr,100);
    return 0;
}

写端:

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <string>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
#define PATHNAME "./a.txt"
int main(void)
{
    int fd = open(PATHNAME,O_RDWR|O_CREAT,0777);
    if(fd == -1)
    {
        perror("open err");
        exit(-1);
    }
    truncate(PATHNAME,100);
    char *addr = (char *)mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(addr == MAP_FAILED)
    {
        perror("mmap err");
        exit(-1);
    }
   for(int i=0;i<100;i++)
   {    
        strcpy(addr,"aaa");
        sleep(1);
   }
    munmap(addr,100);
    return 0;
}

共享内存

基本概念

1.共享内存允许两个或多个进程共享物理内存同一块区域(通常被称为段)。由于一个共享内存段会成为一个进程用户空间一部分,因此这种PIC机制无需内核介入。所需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他进程共享同一个段的进程可用。

2.与管道等要求发送进程将数据从用户空间的缓冲区复制进内核和接受进程数据从内核内存复制进用户空间的缓冲区相比,这种IPC技术速度更快。

  害,我都懒得说了。这个不就跟前面一样嘛,我们之前说过所以进程的内核区共享一块物理内存,管道是不是就是在内核去建立一个缓冲区,那不是跟这个原理一样嘛。所以我常说一定要把原理搞清楚。

数据实际上是写在物理内存中的,之前说虚拟地址空间,虚拟地址空间只需要实现数据结构上的页目和页表就可以了。它是方便我们操作的,更方便多进程的实现。

使用步骤(5步,分别调用5个函数)

1.调用shmget()创建一个新共享内存段或者取得一个已经存在的共享内存段的标识符(即由其他进程创建的共享内存段)。这个调用将返回后续调用中用到的共享内存标识符。

2.使用shmat()来附上共享内存段,即使用该段成为调用进程的虚拟地址内存的一部分

3.为了引用这块共享内存,程序需要使用由shmat()调用返回addr值,它指向进程的虚拟地址空间中该共享内存段的起点。

4.调用shmdt()来分离共享内存

5.调用shmctl()来删除共享内存段。只有当当前所有附加内存段的进程的与值分离之后内存段才会销毁。只有一个进程需要执行这一步

int       shmget(key_t key,size_t size,int shmflg);

void *  shmat(int shmid,const void* shmaddr,int shmflg);

int       shmdt(const void *shmaddr);

int       shmctl(int shmid,int cmd,struct shmid_ds *buf);

key_t  fork(const char*pathname,int proj_id);

-------------------------------------------------------------------------------------------------

int  shmget(key_t key,size_t size,int shmflg);

==功能:创建一个新的共享内存段或者获取一个既有的共享内存段的标识,新创建的内存段中的数据都会被初始化为0

==参数:

       key:       key_t类型是一个整型,通过这个找到或者创建一个共享内存。

                       一般以16进制表示,非0值

       size:      共享内存的大小(页的整数倍)

       shmflg:  属性

                       访问权限

                       附加属性:创建/判断共享内存是不是存在(IPC_EXCL)

                                         创建:IPC_CREAT

                                         判断共享内存是否存在:IPC_EXCL|IPC_CREAT|0664

==返回值

       失败 -1    并设置 error

       成功 >0   返回共享内存的引用的ID,后面操作共享内存都是通过这个值

--------------------------------------------------------------------------------------------------------

int *shmat(int shmid,const void *shmaddr,int shmflg);

功能:和当前的进程进行关联

参数:

          shmid:共享内存的标识符(ID),由shmget返回值获取

          shmaddr:申请的共享内存的起始地址,指定NULL,内存指定

          shmflg:对共享内存的操作

                       读:SHM_RDONLY

                       读写:0

返回值:

       成功:返回共享内存的首地址,失败(void *)-1

--------------------------------------------------------------------------------------------------------------

int shmdt(const void* shmaddr);

功能:解除当前进程和共享内存的关联

参数:

          shmaddr   共享内存的首地址

返回值:

           成功 0,失败 -1

-----------------------------------------------------------------------------------------------------------------

int shmctl(int shmid,int cmd,struct shmid_ds *buf);

功能:删除共享内存,共享内存要删除才会消失

          创建共享内存的进程被销毁了对共享内存没用影响

参数:

          shmid:共享内存的ID

          cmd:   要做的操作

                     IPC_STAT  获取共享内存当前的状态

                     IPC_SET    设置共享内存的状态

                     IPC_RMAD  标记共享内存需要被销毁

          buf: 需要设置或者获取的共享内存的属性信息

                     IPC_STAT   buf存储数据

                     IPC_SET     buf中需要初始化数据

                     IPC_RMID  没有用,NULL

-----------------------------------------------------------------------------------------------------------------

key_t fork(const char* pathname,int proj_id);

功能:根据指定路径名和int,生成一个共享内存的key

参数:

       -pathname:指定一个存在的路径

       -proj_id:int类型的值,但是系统调用只会使用其中一个1字节   (范围0~255)

-----------------------------------------------------------------------------------------------------------------    

案例:

server端读数据

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
#define PATHNAME "./"
#define PROJ_ID 0x666
#define SIZE    4096
int main(void)
{
    //生成一个共享内存 key
    key_t key = ftok(PATHNAME,PROJ_ID);
    if(key == -1)
    {
        perror("fork err");
        exit(-1);
    }
    //创建一个共享内存,如果存在则获取。拿到id
    int shmid = shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0664);
    if(shmid == -1)
    {
        perror("shmget err:");
        exit(-1);
    }
    cout<<"shmid:"<<shmid<<endl;
    //和当前进程进行关联,读写操作
    char *addr = (char *)shmat(shmid,NULL,0);
    //进行数据交换
    while(1)
    {
        sleep(1);
        cout<<addr<<endl;
    }
    //分离当前进程
    shmdt(addr);
    //删除共享内存段
    int ret = shmctl(shmid,IPC_RMID,NULL);
    if(ret == -1)
    {
        perror("shmctl err");
        exit(-1);
    }
    return 0;
}

如果出现如下错误: 这个id已经被占用,移除就可

终端输入:

ipcs -m

ipcrm -m 对应的shmid

然后再./server

client端写数据

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <string.h>
using namespace std;
#define PATHNAME "./"
#define PROJ_ID 0x666
#define SIZE    4096
int main(void)
{
    //先获取key
    key_t key = ftok(PATHNAME,PROJ_ID);
    if(key == -1)
    {
        perror("ftok err");
        exit(-1);
    }
    //获取共享内存块的id
    int shmid = shmget(key,SIZE,0);
    if(shmid == -1)
    {
        perror("shmget err");
        exit(-1);
    }
    //和的当前进程进行关联
    char *addr = (char *)shmat(shmid,NULL,0);
    //进行数据交换
    for(int i=0;i<10;++i)
    {
        strcpy(addr,"aaaaa");
    }
    //分离
    shmdt(addr);
    return 0;
}

本来想把信号也放到这里,信号要是进程间通信的一种方式嘛...

感觉篇幅有点长了,我另写一篇文章

相关文章
|
2月前
|
资源调度 Linux 调度
Linux c/c++之进程基础
这篇文章主要介绍了Linux下C/C++进程的基本概念、组成、模式、运行和状态,以及如何使用系统调用创建和管理进程。
48 0
|
6月前
|
存储 调度 C++
【操作系统】进程与线程的区别及总结(非常非常重要,面试必考题,其它文章可以不看,但这篇文章最后的总结你必须要看,满满的全是干货......)
【操作系统】进程与线程的区别及总结(非常非常重要,面试必考题,其它文章可以不看,但这篇文章最后的总结你必须要看,满满的全是干货......)
146 1
|
6月前
|
存储 分布式数据库 API
技术好文:VisualC++查看文件被哪个进程占用
技术好文:VisualC++查看文件被哪个进程占用
|
2月前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
42 0
Linux c/c++之IPC进程间通信
|
2月前
|
Linux C++
Linux c/c++进程间通信(1)
这篇文章介绍了Linux下C/C++进程间通信的几种方式,包括普通文件、文件映射虚拟内存、管道通信(FIFO),并提供了示例代码和标准输入输出设备的应用。
38 0
Linux c/c++进程间通信(1)
|
2月前
|
Linux C++
Linux c/c++之进程的创建
这篇文章介绍了在Linux环境下使用C/C++创建进程的三种方式:system函数、fork函数以及exec族函数,并展示了它们的代码示例和运行结果。
48 0
Linux c/c++之进程的创建
|
7月前
|
C++ iOS开发
C++ 文件操作的技术性文章
C++ 文件操作的技术性文章
40 0
|
2月前
|
Linux C++
Linux c/c++进程之僵尸进程和守护进程
这篇文章介绍了Linux系统中僵尸进程和守护进程的概念、产生原因、解决方法以及如何创建守护进程。
31 0
|
4月前
|
存储 Serverless C++
【C++航海王:追寻罗杰的编程之路】一篇文章带你认识哈希
【C++航海王:追寻罗杰的编程之路】一篇文章带你认识哈希
24 0
|
6月前
|
存储 缓存 NoSQL
Redis系列学习文章分享---第十三篇(Redis多级缓存--JVM进程缓存+Lua语法)
Redis系列学习文章分享---第十三篇(Redis多级缓存--JVM进程缓存+Lua语法)
84 1