【Linux】进程间通信(IPC)之共享内存详解与测试用例

学习环境centos6.5 Linux内核2.6

什么是共享内存

共享内存允许两个或更多进程访问同一块内存。当一个进程改变了这块内存中的内容的的时候,其他进程都会察觉到这个更改。

效率

因为所有进程共享同一块内存,共享内存在各种进程间通信方式中具有最高的效率。就像访问进程独有的内存区域一样快,并不需要通过系统调用或其他需要切入内核的过程来完成。同时还能避免对数据的各种不必要的复制。

因为系统内核没有对访问共享内存的进程进行同步机制,如果有这方面的需求,比如需要不同进程进程对共享内存区进行读写操作,则必须要提供自己的同步措施,保护临界资源。通常我们使用信号量机制实现进程间同步与互斥。有关信号量的知识请阅读另一篇博客信号量。

原理:

【Linux】进程间通信(IPC)之共享内存详解与测试用例_第1张图片

在Linux中,每个进程都有自己的P C B 和地址空间,并且都有一个对应的页表,负责将进程的虚拟内存和物理内存进行映射,通过MMU来管理。共享内存块,被映射到不同进程的地址空间内,从而实现了高效率的资源共享。

分配一个新的共享内存块会创建新的内存页面。因为所有进程都希望共享对同一块内存的访问,只应由一个进程创建一块新的共享内存。再次分配一块已经存在的内存块不会创建新的页面,会返回一个标识该内存块的标示符。一个进程如需使用这个共享内存块,则首先需要将它绑定到自己的地址空间中。会创建一个从进程本身虚拟地址到共享页面的映射关系。当不需要该共享内存块的时候,必须由一个且只能是一个进程负责释放这个被共享的内存页面。

如何使用

要使用一块共享内存,进程必须先分配它。其他需要访问这个共享内存块的每一个进程都必须将这个共享绑定(attach)到自己的地址空间中(系统维护一个对该内存的引用计数器,通过ipcs -s 命令可查看有几个进程在使用该共享内存块)
。当通信完毕后,所有进程从共享内存块脱离,由一个进程释放该共享内存块。要注意的是,所有用户申请的共享内存块最终大小都必须是向上取整为系统页面大小的整数倍。在Linux系统中,内存页面大小默认是4KB。

注意:当一个进程创建一块共享内存后,该进程在主动去释放该共享内存之前,被kill掉时,只会使该进程脱离(detach)该共享内存块,而不会释放该共享内存块,这时候可以使用命名ipcrm -m 去释放该资源。

相关函数

1、shmget

#include 
#include 
int shmget(key_t key, size_t size, int shmflg);
  • 函数说明:得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符。
  • 参数:
    • key ftok函数返回的I P C键值
    • size 大于0的整数,新建的共享内存大小,以字节为单位,获取已存在的共享内存块标识符时,该参数为0,
    • shmflg IPC_CREAT||IPC_EXCL 执行成功,保证返回一个新的共享内存标识符,附加参数指定IPC对象存储权限,如|0666
  • 返回值:成功返回共享内存的标识符,出错返回-1,并设置error错误位。

2、shmat

#include 
#include 
void *shmat(int shmid, const void shmaddr, int shmflg);
  • 函数说明:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间
  • 参数:
    • shmid: 共享内存标识符
    • shmaddr: 指定共享内存出现在进程内存地址的什么位置,通常指定为NULL,让内核自己选择一个合适的地址位置
    • shmflg: SHM_RDONLY 为只读模式,其他参数为读写模式
  • 返回值:成功返回附加好的共享内存地址,出错返回-1,并设置error错误位

3、shmdt

#include 
#include 
void *shmdt(const void* shmaddr);
  • 函数说明:与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存,需要注意的是,该函数并不删除所指定的共享内存区,而是将之前用shmat函数连接好的共享内存区脱离目前的进程
  • 参数:shmddr 连接共享内存的起始地址
  • 返回值:成功返回0,出错返回-1,并设置error。

4、shmctl

#include 
#Include 
int shmctl(int shmid, int cmd, struct shmid_ds* buf);
  • 函数说明:控制共享内存块
  • 参数:
    • shmid:共享内存标识符
    • cmd:
      • IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构赋值到buf所指向的buf中
      • IPC_SET:改变共享内存的状态,把buf所指向的shmid_ds结构中的uid、gid、mode赋值到共享内存的shmid_ds结构内
      • IPC_RMID:删除这块共享内存
    • buf:共享内存管理结构体
  • 返回值:成功返回0,出错返回-1,并设置error错误位。

代码演示

目的阐述:进程server 创建一块大小为4096kb的共享内存,然后将进程attach到该共享内存块上,并执行写操作,从’a’开始写,每写一次sleep(1);而client进程每隔一秒从该共享内存块读取一个并打印。

comm.h

#ifndef _COMM_H_
#define _COMM_H_

#include 
#include 
#include 
#include 

#define PATHNAME "." // ftok函数 生成key使用
#define PROJ_ID 66 // ftok 函数生成key使用

int create_shm( int size);//  分配指定大小的共享内存块
int destroy_shm( int shmid); // 释放指定id的共享内存块
int get_shmid(); // 获取已经存在的共享内存块


#endif /*_COMM_H_*/

comm.c

#include "comm.h"

// 
static int comm_shm(int size, int shmflag)
{
    key_t key = ftok(PATHNAME, PROJ_ID); // 获取key 
    if(key < 0){
        perror("ftok");
        return -1;
    }
    int shmid = shmget(key,  size, shmflag);
    if(shmid < 0){
        perror("shmget");
        return -2;
    }

    return shmid;
}

int create_shm( int size)
{
    return comm_shm(size, IPC_CREAT|IPC_EXCL|0666);
}
int get_shmid()
{
    return comm_shm(0, IPC_CREAT);
}

int destroy_shm(int shmid)
{
    if( shmctl( shmid, IPC_RMID, NULL) < 0)
    {
        perror("shmctl");
        return -1;
    }
    return 0;
}

server.c

#include "comm.h"

int main()
{
    int shmid = create_shm(4096);// 创建共享内存块
    char *buf;
    int i = 0;
        buf = shmat(shmid,NULL, 0 );

    while( i < 4096)
    {
        buf[i] = 'a'+i ;
        i++;
        sleep(1); 
        if(i == 26)
            break; // 让程序结束,去释放该共享内存
    }
        destroy_shm(shmid);
    return 0;
}

client.c

#include "comm.h"

int main()
{
    int shmid = get_shmid(4096);
    char *buf;
    int index = 0;
        buf = shmat(shmid,NULL, 0 );

    while( index < 4096)
    {
        printf("%s\n", buf);
        sleep(1);
        index++;
        if( index == 27)
            break; // 让程序结束
    }
    return 0;
}

截图演示:
先启动服务端,我们可以执行命令ipcs -m 查看分配的该内存块:
【Linux】进程间通信(IPC)之共享内存详解与测试用例_第2张图片

可以发现倒数第二列,表示为1,表示该进程被attach到该内存块,等启动client进程后该值变为2

【Linux】进程间通信(IPC)之共享内存详解与测试用例_第3张图片

在后台执行server进程,启动server进程后
【Linux】进程间通信(IPC)之共享内存详解与测试用例_第4张图片

等两个进程结束后再次使用ipcs -m 命令,发现已经看不到之前申请的那块共享内存。

你可能感兴趣的:(linux)