进程间通信(二)

简介: 进程间通信

命名管道


概念


匿名管道应用的一个限制就是只能在具有公共祖先的进程间通信;如果想要在不相关的进程之间交换数据,就可以使用命名管道,命名管道是一种特殊类型的文件


假如进程a指向一个名为named_pipe的文件(保存在磁盘上),此时又有一个进程b也要指向文件named_pipe;由于两个进程指向同一个文件,则操作系统便只会创建一个对应的文件结构体struct file供进程使用;进程a此时要向文件内写入,进程b要向文件中读取,此情此景是不是和上面匿名管道一致呢?


不同的是这次的进程没有任何关系,通过打开指定文件(路径+文件名),给两进程提供一份共同的资源,也称作命名管道

图解:


cd944434555ba2211d387662f2d2cbf9_4b5500c3904f48dcac034b02d1bbbf5b.png


创建命名管道


先介绍创建命名管道的函数

int mkfifo(const char *pathname, mode_t mode);


返回值:

On success mkfifo() returns 0. In the case of an error, -1 is returned


创建两个进程,进行通信

student.cpp


学生对老师说话


#include"test.hpp"
int main()
{
    cout<<"student begin: "<<endl;
    int wfd=open(NAME_PIPE,O_WRONLY);
    cout<<"student end: "<<endl;
    if(wfd<0)
    {
        exit(1);
    }
    char buffer[1024];
    while(true)
    {
        cout<<"Please say: ";
        fgets(buffer,sizeof(buffer),stdin);
        if(strlen(buffer)>0)
        {
            buffer[strlen(buffer)-1]=0;
        }
        ssize_t s=write(wfd,buffer,strlen(buffer));
        assert(s==strlen(buffer));
        (void)s;
    }
    close(wfd);
    return 0;
}


teacher.cpp

老师读取学生说的话


#include"test.hpp"
int main()
{
    bool r=creatfifo(NAME_PIPE);
    assert(r==true);
    (void)r;
    cout<<"teacher begin: "<<endl;
    int rfd=open(NAME_PIPE,O_RDONLY);
    cout<<"teacher end: "<<endl;
    if(rfd<0)
    {
        exit(1);
    }
    char buffer[1024];
    while(true)
    {
        ssize_t s=read(rfd,buffer,sizeof(buffer)-1);
        if(s>0)
        {
            buffer[s]=0;
            cout<<"student->teacher: "<<buffer<<endl;
        }
        else if(s==0)
        {
            cout<<"teacher quit"<<endl;
            break;
        }
        else
        {
            cout<<"errno"<<strerror(errno)<<endl;
            break;
        }
    }
    close(rfd);
    removefifo(NAME_PIPE);
    return 0;
}

test.hpp


#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include<cerrno>
#include<cassert>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
using namespace std;
#define NAME_PIPE "/tmp/mypipe.named"
bool creatfifo(const string &path)
{
    int n=mkfifo(path.c_str(),0600);
    if(n==0)
    {
        return true;
    }
    else 
    {
        cout<<"errno: "<<errno<<strerror(errno)<<endl;
        return false;
    }
}
void removefifo(const string&path)
{
    int n=unlink(path.c_str());
    assert(n==0);
    (void)n;
}


判断语句assert和if的区别,前者是明确结果进行判断,后者是不清楚结果进行判断

这里解释一下(void)n的作用,首先在debug版本下,该语句和上一条判断会被执行;在release版本下,判断语句assert(n==0)是不会被执行的,当执行下一条语句是,程序会报错因为变量n产生之后并没有被使用,所以(void)n的作用就是使变量被使用一次


1b297355224a2ce3efe594603a7a7d29_a35ede1984ff47abb8c8ece8531aab1f.png


简单的命名管道就完成了!!!


system v共享内存


概念


共享内存的概念:通过让不同的进程看到同一个内存块的方式称作共享内存


上面两种管道实现进程间通信都是基于文件完成的,共享内存是基于内存完成的,原理解释如下:

假设存在两个进程,分别通过自己的进程空间在页表中映射到内存的不同区域

图解:


5e029aeae3980bb3529d4c6bcc54b966_f7573e5c2cf44398984818da7032132e.png


如果要让这两个进程进行通信,首先操作系统需要在内存中提供一份资源,有了资源,还需要让这两个进程都能看到,所以将创建好的内存资源通过两页表映射到进程中

图解:


cd9c8d5b15c02e14dd783e8c7118d6f7_eb7dc130dd7b4cc295f10bfba8bd1fee.png


在将来如果不想进行通信可以取消进程和内存的关系也称去关联,然后再去释放共享内存


上面只是假设两个进程进行通信,其实系统中只要是想进行通信的进程都可以是共享内存,毕竟这只是一个方式,还有操作系统中一定是存在着许多个共享内存的


代码实现


首先,创建一块共享内存


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


shmglg:本质就是宏,存在两个选项:IPC_CREAT表示如果共享内存不存在,则创建,如果已经存在则进行获取;IPC_EXCL需要和第一个宏一起使用,IPC_CREAT|IPC_EXCL如果共享内存不存在,进行创建,如果存在,返回错误


size_t size:共享内存的大小


key_t key:此参数是为了确保进程可以看到同一内存

key_t ftok(const char *pathname, int proj_id);:进行通过路径名称和项目id便可以到内存中找到同一区域,返回key标识这一内存,也就做到了让不同的内存看到同一资源


返回值On success, a valid shared memory identifier is returned. On errir, -1 is returned, and errno is set to indicate the error.:创建成功,返回内存标识


返回值和key都是标识内存的,那么有什么区别呢???

操作系统中存在着许多的共享内存,肯定不能是杂乱无章的,一定需要进行管理,方法还是:先描述,再组织;所以共享内存其实是由两部分组成的:物理内存块和共享内存的相关属性也就是结构体struct shm;在创建共享内存时,为了保证唯一性使用key标识,也就是将其写入结构体中,当其他进程便可根据这一标识找到共享内存;总之,key标识内存时内核级别的,返回值标识内存是用户级的,都是标识,只是面对的对象不同罢了


17b1557ce4a100d77ce340f61c37c4d4_08d8dad8f3d947d18f57c9e203bee3b3.png


代码实现创建共享内存


key_t getKey()
{
    key_t k=ftok(PATHNAME,PROJ_ID);
    if(k<0)
    {
        cerr<<errno<<" "<<strerror(errno)<<endl;
        exit(1);
    }
    return k;
}
int getShmhelper(key_t k,int flags)
{
    int shmid=shmget(k,MAX_SIZE,flags);
    if(shmid<0)
    {
        cerr<<errno<<" "<<strerrno(errno)<<endl;
        exit(2);
    }
    return shmid;
}
int getShm(key_t k)
{
    return getShmhelper(k,IPC_CREAT);
}
int creatShm(key_t k)
{
    return getShmhelper(k,IPC_CREAT|IPC_EXCL|0600);
}

查看生成的共享内存 指令ipcs -m


324811c1e2b755d0ffe17216e15ddcae_e256ddf18b3f421c9dfd6a676d408154.png


一般而言,当进程执行完毕比那会退出,生命周期也就结束;这里很奇怪,当第二次生成共享内存时,进程报错,并且报错原因还是文件已经存在,由此可知:共享内存的生命周期是随操作系统的,不是随进程的


这里就引入了一个函数来结束共享内存的生命

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


shmid:标识共享内存


cmd:也就宏,这里只介绍一个选项IPC_RMID将共享内存杀掉


共享内存的内核结构体


d287de85dbb7bb84c03108a48bf5caff_599c502417954b218e741641adb2bd61.png


包含共享内存的许多属性,包括标识共享内存的

key也在其中


代码实现 shmctl


void delShm(int shmid)
{
    if(shmctl(shmid,IPC_RMID,nullptr)==-1)
    {
        cerr<<errno<<" "<<strerror(errno)<<endl;
    }
}

6aee4915c9ef5b15034a1f0f6221ff67_323d8236786c4377b3862a2f12205a43.png


共享内存在进程结束之后便也被删除了


共享内存创建之后,接下来就是将进程与共享内存建立联系

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


shmid:标识共享内存

shmaddr:指定共享内存通过页表映射到进程空间的某一区域,一般设置为空

shmflg:设置进程的读写权限,一般设置为空

返回值:共享内存通过页表映射到进程空间某一区域的起始地址

void*attachShm(int shmid)
{
    void*mem=shmat(shmid,nullptr,0);
    if((long long)mem==-1L)
    {
        cerr<<"shmat: "<<errno<<" "<<strerror(errno)<<endl;
        exit(3);
    }
    return mem;
}


进程与共享内存建立联系之后,可以进行查看


fbcc1bc91426f0618ae7621dc6333c29_08f134c5481b49f2bfed31aa7377c8eb.png


当前存在一个进程与共享内存建立联系


建立联系之后,紧接着就是进行通信

代码实现两进程实现通信

client.cpp


int main()
{
    key_t k=getKey();
    printf("key: %x\n",k);
    //获取共享内存
    int shmid=getShm(k);
    printf("shmid: %d\n",shmid);
    //建立联系
    char*start=(char*)attachShm(shmid);
    printf("attach success,address start: %p\n",start);
    const char*message="hello server,我是另一个进程,咱俩正在通信";
    pid_t id=getpid();
    int cnt=0;
    //通信
    while(true)
    {
        snprintf(start,MAX_SIZE,"%s[pid:%d][消息编号:%d]",message,id,cnt++);
    }
    //去关联
    detachShm(start);
    return 0;
}


server.cpp


int main()
{
    key_t k=getKey();
    printf("key:%x\n",k);
    //创建共享内存
    int shmid=creatShm(k);
    printf("shmid: %d\n",shmid);
    //建立联系
    char*start=(char*)attachShm(shmid);
    printf("attach success,address start: %p\n",start);
    //通信
    while(true)
    {
        printf("client say: %s\n",start);
    }
    //去关联
    detachShm(start);
    //删除共享内存
    delShm(shmid);
    return 0;
}



完成通信之后,先将两进程去关联,再删除共享内存


int shmdt(const void *shmaddr);


shmaddr是共享内存映射到进程空间地址的起始地址


返回值:失败返回-1


void detachShm(void* start)
{
    if(shmdt(start)==-1)
    {
        cerr<<"shmdt: "<<errno<<" "<<strerror(errno)<<endl;
    }
}


总结


共享内存的优点:

在所有进程间通信中,速度是最快的,能够大大地减少拷贝次数


对比:

管道

356a79d4b0486a836aafd8305b66a857_985d53a0746e4c1896ca7288a1ac7690.png


共享内存


587a124e40644626d563d930fd5b2103_1b3f8327cf314764838e7fa379a1e997.png


共享内存的缺点:对数据没有包含操作


目录
相关文章
|
9月前
|
存储 并行计算 算法
C++进程间通信之共享内存
C++进程间通信之共享内存
563 0
|
1月前
|
消息中间件 负载均衡 安全
进程间通信
数据传输:一个进程需要将它的数据发送给另一个进程资源共享:多个进程之间共享同样的资源。通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。进程间通信的本质:让不同的进程先看到同一份资源,但是这份资源不能由双方进程提供,而通常是由操作系统所提供!
26 9
进程间通信
|
9月前
|
API
IPC(进程间通信)(下)
IPC(进程间通信)(下)
34 0
|
9月前
|
存储 消息中间件 Linux
IPC(进程间通信)(上)
IPC(进程间通信)
51 0
|
Unix
进程间通信(一)
进程间通信
53 0
|
消息中间件
进程间通信(非常实用)
进程间通信(非常实用)
|
Linux
2.10~2.29 进程间通信(下)
2.10~2.29 进程间通信
96 0
2.10~2.29 进程间通信(下)
|
Linux
2.10~2.29 进程间通信(上)
2.10~2.29 进程间通信
79 0
2.10~2.29 进程间通信(上)
|
开发者
进程间通信 | 学习笔记
快速学习进程间通信,介绍了进程间通信系统机制, 以及在实际应用过程中如何使用。
87 0
进程间通信——共享内存
进程间通信——共享内存
199 0
进程间通信——共享内存