当前位置:   article > 正文

进程间通信IPC-共享内存_ipc共享内存

ipc共享内存

共享内存

​ 共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,它是在多个进程之间对内存段进行映射的方式实现内存共享的。这是IPC最快捷的方式,因为共享内存方式的通信没有中间过程,而管道、消息队列等方式则是需要将数据通过中间机制进行转换;与此相反,共享内存方式直接将某段内存段进行映射,多个进程间的共享内存是同一块的物理空间,仅仅是地址不同而已,因此不需要进行复制,可以直接使用此段空间。

共享内存特点

进程间数据交换的实现方式包括三类

1、通过文件,比如命名管道文件,相互通信的进程通过访问同一磁盘文件实现数据交换。存在read、write等系统调用,效率低下,速度慢、

2、通过内核,比如消息队列,需要进行用户、内核的内存拷贝,开销大,效率低、速度慢

3、通过共享内存,读写速度快,无需数据拷贝、系统调用等,效率高

1、共享内存为相互的通信的进程提供了同一块物理内存空间,每个进程都可以直接访问,不需要用户内核之间的数据拷贝或者系统调用,大大提高了通信的速度,是最快的进程间通信方式
2、共享内存并未提供同步机制,多进程竞争同个共享资源会造成数据混乱
3、共享内存生命周期是随内核的,不是随进程的,程序员必须释放。
  • 1
  • 2
  • 3
共享内存原理图

​ 每个进程都有独立的虚拟空间地址,通过MMU地址转换将虚拟地址与物理地址进行映射,每个进程虚拟地址空间都会映射到不同的物理地址,每个进程在物理内存空间都是相互独立和隔离的。共享内存通过分配一块共享的物理空间,将其挂接到相互通信息的进程虚拟地址空间中,实现虚拟地址到共享物理内存的映射。

在这里插入图片描述

共享内存管理数据结构:

结构shmid_ds结构定义如下:

struct shmid ds{
  struct ipc_perm  shm_perm; /*所有者和权限*/
	size_t shm_segsz; /*段大小,以字节为单位*/
  time t shm_atime; /*最后挂接时间*/
  time_t shm_dtime; /*最后取出时间*/
  time_t shm_ctime; /*最后修改时间*/
  pid_t	 shm_cpid; /*建立者的PID */
  pid_t	 shm_lpid; /*最后调用函数 shmat ()/shmdt ()*/
  shmatt_t shm_nattch; /*现在挂接的数量*/
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
函数原型

​ system V提供的IPC机制包括消息队列,信号量和共享内存3种。使用IPC前必须先创建,每种IPC都有特定的生产者、所有者和访问权限。

​ pcs命令可以查看当前系统正在使用的IPC工具。一个IPC工具至少包含key值、ID值、拥有者、权限和使用的大小等关键信息。如果需要手工删除某个IPC机制,可以使用ipcrm命令。

创建系统层面唯一性标ipc ftok 函数:
//获取IPC键值 用于区分不同的ipc
//key_t key 用来进行进程间通信的,让不同进程能看到同一份资源
key_t ftok(const char *pathname, int proj_id);
  • 1
  • 2
  • 3

参数 pathname:为文件路径,或目录,一般是当前目录。
参数 id:为一个整形变量,是子序号,参与构成ftok()函数的返回值。虽然是int类型,但是只使用8bits(1-255)。
在ftok()函数创建key值过程中使用了该文件属性的st_dev 和st_ino。
key值构成:
key值的第31-24(共8位)为ftok()第二个参数的低8位。//第二个参数的用处在这里。
key值的第23-16(共8位)为该文件的st_dev属性的第8位。
key值的第15-0为该文件的st_ino属性的低16位。

创建共享内存函数shmget()

函数shmget()用于创建一个新的共享内存段,或者访问一个现有的共享内存段,它与消息队列以及信号量集合对应的函数十分相似。函数shmget()的原型如下:

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
/*执行成功返回值为shmid 共享内存的标识符ID
key:只是用来在系统层面进行标识唯一性的,不能用来管理shm。
shmid:是OS给用户返回的ID,用来在用户层进行shm的管理。
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

shmget()的第一个参数是关键字的值。然后,这个值将与内核中现有的其他共享内存段的关键字值相比较。在比较之后,打开和访问操作都将依赖于shmflg参数的内容。size参数为共享内存大小,一般为存物理页的整数倍。

IPC_CREAT:如果在内核中不存在该内存段,则创建它。

IPC_EXCL:当与IPC_CREAT一起使用时,如果该内存段早已存在,则此次调用将失败。

如果只使用IPC_CREAT, shmget()或者将返回新创建的内存段的段标识符,或者返回早已存在于内核中的具有相同关键字值的内存段的标识符。如果同时使用IPC_CREAT和IPC_EXCL,则可能会有两种结果:如果该内存段不存在,则将创建一个新的内存段;如果内存段早已存在,则此次调用失败,并将返回-1。IPC_EXCL本身是没有什么用处的,但在与IPC_CREAT组合使用时,它可用于防止一个现有的内存段为了访问而打开着。旦进程获得了给定内存段的合法IPC标识符,它的下一步操作就是连接该内存段,或者把该内存段映射到自己的寻址空间中。

获得共享内存地址函数shmat()

函数shmat()用来获取共享内存的地址,获取共享内存成功后,可以像使用通用内存一样对其进行读写操作。函数的原型如下:

#include <sys/types.h>
#include <sys/shm.h>
void *shmat (int shmid, const void *shmaddr, int shmflg);
/*返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1*/
  • 1
  • 2
  • 3
  • 4

如果shmaddr参数值等于0,则内核将试着查找一个未映射的区域。用户可以指定一个地址,但通常该地址只用于访问所拥有的硬件,或者解决与其他应用程序的冲突。

SHM_RND标志可以与标志参数进行OR操作,结果再置为标志参数,这样可以让传送的地址页对齐(舍入到最相近的页面大小)。此外,如果把SHM_RDONLY标志与标志参数进行OR操作,结果再置为标志参数,这样映射的共享内存段只能标记为只读方式。当申请成功时,对内存的操作与一般内存一样,可以直接进行写入和读出,以及偏移的操作。

IPC_REMAP:替换位于shmaddr处的任意既有映射:共享内存段或内存映射

删除共享内存函数shmdt()

函数shmdt()用于删除一段共享内存。函数的原型如下:

#include <sys/types.h>
#include <sys/shm.h>
int shmdt (const void *shmaddr);
  • 1
  • 2
  • 3

当某进程不再需要一个共享内存段时,它必须调用这个函数来断开与该内存段的连接。这与从内核删除内存段是两回事!在成功完成了断开连接操作以后,相关的shmid_ds结构的shm_nattch成员的值将减去1。如果这个值减到0,则内核将真正删除该内存段。

共享内存控制函数shmctl()

共享内存的控制函数shmctl()的使用类似ioctl)的方式对共享内存进行操作:向共享内存的句柄发送命令,来完成某种功能。函数shmctl()的原型如下,其中shmid是共享内存的句柄,cmd是向共享内存发送的命令,最后一个参数buf则是向共享内存发送命令的参数。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl (int shmid, int cmd, struct shmid_ds *buf);
  • 1
  • 2
  • 3

此函数与消息队列的msgctlO调用是完全类似的,它的合法命令值是:

IPC_STT:获取内存段的shmid_ds结构,并把它存储在buf参数所指定的地址中。

IPC_SET 设置内存段shmid_ds结构的ipc_perm成员的值,此命令是从buf参数中获得该值的。

IPC_RMID:标记某内存段,以备删除。该命令并不真正地把内存段从内存中删除。相反,它只是标记上该内存段,以备将来删除。只有当前连接到该内存段的最后一个进程正确地断开了与它的连接,实际的删除操作才会发生。当然,如果当前没有进程与该内存段相连接,则删除将立刻发生。为了正确地断开与其共享内存段的连接,进程需要调用 shmdt()函数。

共享内存流程:
1、创建ipc系统唯一标识key -> ftok
2、创建共享内存 -> shmget
3、映射共享内存 -> shmat
4、共享内存读写
5、解除共享内存映射 -> shmdt
6、删除共享内存 -> shmctl
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
共享内存例子
Server.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

int main (void){
    key_t key;                  /*系统唯一性标识ipc*/
    int shmid;                  /*共享内存标识*/
    char  *shms;                /* 共享内存挂接后的地址, */   
    struct shmid_ds shmbuf;      
    pid_t p;                                        /*进程号*/
    key = ftok(".", 'a');                           /*生成系统唯一标识ipc*/
    if(key<0)
    {
        perror("ftok failed");
        return 1;
    }
    shmid = shmget(key, 1024, IPC_CREAT|0600);      /*获得共享内存,大小为1024个字节*/
     if(shmid<0)
    {
        perror("shmget failed");
        return 2;
    }
    shms = (char *)shmat(shmid, NULL, 0);           /*挂接共享内存*/
    printf("shmat success!\n");

    while(strlen(shms)==0){
        sleep(1);
        printf("%s\n",shms);
    }

    shmdt(shms);                                /*摘除共享内存*/
    printf("shmdt success!\n");
    shmctl(shmid,IPC_RMID,&shmbuf);             /*删除共享内存*/
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
Client.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

static char msg[]="你好,共享内存!\n";
int main (void){
    key_t key;                  /*系统唯一性标识ipc*/
    int shmid;                  /*共享内存标识*/
    char  *shmc;                /* 共享内存挂接后的地址, */   
    struct shmid_ds shmbuf;      
    pid_t p;                                        /*进程号*/
    key = ftok(".", 'a');                           /*生成系统唯一标识ipc*/
    if(key<0)
    {
        perror("ftok failed");
        return 1;
    }
    shmid = shmget(key, 1024, IPC_CREAT|0600);      /*获得共享内存,大小为1024个字节*/
     if(shmid<0)
    {
        perror("shmget failed\n");
        return 2;
    }
    shmc = (char *)shmat(shmid, NULL, 0);           /*挂接共享内存*/
    printf("shmat success!");

    memcpy(shmc, msg, strlen(msg) +1);          /*复制内容到共享内存*/
    
    shmdt(shmc);                                /*摘除共享内存*/
    printf("shmdt success!\n");
    shmctl(shmid,IPC_RMID,&shmbuf);             /*删除共享内存*/
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
Makefile
all:client server  																
client: client.o 								
	gcc -o client client.o
server: server.o 								
	gcc -o server server.o					
clean:										
	rm -rf server client *.o
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
运行截图

先运行server

在这里插入图片描述

之后检查是否创建共享内存
在这里插入图片描述

运行client

在这里插入图片描述

检查共享内存是否被删除
在这里插入图片描述
传送门

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/1000840
推荐阅读
相关标签
  

闽ICP备14008679号