文章目录
- 0.linux共享内存相关操作命令
- 1.共享内存的通信原理
- 2.为什么共享内存速度最快
- 3.共享内存的API
- 3.0 相关头文件
- 3.1.linux查看系统中的共享存储段指令
- 3.2.linux删除系统中的共享存储段指令
- 3.3 int shmget(key_t key, size_t size, int shmflg);创建共享内存或若共享内存已经存在,则打开共享内存
- 3.4 void *shmat(int shmid, const void *shmaddr, int shmflg);:挂接共享内存,启动对该共享内存的访问,并把共享内存连接到进程的地址空间
- 3.5 int shmdt(const void *shmaddr);和当前进程分离
- 3.6 int shmctl(int shmid, int cmd, struct shmid_ds *buf);用来控制共享内存的各种操作
- 4.模拟共享内存
- 5.使用共享内存完成进程间通信
- 6.总结
- 7.共享内存使用流程(怎么用共享内存?)
- 8.ftok函数:获得key_t的参数
- 9.共享内存shm和映射区mmap的区别
- 10.共享内存类的封装
0.linux共享内存相关操作命令
1)ipcs用法
ipcs -a //打印当前系统所有进程间通信方式的信息
ipcs -m //打印出使用共享内存进行进程间通信的信息
================以下仅为了解信息==================
ipcs -q //打印出使用消息队列进行进程间通信的信息
ipcs -s //打印出使用信号进行进程间通信的信息
2)ipcrm用法
ipcrm -M shmkey //移除用shmkey创建的共享内存段
ipcrm -m shmid //移除用shmid标识的消息队列
================以下仅为了解信息==================
ipcrm -Q msgkey //移除用msgkey创建的消息队列
ipcrm -q msqid //移除msqid标识的消息队列
ipcrm -S semkey //移除semkey创建的信号
ipcrm -s semid //移除用semid标识的信号
1.共享内存的通信原理
0)特点
最快的进程间通信;
1)定义:
允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。
2)原理:
不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
3)特别提醒:
共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。
4)原理图:
在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。
共享内存的通信原理示意图:
5)补充
对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。
2.为什么共享内存速度最快
1)原理
- 借助上图说明:Proc A 进程给内存中写数据, Proc B 进程从内存中读取数据,在此期间一共发生了两次复制
2)步骤
(1)Proc A 到共享内存 (2)共享内存到 Proc B
3)为什么速度快?
因为直接在内存上操作,所以共享内存的速度也就提高了。
3.共享内存的API
3.0 相关头文件
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
3.1.linux查看系统中的共享存储段指令
ipcs -m
3.2.linux删除系统中的共享存储段指令
ipcrm -m [shmid]
3.3 int shmget(key_t key, size_t size, int shmflg);创建共享内存或若共享内存已经存在,则打开共享内存
int shmget(key_t key, size_t size, int shmflg);
-
参数key
:由ftok生成的key标识,标识系统的唯一IPC资源,记录共享内存在内核中的位置,需要是一个大于0的整数就行,随便一个数就行 -
参数size
:需要申请共享内存的大小。在操作系统中,申请内存的最小单位为页,一页是4k字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。如果是打开一个已经存在的共享内存,size写0就行 -
参数shmflg
:类似open函数的flag,创建后打开填啥都行
-IPC_CREAT: 创建贡献内存
-IPC_CREAT | 0664 创建的时候给共享内存一个操作权限
-IPC_CREAT | IPC_EXCL :检测共享内存是否存在,存在则函数返回-1,不存在返回0
返回值
:成功时返回一个新建或已经存在的的共享内存标识符,取决于shmflg的参数。失败则返回-1并设置错误码。
3.4 void *shmat(int shmid, const void *shmaddr, int shmflg);:挂接共享内存,启动对该共享内存的访问,并把共享内存连接到进程的地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
-
[参数shmid]:共享存储段的标识符,shmid是由shmget()函数返回的共享内存标识(也就是返回值)。通过这个参数访问共享内存
-
[参数*shmaddr]:指定共享内存在内核中的位置;或写NULL,委托内核去指定。
(shmaddr = 0,则存储段连接到由内核选择的第一个可以地址上(推荐使用)shmaddr指定共享内存连接到当前进程中的地址位置,通常是一个空指针,表示让系统来选择共享内存出现的地址) -
[参数shmflg]:表示关联成功后对共享内存的操作权限
(若指定了
①
SHM_RDONLY
位,则以只读方式连接此段,否则以读写方式连接此段。
②shmflg是一组标志位,通常为0
。一般很少需要控制共享内存连接的地址,通常都是让系统来选择一个地址,否则就会使应用程序对硬件的依赖性过高。)
- 使用举例
void* ptr = shmat(shmid , NULL , 0);
- [返回值]:成功返回共享存储段的
地址
(虚拟地址),并且内核将使其与该共享存储段相关的shmid_ds结构中的shm_nattch计数器加1(类似于引用计数);出错返回(void*)-1。
3.5 int shmdt(const void *shmaddr);和当前进程分离
-
当一个进程不需要共享内存的时候,就需要去关联。该函数并不删除所指定的共享内存区,而是将之前用shmat函数连接好的共享内存区脱离目前的进程。
-
[参数*shmaddr]:
参数shmaddr是shmat()函数返回的地址指针
- [返回值]:
成功返回0,并将shmid_ds结构体中的 shm_nattch计数器减1;出错返回-1。
3.6 int shmctl(int shmid, int cmd, struct shmid_ds *buf);用来控制共享内存的各种操作
- [参数shmid]:
共享存储段标识符,shmid是shmget()函数的返回值,共享内存标识符。
- [参数cmd]:对共享内存的操作,设置为IPC_RMID时表示可以删除共享内存。
IPC_STAT:获取共享内存的状态
IPC_SET:如果进程有足够的权限,就可以设置共享内存状态
IPC_RMID:标记共享内存要被销毁,类似智能指针的引用计数,引用计数为零就把共享内存销毁,那传入的结构体可为空
- [参数*buf]:传入的事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_t shm_lpid
表示哪个进程最后进程了shmat或shmdt
shmatt_t shm_nattch
有多少个进程和共享内存进行了关联操作
- [返回值]:成功返回0,失败返回-1。
- 效果:比如说读共享内存的状态,读出来后放在传入的结构体struct shmid_ds *buf里面
4.模拟共享内存
- 用server来创建共享存储段,用client获取共享存储段的标识符,二者关联起来之后server将数据写入共享存储段,client从共享区读取数据。通信结束之后server与client断开与共享区的关联,并由server释放共享存储段。
- 头文件
comm.h
//comm.h
#ifndef _COMM_H__
#define _COMM_H__
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
int CreateShm(int size);
int DestroyShm(int shmid);
int GetShm(int size);
#endif
- 函数实现文件
//comm.c
#include"comm.h"
static int CommShm(int size,int flags)
{
key_t key = ftok(PATHNAME,PROJ_ID);
if(key < 0)
{
perror("ftok");
return -1;
}
int shmid = 0;
if((shmid = shmget(key,size,flags)) < 0)
{
perror("shmget");
return -2;
}
return shmid;
}
int DestroyShm(int shmid)
{
if(shmctl(shmid,IPC_RMID,NULL) < 0)
{
perror("shmctl");
return -1;
}
return 0;
}
int CreateShm(int size)
{
return CommShm(size,IPC_CREAT | IPC_EXCL | 0666);
}
int GetShm(int size)
{
return CommShm(size,IPC_CREAT);
}
- 客户端
//client.c
#include"comm.h"
int main()
{
int shmid = GetShm(4096);
sleep(1);
char *addr = shmat(shmid,NULL,0);
sleep(2);
int i = 0;
while(i < 26)
{
addr[i] = 'A' + i;
i++;
addr[i] = 0;
sleep(1);
}
shmdt(addr);
sleep(2);
return 0;
}
- 服务器
//server.c
#include"comm.h"
int main()
{
int shmid = CreateShm(4096);
char *addr = shmat(shmid,NULL,0);
sleep(2);
int i = 0;
while(i++ < 26)
{
printf("client# %s\n",addr);
sleep(1);
}
shmdt(addr);
sleep(2);
DestroyShm(shmid);
return 0;
}
- makefile
//Makefile
.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
5.使用共享内存完成进程间通信
- 下面就以两个不相关的进程来说明进程间如何利用共享内存来进行通信。其中一个文件shm1.c(消费者)创建共享内存,并读取其中的信息,另一个文件shm2.c(生产者)将连接一个已有的共享内存段,向共享内存中写入数据。
#ifndef _SHM_COM_H_HEADER //shm_com.h
#define _SHM_COM_H_HEADER
#define TEXT_SZ 2048
struct shared_use_st
{
int written_by_you; // 作为一个标志,非0:表示可读,0:表示可写
char some_text[TEXT_SZ]; // 记录写入 和 读取 的文本
};
#endif
#include <stddef.h> //shm1.c
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "shm_com.h"
int main(int argc, char **argv)
{
int running = 1;
void *shared_memory = NULL;
struct shared_use_st *shared_stuff; // 指向shm
int shmid; // 共享内存标识符
srand((unsigned int)getpid());
// 创建共享内存
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
if (shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
// 将共享内存连接到当前进程的地址空间
shared_memory = shmat(shmid, (void *)0, 0);
if (shared_memory == (void *)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("\nMemory attached at %p\n", shared_memory);
printf("Memory attched at %d\n", *(int*)shared_memory);
// 设置共享内存
shared_stuff = (struct shared_use_st*)shared_memory; // 注意:shm有点类似通过 malloc() 获取到的内存,所以这里需要做个 类型强制转换
shared_stuff->written_by_you = 0;
while (running) // 读取共享内存中的数据
{
// 没有进程向内存写数据,有数据可读取
if (shared_stuff->written_by_you == 1)
{
printf("You wrote: %s", shared_stuff->some_text);
sleep(1);
// 读取完数据,设置written使共享内存段可写
shared_stuff->written_by_you = 0;
// 输入了 end,退出循环(程序)
if (strncmp(shared_stuff->some_text, "end", 3) == 0)
{
running = 0;
}
}
}
// 把共享内存从当前进程中分离
if (shmdt(shared_memory) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
// 删除共享内存
if (shmctl(shmid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
reutrn 0;
}
#include <unistd.h> //shm2.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "shm_com.h"
int main(int argc, char **argv)
{
int running = 1;
void *shared_memory = NULL;
struct shared_use_st *shared_stuff; // 指向shm
char buffer[BUFSIZ];
int shmid; // 共享内存标识符
// 创建共享内存
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
if (shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
// 将共享内存连接到当前的进程地址空间
shared_memory = shmat(shmid, (void *)0, 0);
if (shared_memory == (void *)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attched at %p\n", shared_memory);
printf("Memory attched at %d\n", *(int*)shared_memory);
// 设置共享内存
shared_stuff = (struct shared_use_st *)shared_memory;
while (running) // 向共享内存中写数据
{
// 数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
while (shared_stuff->written_by_you == 1)
{
sleep(1);
printf("Waiting...\n");
}
// 向共享内存中写入数据
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
// 写完数据,设置written使共享内存段可读
shared_stuff->written_by_you = 1;
// 输入了end,退出循环(程序)
if (strncmp(buffer, "end", 3) == 0)
{
running = 0;
}
}
// 把共享内存从当前进程中分离
if (shmdt(shared_memory) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
return 0;
}
- 实验解析
第一个程序shm1创建共享内存段,然后它连接到自己的地址空间中。我们在共享内存的开始处使用一个结构体shared_use_st。该结构体中有个标志written_by_you,当共享内存中有数据写入时,就设置这个标志。这个标志被设置时,程序就从共享内存中读取文本,将它打印出来,然后清除这个标志表示已经读完数据。我们用一个特殊字符串end来退出循环。接下来,程序分离共享内存段并删除它。
第二个程序shm2使用相同的键1234来取得并连接同一个共享内存段。然后它提示用户输入一些文本。如果标志written_by_you被设置,shm2就知道客户进程还未读完上一次的数据,因此就继续等待。当其他进程清除了这个标志后,shm2写入数据并设置该标志。它还使用字符串end来终止并分离共享内存段。
注意,这里提供了非常简陋的同步标志written_by_you,它包括一个非常缺乏效率的忙等待(不停地循环)。这可以使得我们的示例比较简单,但在实际编程中,应该使用信号量或通过传递消息、生成信号的方式来提供应用程序读、写部分之间的一种更有效的同步机制。
6.总结
总结:
(1)优点:我们可以看到使用共享内存进行进程之间的通信是非常方便的,而且函数的接口也比较简单,数据的共享还使进程间的数据不用传送,而是直接访问内存,加快了程序的效率。
(2)缺点:共享内存没有提供同步机制,这使得我们在使用共享内存进行进程之间的通信时,往往需要借助其他手段来保证进程之间的同步工作。
7.共享内存使用流程(怎么用共享内存?)
1)向内核申请一块内存,并指定内存有多大(这块内存属于内核)
2)如果有两个进程需要通信,可以使用共享内存完成,先创建两个进程
3)进程A和进程B分别和这块共享内存关联,那这两个进程就拿到了共享内存的地址-》首地址
4)两个进程可以通过这个首地址对共享内存进行读/写操作
5)如果这个进程不再使用这块共享内存,需要和共享内存断开关联(进程退出,对共享内存没影响)
6)当不再使用共享内存时候,需要将共享内存销毁
8.ftok函数:获得key_t的参数
key_t ftok(const char* pathname, int proj_id)
-参数pathname:对应某个存在的路径或路径对应的文件名(路径或名都可以)
-参数proj_id:目前只占用了该变量占用内存的一部分(1个字节,0到255都可以)
举例:
key_t t = ftok('/home/','a');//a就是129
shmget(t,0,0);
9.共享内存shm和映射区mmap的区别
1)shm可以直接创建,mmap创建需要依赖磁盘文件(也就是fd套接字)
内存映射区匿名映射不能进行无血缘关系的进程通信
2)shm效率更高
①shm直接对内存操作
②mmap需要同步磁盘文件
3)内存共享区域不同
①shm中所有进程操作同一块内存
②内存映射区:每个进程都会在自己的虚拟地址空间有一块独立的内存
4)数据安全性不同
①进程突然退出:
-》共享内存还在
-》内存映射区消失了
②运行进程的电脑突然挂了:
-》共享内存的数据消失了
-》内存映射区的数据还在
5)声明周期不同
①内存映射区:进程退出,内存映射区销毁
②共享内存:进程退出,共享内存还在,手动删除,或者关机
10.共享内存类的封装
1)BasShm(父类)
- UML类图信息
- 代码函数
①mapshm关联
②unmapshm解除关联
③delshm删除共享内存
④void* m_ptr
和int 类型的m_shmid
是proteced
的,下面图片写错了
2)SecKey Shm存储密钥(子类)
- 类图