目录
一、进程间通信的目的二、管道通信1.匿名管道2.命名管道①.创建命名管道②.使用命名管道 三、system V 标准进程间通信1.共享内存①常用接口②共享内存的内核数据结构③查看删除共享内存
一、进程间通信的目的
数据传输:一个进程需要将它的数据发送给另一个进程
 资源共享:多个进程之间共享同样的资源。
 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
二、管道通信
1.匿名管道
通过打开同一个文件,父子进程对文件进行读写操作,父子进程在文件内核缓冲区中写入或读出数据,从而实现通信
创建匿名管道
pipe:创建一个管道,参数为输出型参数,打开两个文件描述符(fd),返回值为0表示打开失败。
#include<iostream>#include<string>#include<cerrno>#include<cassert>#include<string.h>#include<sys/types.h>#include<unistd.h>int main(){    int pipefd[2]={0};    //1.创建管道    int n=pipe(pipefd);    if(n<0)    {        //创建不成功        std::cout<<"pipe error, "<<errno<<":"<<strerror(errno)<<std::endl;        return 1;    }    std::cout<<"pipefd[0]: "<<pipefd[0]<<std::endl; //读    std::cout<<"pipefd[1]: "<<pipefd[1]<<std::endl; //写    //2.创建子进程    pid_t id=fork();    assert(id!=-1); //省去判断    if(id==0)    {        //子进程        //3.关闭不需要的fd   父进程读取,子进程进行写入        close(pipefd[0]);        //4.开始通信        std::string namestr="hello,我是子进程";        int cnt=1;        char buffer[1024];        while(true)        {            snprintf(buffer,sizeof(buffer),"%s, 计数器:%d, 我的id: %d",namestr.c_str(),cnt++,getpid());            write(pipefd[1],buffer,strlen(buffer));            sleep(1);        }        exit(0); //子进程退出    }        //父进程    //3.关闭不需要的fd   父进程读取,子进程进行写入    close(pipefd[1]);    //4.开始通信    char buffer[1024];    while(true)    {        //sleep(1);        int n=read(pipefd[0],buffer,sizeof(buffer)-1);        if(n>0)        {            //读取成功            buffer[n]='\0';            std::cout<<"我是父进程, child give me massage: "<<buffer<<std::endl;                    }    }    return 0;} 
子进程往内核缓冲区中写入数据,父进程读取数据,实现了简单的父子间进程通信。
管道的特点:
 1.只能单向通信
 2.面向字节流
 3.只能在有"血缘"关系的进程中通信
 4.管道也是文件,管道的生命周期随进程
 5.管道自带同步系统,原子性写入
云服务器中,管道的大小为64KB,写端写满后不会再写,会等读端读取管道内容,且读取4KB后才会重新写入(读端的容量为4KB)

#include<iostream>#include<string>#include<cerrno>#include<cassert>#include<string.h>#include<sys/types.h>#include<unistd.h>int main(){    int pipefd[2]={0};    //1.创建管道    int n=pipe(pipefd);    if(n<0)    {        //创建不成功        std::cout<<"pipe error, "<<errno<<":"<<strerror(errno)<<std::endl;        return 1;    }    std::cout<<"pipefd[0]: "<<pipefd[0]<<std::endl; //读    std::cout<<"pipefd[1]: "<<pipefd[1]<<std::endl; //写    //2.创建子进程    pid_t id=fork();    assert(id!=-1); //省去判断    if(id==0)    {        //子进程        //3.关闭不需要的fd   父进程读取,子进程进行写入        close(pipefd[0]);        //4.开始通信        std::string namestr="hello,我是子进程";        int cnt=1;        char buffer[1024];        int count=0;        char c='x';        while(true)        {            //snprintf(buffer,sizeof(buffer),"%s, 计数器:%d, 我的id: %d",namestr.c_str(),cnt++,getpid());            //write(pipefd[1],buffer,strlen(buffer));            write(pipefd[1],&c,1);            count++;            printf("%d\n",count);            //sleep(1);        }        exit(0); //子进程退出    }        //父进程    //3.关闭不需要的fd   父进程读取,子进程进行写入    close(pipefd[1]);    //4.开始通信    char buffer[1024];    while(true)    {        sleep(3);        int n=read(pipefd[0],buffer,sizeof(buffer)-1);        if(n>0)        {            //读取成功            buffer[n]='\0';            std::cout<<"我是父进程, child give me massage: %d,"<<n<<buffer<<std::endl;                    }    }    return 0;} 
管道的读写规则
当没有数据可读时
 O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
 O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
当管道满的时候
 O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
 O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
 如果所有管道写端对应的文件描述符被关闭,则read返回0 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
  
1.读端不读或读的慢,写端要等读端
 2.读端关闭,写端收到SIGPIPE信号直接终止
 3.写端不写或者写的慢,读端要等写端
 4.写端关闭,读端会读完管道内的数据然后再读,会读到0,表示读道文件结尾
2.命名管道
匿名管道只能解决有 “血缘” 关系的进程之间通信
 为解决这一问题,引入命名管道,可以实现没有 “血缘” 关系的进程也可以进行通信,
 原理相同,使两个进程看到同一块内存。
①.创建命名管道
命令行创建

代码创建
 umask(0); if(mkfifo(pipe, 0666) < 0) //创建命名管道 { //创建失败     perror("mkfifo");     return 1; } ②.使用命名管道
进程一
#include<stdio.h>#include<string.h>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#define MAX 128//实现两个进程进行相互通信int main(){     //1.打开文件  对fifo文件进行只写    int fd1=open("fifo",O_WRONLY);    if(fd1==-1)    {        perror("open");        return 1;    }    printf("已打开一个管道文件\n");    char buf[MAX];    int cnt=5;    //2.写数据    while(cnt)    {        memset(buf,0,MAX);        sprintf(buf,"hello linux %d",cnt--);        int ret=write(fd1,buf,strlen(buf));                printf("write fifo : %d\n",ret);        sleep(1);    }    close(fd1);    return 0;}   进程二
#include<stdio.h>#include<string.h>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#define MAX 128int main(){    //1.打开管道    int fd=open("fifo",O_RDONLY);    if(fd==-1)    {        perror("open");        return 1;    }    printf("以读方式打开一个管道\n");    char buf[MAX];    //2.读数据    while(1)    {        memset(buf,0,MAX);        int ret=read(fd,buf,MAX);        if(ret<=0)//读到结尾        {            perror("read");            break;        }        printf("buf:%s\n",buf);    }    //2.关闭文件    close(fd);    return 0;}   运行两个程序,由此实现了两个进程的通信,如果想要实现相互通信,可以再开一个管道进行操作
三、system V 标准进程间通信
system V:同一主机内的进程间通信方案,在OS层面专门为进程间通信设计的方案
进程间通信的本质:让不同的进程看到同一份资源
system V标准下的三种通信方式 ①共享内存 ②消息队列 ③信号量
1.共享内存
实现原理
1.通过系统调用,在内存中创建一份内存空间
 2.通过系统调用,让进程"挂接"到这份新开辟的内存空间上(即在页表上建立虚拟地址与物理地址的映射关系)
 3.去关联(挂接)
 4.释放共享内存
①常用接口
sheget :申请共享内存

返回值
#include <sys/ipc.h>#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);// key:创建共享内存时的算法和数据结构中唯一标识符,由用户自己设定需用到接口ftok// size:共享内存的大小,建议是4KB的整数倍// shmflg:有两个选项:IPC_CREAT(0),创建一个共享内存,如果已经存在则返回共享内存;IPC_EXCL(单独使用没有意义)// IPC_CREAT|IPC_EXCL(如果调用成功,一定会得到一个全新的共享内存):如果不存在共享内存,就创建;反之,返回出错// 返回值:shmdi,描述共享内存的标识符 #include <sys/types.h>#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id); // 算法生成key// pathname:自定义路径名// proj_id:自定义项目id 
shmctl :控制共享内存

返回值
#include <sys/ipc.h>#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);// shmid:共享内存id// cmd:控制方式,这里我们只使用IPC_RMID 选项,表示删除共享内存// buf:描述共享内存的数据结构 
关联和去关联共享内存

返回值
#include <sys/types.h>#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg); // 关联// shmid:共享内存id// shmaddr:挂接地址(自己不知道地址,所以默认为NULL)// shmflg:挂接方式,默认为0// 返回值:挂接成功返回共享内存起始地址(虚拟地址),类似C语言malloc    int shmdt(const void *shmaddr); // 去关联(取消当前进程和共享内存的关系)// shmaddr:去关联内存地址,即shmat返回值// 返回值:调用成功返回0,失败返回-1 
②共享内存的内核数据结构
共享内存是内存,操作系统同时是运行多个进程的,所以操作系统中有多个共享内存同时存在,操作系统如何管理这些内存文件?先描述,后组织,操作系统会定义一个struct shmid_ds的内核数据结构去管理这些打开的贡献内存。

  
③查看删除共享内存
指令查看共享内存:    ipcs -m
system V 的IPC资源,生命周期随内核,只能通过程序员显示释放(或者OS重启)
指令删除共享内存:  ipcrm -m shmid
也可使用指令shmctl进行共享内存删除操作
实现一个小demo通过内存共享实现进程间相互通信
comman.hpp
#ifndef __COMM_HPP__#define __COMM_HPP__ #include<iostream>#include<cerrno>#include<cstdio>#include<cassert>#include<cstring>#include<sys/ipc.h>#include<sys/shm.h>#include<sys/types.h>#include<sys/stat.h>#include<sys/shm.h>using namespace std;#define PATHNAME "."#define PROJID 0x6666const int gsize=4096;key_t getKey(){    key_t k=ftok(PATHNAME,PROJID);    if(k==-1)    {        //失败        cerr<<"error"<<errno<<":"<<strerror(errno)<<endl;        exit(1);    }    return k;}string toHex(int x){    char buffer[64];    snprintf(buffer,sizeof(buffer),"0x%x",x);    return buffer;}static int creatShmHelper(key_t k,int size,int flag){    int shmid=shmget(k,size,flag);    if(shmid==-1)    {        // 创建失败        cerr<<"error: "<<errno<<": "<<strerror(errno)<<endl;        exit(2);    }    return shmid;}//创建共享内存//IPC_CREATE//单独使用IPC_CREATE:创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回//IPC_EXCL不能单独使用,一般配合IPC_CREATE//IPC_CREATE | IPC_EXCL  :创建一个共享内存,如果不存在,就创建之,如果已经存在,立马出错返回,如果创建成功,返回创建好的共享内存int creatShm(key_t k,int size){       umask(0);    return creatShmHelper(k,size,IPC_CREAT|IPC_EXCL|0666);  //设置权限}//获取共享内存int getShm(key_t k,int size){    return creatShmHelper(k,size,IPC_CREAT);}void delShm(int shmid){    int n=shmctl(shmid,IPC_RMID,nullptr);    assert(n!=-1);    (void)n;}char* attachShm(int shmid){    char* start=(char*)shmat(shmid,nullptr,0);      return start;} void detachShm(char* start){    int n=shmdt(start);    assert(n!=-1);    (void)n;}#endif shmserver.cc
#include"comman.hpp"#include<unistd.h>int main(){      //1.创建key    key_t k=getKey();    cout<<"server:"<<toHex(k)<<endl;    //2.创建共享内存    int shmid=creatShm(k,gsize);    cout<<"server shmid: "<<shmid<<endl;    //3.将自己和共享内存关联起来    char * start=attachShm(shmid);    //通信代码      int n=0;     while(n<=26)     {         cout<<"client -> server # "<<start<<endl;         n++;         sleep(1);     }    //sleep(3);    //4.将自己和共享内存去关联    detachShm(start);    //删除共享内存    delShm(shmid);    return 0;} shmclient.cc
#include"comman.hpp"#include<unistd.h>int main(){    key_t k=getKey();    cout<<"client:"<<toHex(k)<<endl;    int shmid=getShm(k,gsize);    cout<<"client shmid: "<<shmid<<endl;    //3.将自己和共享内存关联起来    char * start=attachShm(shmid);    char c='A';    while(c<='Z')    {        start[c-'A']=c;        c++;        start[c]='\0';        sleep(1);    }    //4.将自己和共享内存去关联    detachShm(start);    sleep(5);    return 0;} 实现效果
使用共享内存进行通信时,不需要使用read和write 接口。
共享内存是所有进程间通信中速度最快的。
共享内存不提供任何同步或互斥机制,需要程序员自行保证数据安全。
ps: 共享内存在内核中的申请的基本单位是页,内存页的大小为4KB,如果申请4097个字节,内核会分配8KB空间,但实际给使用的内存还是4097个字节。