进程间通信【system V】

共享内存

原理图

进程间通信【system V】_第1张图片

共享内存=内存空间+共享内存的数据结构

  1. ftok——获取一个共享内存的唯一标识符
#include 
#include 
key_t ftok(const char *pathname, int proj_id);

功能:
获取一个共享内存的唯一标识符key
函数参数:
pathname:可以传入任何文件名
proj_id:只有是一个非0的数都可以
返回值:
成功返回key值,失败返回-1

2.shmget——创建共享内存

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

功能:
创建共享内存
函数参数:
key:传入ftok函数获取的共享内存唯一标识符
size:共享内存的大小(页(4kb)的整数倍),size如果不是整数倍,系统会给你开整数倍,但是实际大小还是你所开的大小
shmflg:权限,由9个权限标准构成
这里介绍两个选项
IPC_CREAT: 如果底层存在这个标识符的共享内存空间,就打开返回,不存在就创建
IPC_EXCL: 如果底层存在这个标识符的共享内存空间,就出错返回(必须和IPC_CREAT一起使用)
两个选项合起来用就可以创建一个权限的共享内存空间
返回值:
成功返回共享内存标识码值(给用户看的),失败返回-1

  1. shmat——将共享内存空间关联到进程地址空间
#include 
#include 
void *shmat(int shmid, const void *shmaddr, int shmflg);

功能:
将共享内存空间关联到进程地址空间
参数:
shmid:共享内存标识符(shmat的返回值)
shmaddr:指定连接地址。
shmfig:两个可能取值是SHM_RND和SHM_RDONLY
返回值: 成功返回一个指针(虚拟地址空间中共享内存的地址,是一个虚拟地址),失败返回-1(返回值的意思类似于malloc)
说明:
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整> 数倍。公式:shmaddr -(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

  1. shmdt——取消关联
#include 
#include 
int shmdt(const void *shmaddr);

功能:
取消共享内存空间和进程地址空间的关联
参数:
shmaddr:共享内存的起始地址(shmat获取的指针)
返回值: 成功返回0,失败返回-1

  1. shmctl——控制共享内存
#include 
#include 
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

功能:
控制共享内存
参数:
shmid:共享内存标识符(shmat的返回值)
cmd:命令,有三个
IPC_STAT: 把shmid_ds结构中设置为共享内存当前关联值
IPC_SET: 把共享内存的当前关联值设置为shmid_ds数据结构中的值
IPC_RMID:删除共享内存段
buf:指向一个报错这共享内存的模式状态和访问权限的数据结构
返回值:
成功返回0,失败返回-1

  • 获取共享内存的唯一标识,并创建共享内存
#include
#include
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define pathname "."
#define proj_id 0x6464
int main()
{
    key_t key=ftok(pathname,proj_id);
    if(key==-1)
    {
        cerr << errno << strerror(errno) << endl;
    }
    printf("0x%x\n",key);
    //如果底层存在这个标识符的共享内存空间,就出错返回(必须和IPC_CREAT一起使用)
    int shimid=shmget(key,4096,IPC_CREAT|IPC_EXCL);
    cout << shimid << endl;
    if(shimid==-1)
    {
        cerr << errno << strerror(errno) << endl;
    }
    return 0;
}

查看ipc资源指令(共享内存)

ipcs -m(memory)

进程间通信【system V】_第2张图片

结论:共享内存的生命周期是随OS的,不是随进程的

取消共享内存的id

ipcrm -m shimid

  • 将进程和这块共享内存关联起来,5秒后取消关联并删除共享内存空间
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define pathname "."
#define proj_id 0x6464
int main()
{
    key_t key=ftok(pathname,proj_id);
    if(key==-1)
    {
        cerr << errno << strerror(errno) << endl;
    }
    printf("0x%x\n",key);
    //如果底层存在这个标识符的共享内存空间,就出错返回(必须和IPC_CREAT一起使用)
    int shimid=shmget(key,4096,IPC_CREAT|IPC_EXCL);
    cout << shimid << endl;
    if(shimid==-1)
    {
        cerr << errno << strerror(errno) << endl;
    }
    //将进程和共享内存关联起来
    //最后一个权限设置为0,默认可以读写
    char* start=(char*)shmat(shimid,nullptr,0); 
    //
    if((long long)start==-1)
    {
        cerr << errno << strerror(errno) << endl;
    }
    sleep(5);
    //取消关联
    if(shmdt(start)==-1)
    {
        cerr << errno << strerror(errno) << endl;
    }
    return 0;
}

进程间通信【system V】_第3张图片

需要把shmget多加一个权限参数,和系统接口open一样

int shimid=shmget(key,4096,IPC_CREAT|IPC_EXCL|0600);

进程间通信【system V】_第4张图片

使用共享内存实现进程通信

和匿名管道那里一样,这里有client.c和server.c两个文件,还有一个comm.h一个头文件,里面存放两个进程公共的pathname和proj_id,这样两个进程就可以得到相同的共享内存唯一标识符。
这里我们选择使用服务端创建共享内存,然后连接到共享内存,让客户端也连接上这块共享内存,客户端写数据,服务端不断读

comm.h

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define MAX_SIZE 4096
using namespace std;
#define pathname "."
#define proj_id 0x6464
//获取唯一的key
key_t getKey()
{
    key_t key=ftok(pathname,proj_id);
    if(key==-1)
    {
        cerr << errno << strerror(errno) << endl;
        exit(-1);
    }
    return key;
}
int getShmHelper(int key,int flags)
{
    int shimid=shmget(key,MAX_SIZE,flags);
    if(shimid==-1)
    {
        cerr << errno << strerror(errno) << endl;
        exit(-1);
    }
    return shimid;
}
//获取共享内存
int getShm(key_t key)
{
    return getShmHelper(key,IPC_CREAT);
}
//创建共享内存
int createShm(key_t key)
{
    return getShmHelper(key,IPC_CREAT|IPC_EXCL|0600);
}
//进程和共享内存关联
void* attach(int shimid)
{
    void* start=shmat(shimid,nullptr,0);
     if((long long)start==-1)
    {
        cerr << errno << strerror(errno) << endl;
        exit(-1);
    }
    return start;
}
//进程取消和共享内存的关联
void detachShm(void* start)
{
    if(shmdt(start)==-1)
    {
        cerr << errno << strerror(errno) << endl;
    }
}
//删除共享内存
void delShm(int shimid)
{
    //这个接口是用来控制共享内存的,删除只是其中一个操作
    if(shmctl(shimid,IPC_RMID,nullptr)==-1)
    {
        cerr << errno << strerror(errno) << endl;
    }
}

client.cpp

#include"comm.h"
int main()
{
    //获取唯一的key
    key_t key=getKey();
    //获取共享内存
    int shimid=getShm(key);
    //关联
    char* start=(char*)attach(shimid);
    cout << "attach success,address start "<< &start << endl;
    //使用
    int cnt=1;
    pid_t id=getpid();
    const char* message="hello server,我是一个进程,正在和你通信";
    while(1)
    {
        snprintf(start,MAX_SIZE,"%s[pid:%d][消息编号:%d]",message,id,cnt++);
        // char buffer[1024];
        // snprintf(buffer,sizeof(buffer),"%s[pid:%d][消息编号:%d]",message,id,cnt++);
        // //拷贝到start里面
        // memcpy(start,buffer,sizeof(buffer));
    }
    //取消关联
    detachShm(start);
    //删除共享内存
    delShm(shimid);
}

server.cpp

#include"comm.h"
int main()
{
    //获取唯一的key
    key_t key=getKey();
    //创建共享内存
    int shimid=createShm(key);
    //关联
    char* start=(char*)attach(shimid);
    cout << "attach success,address start "<< &start << endl;
    //使用
    int cnt=1;
    while(1)
    {
        //查看共享内存的结构
        //操作系统暴露给用户的结构,在内核中属性基本和这个一样

        struct shmid_ds ds;
        shmctl(shimid,IPC_STAT,&ds);
        printf("获取属性:size:%d,pid:%d,myself:%d,key:0x%x\n",ds.shm_segsz,ds.shm_cpid, getpid(), ds.shm_perm.__key);
        //拿到他的启始地址
        cout << "client say: " << start << endl;
    }
    //取消关联
    detachShm(start);
    //删除共享内存
    delShm(shimid);
}

共享内存的优点:

所有进程间通信,速度最快

进程间通信【system V】_第5张图片

管道通信至少需要4次拷贝,先写到buffer(缓冲区里),然后在写到管道里面,然后在读到缓冲区里,最后在拷贝到显示器里,共四次,但是如果细算,C++有输入输出流,输入设备会拷贝到cin/stdin,输出先拷贝到cout然后在拷贝到显示器,实际共6次

进程间通信【system V】_第6张图片

共享内存的话是2次,实际算是4次

共享内存的缺点:共享内存底层不提供任何同步与互斥的机制,例如,写进程5秒写一次,读进程1秒读一次,每次都可以读到5秒前的重复数据

消息队列

  • 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法

  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值

    特性方面:

  • IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

信号量

本质是一个计数器,用来表示公共资源中,资源数量的问题

公共资源:被多个进程可以同时访问的资源

访问没有保护的公共资源:数据不一致问题(例如我要发送123456,但是我不能阻止你读,你可能只读取了123)

在理解信号量问题之前,为什么要让不同的进程看到同一份资源呢?

因为我想通信,进程实现协同,不过进程具有独立性,要让进程看到同一份资源,提出方法,引入了新的问题(数据不一致的问题),我们将被保护起来的公共资源称为临界资源。

资源(内存,文件,网络等)是要被使用的,如何被进程使用?

一定是该进程有对应的代码来访问这部分资源,这部分代码称为临界区,其他称为非临界区

如何保护:互斥&&同步,互斥:只能让一个人进行访问,其他人只能等待

共享资源的使用:1.作为整体使用 2.划分为一个一个资源的子部分

另一个概念,原子性,举个例子,如果我向缓冲区写入1234,必须写完,这种要么不做,要做就做完,这种两态就是原子性

所以说什么是信号量呢?

举例:我们去电影院,想要获得这个座位,需要预定对吧?电影院有100个座位,count=100,预定以后count- -,取消预定就count++,信号量就是这样的存在,当我们需要某种资源的时候,我们可以进行预定资源(sem- -,P操作),释放资源(sem++,V操作,PV操作

所以进程在访问公共资源之前,必须申请信号量,申请信号量的前提,是所有进程必须先要看到同一个信号量,信号量本身就是一个公共资源,信号量本身也要保证自己的安全,所以它对应的++,- -操作是原子的,如一个信号量的初始值是1,就是作为整体使用,称为二元信号量——互斥功能

你可能感兴趣的:(linux)