Linux通信--构建进程通信IPC的方案之共享内存|实现使用共享内存进行server&client通信

共享内存是最快的IPC形式。一旦这样的内存映射到共享它的进程地址空间,这些进程间数据传递不再涉及到内核,即进程不再通过执行进入内核的系统调用来传递彼此的数据。

目录

一、共享内存的原理

二、使用共享内存

三、共享内存函数

1.shmget(用来创建共享内存)

2.shmat(将共享内存和进程地址空间关联)

3.shmctl(用于控制共享内存)

4.shmdt(将共享内存段与当前进程脱离)

四、共享内存server&client通信测试

①创建共享内存

 ②关联共享内存和进程

//2-3 进行通信

③取消关联进程和共享内存

④关闭共享内存


一、共享内存的原理

  • 1.在物理内存中开辟一块空间
  • 2.让不同的进程通过页表将该空间映射到自己的进程虚拟地址空间中
  • 3.不同进程通过操作自己进程虚拟空间中的虚拟地址,来操作共享内存。

进程A和B都通过各自的页表将自己的虚拟地址和物理地址进行对应,进程A操作虚拟地址写入数据,保存在物理内存,进程B读取该物理内存。

二、使用共享内存

共享内存在物理地址空间上,是在共享区中。

  1. 创建共享内存
  2. 关联进程--将进程的虚拟地址和共享内存的地址通过页表建立映射关系
  3. 通信
  4. 取消关联进程--通过将进程中页表的key-value删除
  5. 共享内存释放

三、共享内存函数

上图表示进程A和进程B可以通过共享内存来通信,同样C和D也可以通过另一块共享内存通信。所以在系统中,一定同时存在多个共享内存,os需要对这些内存块做管理,所以使用一个结构体,里面存放共享内存的各个属性

所以 共享内存 == 共享内存的内核数据结构 + 真正开辟的物理空间

1.shmget(用来创建共享内存)

原型:int shmget(key_t key,size_t size,int shmflg)

参数:key 这个共享内存段名字 size 共享内存大小 shmflg 由九个权限标志构成,用法的mode一样

返回值:成功返回一个非负整数,即共享内存段的标识码,失败返回-1

其中key_t key 是由ftok算法生成的随机值,具有唯一性,ftok函数如下:

key_t  ftok(const char * pathname,int proj_id)

形参:pathname 传入一个地址  proj_id 输入一个数字 这两个值可以任意设置,但是在通信双方必须是相同的,具体地,A进程调用ftok生成key,将key放入struct shm中,B也生成相同的key,B用这个key去struct shm去匹配,匹配成功,就找到了共享内存。

shmflg宏 含义
IPC_CREAT 单独使用时,创建一个共享内存,如果不存在就直接创建,如果存在就获取已有的共享内存起始地址返回
IPC_EXCL 需要依赖IPC_CREAT使用,如果不存在则创建共享内存,如果存在立马退出报错返回

2.shmat(将共享内存和进程地址空间关联)

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

参数: shmid 共享内存标识 shmaddr 指定连接的地址 shmflg它的两个可能取值是SHM_RND 和SHM_RDONLY

返回值:成功返回一个指针,指向共享内存的起始地址,失败返回-1(类似于malloc)

说明:shmaddr为null,os自动选择一个地址,

           shmaddr不为null,且shmflg无SHM_RND标记,则以shmaddr为连接地址

         shmaddr不为null,且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整 shmlba的整数倍

            shmflg = SHM_RDONLY 表示连接操作用来只读共享内存

3.shmctl(用于控制共享内存)

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

参数:shmid由shmget返回的共享内存标识码

            cmd:将要采取的动作(有三个可能取值如下表)

           buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

返回值:成功返回0,失败返回-1

命令 说明
IPC_STAT 将shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET 在进程有足够权限的前提下,将共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID 删除共享内存段


4.shmdt(将共享内存段与当前进程脱离)

原型:int shmdt(const void * shmaddr);

参数:shmaddr:由shmat所返回的指针

成功返回0,失败返回-1

注:将共享内存与当前进程脱离不等于删除共享内存段 

四、共享内存server&client通信测试

使用共享内存通信的步骤:创建共享内存,关联进程和共享内存,进行通信,取消关联,删除共享内存

 创建4个文件:server.cc,client.cc,common.hpp,makefile。server实现写入,client实现读取

makefile要生成两个目标文件server,client,可以这样写:

.PHONY:all
all:server client
client:client.c comm.c
 gcc -o $@ $^
server:server.c comm.c
 gcc -o $@ $^
.PHONY:clean
clean:
 rm -f client server

common.hpp中声明和定义一些客户端和服务端公有的函数,如下:

这个文件中主要实现创建共享内存,关联进程和取消关联,共享内存释放。

①创建共享内存

  1. 首先,创建共享内存,使用shmget
  2. 其中有一个key,使用ftok生成,在两端调用即可\
  3. 通信的时候,一个进程创建共享内存,一个进程获取.
  4.   共享内存创建时,是按字节为单位。
//创建共享内存,要知道k和这个共享内存的大小
int createShm(key_t k, int size)
{
    //创建的时候,最好创建全新的
    int shmid = shmget(k,gsize,IPC_CREAT|IPC_EXCL);
    if(shmid == -1)
    {
       //创建失败
        exit(2);
    }
    
    return shmid;
}

//创建成功,一个进程获取
int getShm(key_t,int size)
{
    int shmid = shmget(k,gsize,IPC_CREAT);
    return shmid;
}

从上述两个函数,一个创建一个获取,只是shmflg不同,所以可以封装为一个函数

//static 只在本文件内有效
static int createShmHelper(int k, int size,int flag)
{
    int shmid = shmget(k,gsize,flag);
    if(shmid == -1)
    {
        exit(2);
    }

    return shmid;
}



int createShm(key_t k,int size)
{
      //创建的时候注意加权限
      umask(0);
      return createShmHelper(key,gsize,IPC_CREAT|IPC_EXCL|0666);
}

int getShm(key_t k,int size)
{
    return createShmHelper(key,gsize,IPC_CREAT);
}
    

 ②关联共享内存和进程

        在物理内存中创建好共享内存后,这个共享内存在创建的时候必须有权限才能进行操作。关联共享内存和进程,将共享内存的起始地址经过页表映射放到进程pcb中,具体挂接到pcb的哪里可以自己设定,设置为null让系统自主选择,即完成了关联。

char * attachShm(int shmid)
{
    char * start = (char *)shmat(shmid,nullptr,0);
    return start;
}

//2-3 进行通信

具体地通信可以自己设置

③取消关联进程和共享内存

detach(char * start)
{
    int n = shmdt(start);
    (void)n;
}

④删除共享内存

当运行两个端,发现进程退出后,再次运行无法创建共享内存,说明共享内存没有直接随着进程关闭。

关闭共享内存可以用两种方法

  1. ipcrm - m命令
  2. 函数shmctl
void delShm(int shmid)
{
   int n =  shmctl(shmid,IPC_RMID,nullptr);
   assert(n != -1);
   (void)n;
}

server.cc中主要实现创建共享内存,关联共享内存,进行通信(从共享内存读数据),取消关联,删除共享内存。

client.cc中主要实现获取共享内存,关联共享内存,进行通信(写数据到共享内存),取消关联。

接下来,优雅的修改上面的代码,封装起来。

common.hpp:

//前面的方法不变


#define SERVER 1
#define CLIENT 0

class Init
{

public:
    //构造
    Init(int t):type(t)
    {
        key_t k = getKey();
        if(type == SERVER) 
            shmid = createShm(k, gsize);
        else
             shmid = getShm(k, gsize);

        start = attachShm(shmid);
    }

    char *getStart()
    {     
        return start;
    }

    //析构
    ~Init()
    {
        detachShm(start);
        if(type == SERVER) delShm(shmid);
    }
private:
    char *start;
    int type;     //server or client
    int shmid;
};


server.cc

int main()
{
    Init init(SERVER);
    char * start = init.getStart();
    //开始通信
    ....
    //读取
    int n = 0;
    while(n <= 26)
    {
        cout<<" "<<start<<endl;  //设置start里都是字符串
        sleep(1);
    }

    //因为init是一个临时对象,所以函数跑完会自动调用析构
    return 0;
}

client.cc

int main()
{
    Init init(CLIENT);
    char * start = init.getStart();
    
    //开始通信

    ...
    //往start里写
    char c = 'A';
    while(c <= 'Z')
    {
        start[c-'A'] = c;
        c++;
        start[c] = '\0';
        sleep(1);
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/jolly0514/article/details/132562297