什么是共享内存
共享内存是内存上的一个区域,允许多个进程同时访问、写入数据,使用起来类似于使用malloc
函数分配的内存,在写入数据时另外一个进程可以立刻获取到最新的数据。共享内存的生命周期和系统内核的生命周期是一致的,也可于命令行界面显式释放。
需要注意的是共享内存没有提供同步机制,也就是说,假设有多个进程同时写入数据,那么可能会造成其中的数据不是我们所预期的,所以需要我们自己定义其同步机制,例如信号量。
使用方法
共享内存的构造
Linux在头文件sys/shm.h
定义了和共享内存相关的函数,首先是构造共享内存的函数shmget
:
int shmget (key_t key, size_t size, int shmflg) ;
-
第一个参数
key
属于int
类型,类似于信号量,程序需要提供一个非0整数作为该共享内存的命名(注意和下面函数中的shmid
并不等价) -
第二个参数
size
表示共享内存的大小。 -
第三个参数
shmflg
为共享内存的访问权限(前9位)、IPC_CREATE
或IPC_EXCL
(第10~12位)。类似于Linux文件系统的访问权限,例如666
、644
(八进制数),可以用来分别限制进程的读取、写入权限。此外还可以添加IPC_CREATE
或IPC_EXCL
字段,IPC_CREAT
表示若存在该shmid
对应的共享内存,则返回,否则自动创建一个ID为shmid
的共享内存。IP_EXCL
一般不会单独使用,如果要使用一般来说传入的是IPC_CREAT | IPC_EXCL
,保证返回的共享内存是新构造的。
函数执行完成后返回共享内存的ID,如果返回-1
表示共享内存创建失败。
共享内存的访问
shmget
函数仅仅是用来构造共享内存,真正用来进行访问的函数是shmat
:
void *shmat (int shmid, const void *shmaddr, int shmflg);
- 第一个参数
shmid
就是需要访问的共享内存ID,对应于我们刚才调用shmget
函数传入的key
参数。 - 第二个参数
shmaddr
用来指定共享内存映射到当前进程的内存空间中起始的地址,一般为NULL
即可,表示由系统自动选择其映射的地址。 - 第三个参数
shmflg
为标志位,一般为0
函数执行成功后,会返回void*
指针,指向该共享内存的首地址,用户可以按照自己的定义的规则来访问该内存。
共享内存的分离
如果当前进程不需要再使用该共享内存,可以使用shmdt
函数:
int shmdt(const void *shmaddr);
shmaddr
需要传入目标共享内存的首地址。一般传入上述shmat
返回的指针即可。
共享内存的操纵
可以调用shmctl
函数来控制共享内存的一些基本属性:
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
- 第一个参数
shm_id
为共享内存的ID - 第二个参数
cmd
为需要对该共享内存采取的操作,取值范围为:IPC_STAT
、IPC_SET
、IPC_RMID
。
IPC_STAT
用来把第三个参数buf
所指向的结构设为当前共享内存的基本属性,也就是获取共享内存的属性并赋值给buf
。
IPC_SET
表示如果进程有足够权限就把当前共享内存的基本属性设为buf
。
IPC_RMID
用来删除共享内存 - 第三个参数表示需要设置的共享内存的基本属性。操作系统内核为每个共享内存都维护了一个特殊的数据结构,记录了共享内存的基本属性,这个数据结构就是结构体
shmid_ds
:
struct shmid_ds
{
struct ipc_perm shm_perm; /* 该共享内存的访问权限 */
size_t shm_segsz; /* 段大小,单位为字节 */
__time_t shm_atime; /* 最后一个进程调用shmat访问该共享内存的时间戳 */
#ifndef __x86_64__
unsigned long int __unused1;
#endif
__time_t shm_dtime; /* time of last shmdt() */
#ifndef __x86_64__
unsigned long int __unused2;
#endif
__time_t shm_ctime; /* 最后调用shmctl修改该共享内存的时间戳 */
#ifndef __x86_64__
unsigned long int __unused3;
#endif
__pid_t shm_cpid; /* 创建该共享内存的进程PID */
__pid_t shm_lpid; /* pid of last shmop */
shmatt_t shm_nattch; /* number of current attaches */
__syscall_ulong_t __unused4;
__syscall_ulong_t __unused5;
};
共享内存的删除除了在程序中调用stmctl
函数以外,还可以在命令行界面中使用ipcrm
命令:
[root@bogon ~]# ipcrm -m <shmid>
此外,还可以通过执行ipm
命令来查看当前操作系统所有的共享内存:
[root@bogon shm]# ipcs -m
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 nattch 状态
0x0000abcd 0 root 666 1048578 0
程序示例
下面程序中我们利用共享内存来实现两个进程间的“聊天”。
首先我们需要定义一个结构体,用来定义共享内存的存储结构:
memdef.h
:
#pragma once
#include <sys/shm.h>
#include <unistd.h>
#include <stdbool.h>
#define BUFFER_SIZE (1024 * 512) //512KB
typedef struct memory {
bool buf0_can_read; //buf0缓冲区是否可读
bool buf1_can_read; //buf1缓冲区
char buf0[BUFFER_SIZE]; //buf0缓冲区
char buf1[BUFFER_SIZE]; //buf1缓冲区
} *memory;
#define MEM_SIZE (sizeof(struct memory)) //上述结构所需共享内存总大小
为了保证并发安全,我们让创建共享内存的进程
使用buf0
缓冲区,另外一个进程
使用buf1
缓冲区。保证进程
只能读取buf1
缓冲区,进程
读取buf0
缓冲区
shmdemo.c
:
#include <stdio.h>
#include <string.h>
#include "memdef.h"
int main(int argc, char** argv) {
const key_t key = 0xABCD;
const pid_t pid = getpid(); //获取当前进程PID
int shmid;
//尝试创建共享内存并返回shmid,如果存在则直接返回对应的shmid
if ((shmid = shmget(key, MEM_SIZE, IPC_CREAT | 0666)) == -1) {
fprintf(stderr, "Fail to create mem\n");
return 1;
}
//根据shmid获取内存
void* memptr = shmat(shmid, NULL, 0);
if (memptr == (void*)-1) {
fprintf(stderr, "Failure visit mem\n");
return 1;
}
struct shmid_ds stat;
shmctl(shmid, IPC_STAT, &stat); //获取共享内存基本属性,并赋给stat
memory mem = (struct memory*)memptr; //以我们定义的结构体来读取共享内存
char *slf_buf, *other_buf; //slf_buf分别对应当前线程写入的缓冲区,other_buf对应读取的缓冲区
bool *slf_can_read, *other_can_read;
if (stat.shm_cpid == pid) { //如果该共享内存是当前进程创建的,则使用缓冲区buf0
fprintf(stdout, "Using buf0\n");
slf_buf = mem->buf0;
other_buf = mem->buf1;
slf_can_read = &(mem->buf0_can_read);
other_can_read = &(mem->buf1_can_read);
} else {
fprintf(stdout, "Using buf1\n");
slf_buf = mem->buf1;
other_buf = mem->buf0;
slf_can_read = &(mem->buf1_can_read);
other_can_read = &(mem->buf0_can_read);
}
memset(slf_buf, 0, BUFFER_SIZE); //初始化自己的缓冲区
*slf_can_read = false; //初始化自己的可读标记
while (1) {
if (*slf_can_read == false) { //如果自己的缓冲区已经被另外一个进程读取了
fprintf(stdout, "Enter message:");
fflush(stdout);
if (fscanf(stdin, "%s", slf_buf) == EOF) { //从键盘读入一行字符串,写入到自己的缓冲区
*slf_can_read = -1;
break;
}
*slf_can_read = true; //可读标记为true
} else {
usleep(50 * 1000); //如果另外一个进程还没有读取自己的缓冲区,睡眠50毫秒
}
if (*other_can_read == true) { //如果其它进程发送的消息可读
fprintf(stdout, "Receive message:%s\n", other_buf);
*other_can_read = false; //将其它进程的可读标记为false
} else if (*other_can_read == -1) { //如果其它进程退出程序
break;
}
}
shmdt(memptr);
if (stat.shm_cpid == pid) { //由创建的进程回收共享内存
shmctl(shmid, IPC_RMID, &stat);
}
return 0;
}
拷贝上述两个文件到同一目录,使用gcc编译上述程序为shm-demo
:
[root@bogon shm]# gcc shm-demo.c -o shm-demo
打开两个终端,同时运行shm-demo
:
终端1:
[root@bogon shm]# ./shm-demo
Using buf0
Enter message:abcdef
Receive message:asdasd
Enter message:asdasdasdasd
Receive message:adadasdaad
Enter message:123456
Receive message:123456
Enter message:
终端2:
[root@bogon shm]# ./shm-demo
Using buf1
Enter message:asdasd
Receive message:abcdef
Enter message:adadasdaad
Receive message:asdasdasdasd
Enter message:123456
Receive message:123456
Enter message:
当需要退出程序时按下Ctrl + D
即可,共享内存会被回收。
转载:https://blog.csdn.net/abc123lzf/article/details/101440068