《管道与共享内存》进程间通信

进程间通信的宗旨:要让两个不同的进程之间实现通信,前提是要让这两个进程看到同一份(内存空间的)资源
本章节讲述:System V标准的进程间通信
进程间通信的目的:
1.数据传输:一个进程需要将它的数据发送给另一个进程。
2.资源共享:多个进程之间共享同样的资源。
3.通知事件:一个进程需要向另一个或另一组进程发送消息,通知它(们)发生了某种事件(如子进程终止时要通知父进程)
4.进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
管道
管道是一种半双工通信,可以选择方向的单向通信。
本质:是在内核中开辟一段缓冲区(内核空间中的一块内存)
原理:多个进程通过访问同一块内存中的缓冲区实现通信
分类:
1)匿名管道:缓冲区没有标识符,只能用于具有亲缘关系的进程间通信。
2)命名管道:缓冲区具有标识符,可用于同一主机上任意的进程间通信。
通过IO操作来完成对管道的访问。
匿名管道
创建:int pipefd[2]={0};

 头文件:#include<unistd.h>
int pipe(int pipefd[2])	---->>>   pipe(pipefd[2])
返回值:成功0;失败-1。
pipefd[0]:读; pipefd[1]:写。

匿名管道的特性:由于匿名管道没有标识符,无法被其他进程找到, 只能通过子进程复制父进程的方式获取到操作句柄实现通信。因此匿名管道只能用于具有亲缘关系的进程间通信

#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>
using namespace std;
int main()
{
    int pipefd[2]={0};
    pipe(pipefd);
    pid_t id = fork();
    if(0 == id)
    {
        //child write
        close(pipefd[0]);//关闭读端,互斥性
        const char* buf ="hello my dream!";
        int count = 0;
        while(count <= 3)
        {
        write(pipefd[1],buf,strlen(buf));
        sleep(1);
        count++;
        }
    }
    else if(id > 0)
    {
        //parent read
        close(pipefd[1]);关闭写端,互斥性
        char buf[64];
       while(1)
       {
             ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);
             if(s > 0)
            {
              buf[s] = '\0';
              cout<<s<<endl;
              cout<<"father get a msg :"<<buf<<endl;
              sleep(1);
            }
             if(0 == s)
             break;
        }   
    }
    return 0;
}

管道的深入理解:
情况一:(同步)
若读取条件不满足时(写端不关闭,且不再写入),则读入端就会被阻塞

#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>
using namespace std;
int main()
{
    int pipefd[2]={0};
    pipe(pipefd);
    pid_t id = fork();
    if(0 == id)
    {
        //child write
        close(pipefd[0]);
        const char* buf ="hello my dream!";
        int count = 0;
        while(count <= 3)
        {
        write(pipefd[1],buf,strlen(buf));
        sleep(1);
        count++;
        }
    }
    else if(id > 0)
    {
        //parent read
        close(pipefd[1]);
        char buf[64];
       while(1)
       {
             ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);
             if(s > 0)
            {
              buf[s] = '\0';
              cout<<s<<endl;
              cout<<"father get a msg :"<<buf<<endl;
              sleep(1);
            }
        } //若父进程退出则不会有该阻塞情况  
    }
    return 0;
}

在这里插入图片描述
情况二:(同步)
如写入条件不满足时(当读端不读数据,且不关闭文件描述符时),则写入端就会被阻塞。

#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>
using namespace std;
int main()
{
    int pipefd[2]={0};
    pipe(pipefd);
    pid_t id = fork();
    if(0 == id)
    {
        //child write
        close(pipefd[0]);
        const char* buf ="hello my dream!";
        while(1)
        {
        write(pipefd[1],buf,strlen(buf));
        sleep(1);
        }
    }
    else if(id > 0)
    {
        //parent read
        close(pipefd[1]);
        char buf[64];
           int count = 0;
        while(1)
       {
           while(count <= 5)
           {
             ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);
             if(s > 0)
            {
              buf[s] = '\0';
              cout<<s<<endl;
              cout<<"father get a msg :"<<buf<<endl;
              sleep(1);
            }
             if(0 == s)
             break;
            count++;
            }     
       }
    }
    return 0;
}

测试结果:在这里插入图片描述
情况三:写端关闭文件描述符,当读端读完管道数据时,会读到文件结尾,read返回0,且读端不阻塞

#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>
using namespace std;
int main()
{
    int pipefd[2]={0};
    pipe(pipefd);
    pid_t id = fork();
    if(0 == id)
    {
        //child write
        close(pipefd[0]);
        const char* buf ="hello my dream!";
        int count = 0;
        while(count <= 20)
        {
        write(pipefd[1],buf,strlen(buf));
        count++;
        }
        close(pipefd[1]);
    }
    else if(id > 0)
    {
        //parent read
        close(pipefd[1]);
        char buf[64];
       while(1)
       {
             ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);
             cout<<s<<endl;
             if(s > 0)
            {
              buf[s] = '\0';
              cout<<"father get a msg :"<<buf<<endl;
              sleep(1);
            }
        }   
    }
    int status = 0;
    waitpid(id,&status,0);
    cout<<"child exit num is: "<<(status & 0x7F);
    return 0;
}

测试结果:
在这里插入图片描述

情况四:
若读端关闭,写端进程后续可能被进程会直接杀掉(SIGPIPE)

#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
#include<sys/types.h>
using namespace std;
int main()
{
    int pipefd[2]={0};
    pipe(pipefd);
    pid_t id = fork();
    if(0 == id)
    {
        //child write
        close(pipefd[0]);
        const char* buf ="hello my dream!";
        while(1)
        {
        write(pipefd[1],buf,strlen(buf));
        sleep(1);
        }
    }
    else if(id > 0)
    {
        //parent read
        close(pipefd[1]);
        char buf[64];
        int count = 0;
       while(1)
       {
             ssize_t s = read(pipefd[0],buf,sizeof(buf)-1);
             if(s > 0)
            {
              buf[s] = '\0';
              cout<<s<<endl;
              cout<<"father get a msg :"<<buf<<endl;
              sleep(1);
            }
             if(count == 5)
             {
                 close(pipefd[0]);
                break;
            }
             count++;
             cout<<"father exit renturn:"<<s<<endl;
    }
             int status = 0;
        waitpid(id,&status,0);
        cout<<"child exit num is: "<<(status & 0x7F)<<endl;
    }
    return 0;
}

测试结果:
在这里插入图片描述
命名管道
本质是内核中的一块缓冲区,具有标识符,可以被其他进程找到,因此可以用于同一主机的任意进程间通信。
命名管道的标识符就是一个可见于文件系统的管道类型文件。
多个进程通过打开同一个管道文件,访问同一块内核中的缓冲区实现通信。
创建命名管道:
头文件 : #include<sys.types.h> #include<sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);
pathname:文件路径,mdoe:文件权限
返回值:成功返回0,失败返回-1
同时mkfifo()也是系统调用接口。可以在命令行下直接输入创建命名管道。

//server.cc文件
#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define FIFO_FILE "./fifo"
using namespace std;
int main()
{
    umask(0);
    if(-1 == mkfifo(FIFO_FILE,0666))//创建命名管道
    {
        cout<<"mkfifo ,File exist"<<endl;
    }
    int fd = open(FIFO_FILE,O_RDONLY);//打开管道文件,获取标识符
    int fd1 = open("./log.txt",O_WRONLY);创建log.txt文件
            if(fd1 >= 0)
            {
                dup2(fd1,1);//输出重定向,缓冲区读来的数据本来应打印在显示器上,重定向到log.txt文件
            }
    if(fd >= 0)
    {
        char buf[64];
        while(1)
        {
            ssize_t s = read(fd,buf,sizeof(buf)-1);
        if(s>0)
        {        
            buf[s]=0;
            cout<<"client#"<<buf;
        }
        else if(s == 0)
        {
            //client 退出
            cout<<"client exist,me too!"<<endl;
            break;
        }
        else{
            cout<<"read"<<endl;
            break;
        }
        }
    }
    return 0;
}
//client.cc文件
#include<iostream>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
#define FIFO_FILE "./fifo"
using namespace std;
int main()
{
    int fd = open(FIFO_FILE,O_WRONLY);以只写方式打开管道文件
    if(fd >= 0)
    {  
        char buf[64];
    while(1)
    {   
    cout<<"please input message#";
    fflush(stdout);//刷新C++标准库缓冲区数据 
    ssize_t s = read(0,buf,sizeof(buf)-1);//考虑到若是字符串,有'\0'
       if(s>0)
       {
           buf[s]=0;
           write(fd,buf,s);
       } 
        }
    }
    return 0;
}

这俩个程序文件实现了client作为用户端发送数据,server作为服务器接收数据,并最终把收到的数据打印到log.txt中的过程。
思考:
由于client和server之间存在IO操作,若是命名管道文件存在于硬盘中,则会降低IO效率,因此管道文件fifo只是一个符号性的管道文件,存在于内存中,数据不会刷新到磁盘上。
在这里插入图片描述
共享内存(用于实现进程间的数据共享)
本质:一块物理内存(不同于消息队列和管道)
原理:开辟一块物理内存空间,多个进程将同一块内存映射到自己的虚拟空间,通过虚拟地址直接进行访问进而实现数据共享。
共享内存的数据结构如下:
在这里插入图片描述
os让进程通过key值看到同一份资源(共享内存)
在这里插入图片描述

	操作流程:

1.创建或打开共享内存

		int shmget(ket_t key ,size_t size ,int shmflg);
		头文件:#include <sys/ipc.h>#include <sys/shm.h>
			key:在系统层面体现了共享内存的唯一性,通过key值使多个进程看到同一份内存资源。
			size:创建时所开辟的空间大小(以内存页为单位:4096)
		    shmflg:打开方式+创建权限   IPC_CREAT |IPC_EXCL|0664
		    返回值:成功返回非负整数—操作句柄;失败返回-1		   

2.将共享内存映射到进程的虚拟地址空间

void* shmat(int shmid,const void* shmaddr,int shmflg);	
	头文件:#include <sys/types.h>    #include <sys/shm.h>
	 shmid:shmget返回的操作句柄(用户层面)
	 shmaddr:映射地址——通常置NULL
	 shmflg:映射成功后的访问方式;SHM_RDONLY—只读;0—读写
	 返回值:成功返回映射后的首地址(虚拟地址),失败返回(void*)-1

3.通过映射的虚拟地址进行各种内存操作
4.解除映射关系

int shmdt(const void *shmaddr);//解除映射关系(Share Memory Detach)
头文件:#include <sys/types.h>    #include <sys/shm.h>
shmaddr:映射后的首地址
返回值:成功返回0;失败返回-1

5.删除共享内存

			int shmctl(int shmid,int cmd,struct shmid_ds* buf)//删除共享内存
		
			shmid:shmget返回的操作句柄
			cmd:操作类型
				IPC_RMID只是标记共享内存为销毁状态,实际上并没删除,直到进程退出共享内存才会被真正的删除。

在这里插入图片描述

			buf:用于设置或者获取共享内存信息,不用设置,置空即可。
			返回值:对于IPC_RMID来说,成功返回0,失败返回-1.

本质原理
开辟一块内存空间,多个进程将同一块内存映射到虚拟地址空间,通过自己的虚拟地址空间地址进行访问,进而实现数据共享。
特性
最快的进程间通信方式,共享内存资源生命周期随内核。

在这里插入图片描述
共享内存通过虚拟地址直接访问物理内存实现数据共享,相较于其他方式(例如管道,消息队列)需要将数据拷贝到内核,使用时拷贝到用户态,少了两次数据拷贝操作。是最快的IPC形式。

//comm.h

  #ifndef __COMM_H__                                 
  #define __COMM_H__                             
  #define PATHNAME "/tmp"    //生成key值                          
  #define PROJ_ID 0x4321     //生成key值                       ;
  #define SIZE 4096          //共享内存的大小                      
  #include<iostream>                               
  #include<sys/types.h>                            
  #include<sys/ipc.h>                               
  #include<sys/shm.h>                              
  #include<stdio.h>                                
  #include<unistd.h>                               
  #include<string.h>                               
  using namespace std;                              
  #endif    
  //server.cc
 #include"comm.h"
 int main ()
 {
   key_t k = ftok(PATHNAME,PROJ_ID);//生成唯一值key,表示共享内存的唯一性
   if(k<0)
   {
     perror("ftok");
     return -1;
   }
   cout << k << endl;
   int shmid =shmget(k,SIZE,IPC_CREAT|IPC_EXCL|0666);//创建共享内存
   //IPC_CREAT|IPC_EXCL表示若共享内存已存在则报错,不存在则创建
   cout << shmid<<endl;
   if(shmid < 0)
   {
     perror("shmget");
     return 1;
   }
   char* addr = (char*)shmat(shmid,NULL,0);//使调用该函数的进程与共享内存建立映射关系,返回值为虚拟内存的地址,虚拟地址不关心则置空;0代表默认:读写
   sleep(4);                                                                               
   cout<<addr<<endl;
     sleep(1);
   shmdt(addr);//解除映射关系,参数为shmat返回的指针。
   shmctl(shmid,IPC_RMID,NULL);//IPC_RMID代表删除共享内存,
   return 0;
 }
 //client.cc
 #include"comm.h"
  int main ()
  {
     key_t k = ftok(PATHNAME,PROJ_ID);
     if(k<0)
     {
       perror("ftok");
       return -1;
     }
    cout << k << endl;
    int shmid =shmget(k,SIZE,IPC_CREAT);
    cout << shmid<<endl;
    if(shmid < 0)
    {
      perror("shmget");
      return 1;
    }
     char* addr = (char*)shmat(shmid,NULL,0)   ;         
     sleep(1);
     char* buf = "i am processA";      
     strcpy(addr,buf);
     shmdt(addr);
     return 0;
  }

在这里插入图片描述

实验目的: 进程A 向共享内存当中写 “i am process A”,进程B 从共享内存当中读出内容,并且打印到标准输出。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倚心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值