共享内存

共享内存

共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,当一个进程改

变了这块地址中的内容的时候,其它进程都会察觉到这个更改。

共享内存头文件

1
2
#include <sys/ipc.h>
#include <sys/shm.h>

共享内存操作函数

创建或打开一块共享内存区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int shmget(key_t key, size_t size, int shmflg);
- 参数key: 创建出的共享内存的键值, 每块共享内存的键值是唯一的
- 看做一个32位整形数, 一般指定的数据格式是16进制形式的
- 参数size: 创建的共享内存的大小, 分配的时候实际大小是是4k的倍数
- 参数shmflag: 共享内存的属性, 与创建文件相同
- open(name, flag, mode); - 与flag相同
- shmflag的取值
- IPC_CREAT: 创建共享内存
- 创建文件并指定权限: IPC_CREAT|0664
- IPC_EXCL: 必须和IPC_CREAT一起使用, 检测共享内存是否存在
- 返回值: 返回创建的共享内存的描述符, 理解为共享内存的ID, ID也是唯一的
成功: 返回共享内存的ID值
失败: 返回-1, 并设置errno

// 1. 创建一块不存在的共享内存
// 如果检测到key值为0x12的共享内存已经存在, 该函数调用失败
shmget(0x12, 4096, IPC_CREAT|IPC_EXCL|0664)

// 2. 打开一块已经存在的共享内存, 共享内存的key 0x12
shmget(0x12, 0, 0)

// 3. 操作一块内存, 存在打开, 不存在创建
shmget(0x12, 4096, IPC_CREAT|0664)

将当前进程和共享内存关联到一起

1
2
3
4
5
6
7
8
9
10
void *shmat(int shmid, const void *shmaddr, int shmflg);
- 参数shmid: shmget函数的返回值
- 参数shmaddr: 共享内存和进程关联, 指定的内存位置
- 赋值为NULL, 内核会自动分配
- 参数shmflg:
- SHM_RDONLY: 对共享内存只读
- 0: 可以对共享内存读写
- 返回值: 关联成功之后, 内核分配的可进行读写的内存块的首地址
成功: 内存地址
失败: (void *) -1

将共享内存和当前进程分离

1
2
3
4
5
int shmdt(const void *shmaddr); 
- 参数shmaddr: shmat函数的返回值
- 返回值:
成功: 返回0
失败: 返回-1, 并设置errno

共享内存操作 -(删除共享内存 )

1
2
3
4
5
6
7
8
9
10
int shmctl(int shmid, int cmd, struct shmid_ds *buf); 
- 参数shmid: shmget函数的返回值
- 参数cmd:
- IPC_STAT: 查看共享内存状态
- IPC_SET: 设置共享内存状态
- IPC_RMID: 删除共享内存
- 参数buf:
- IPC_STAT: 传出参数, 记录共享内存信息
- IPC_SET: 传入参数
- IPC_RMID: 用不到, 赋值为NULL
  • 问题1: 是不是可以对共享内存进行多次删除
    • 可以
    • 共享内存被删除一次之后, 如果还有进程和共享内存关联着, 共享内存的key会发生变化变成0
      • 如果共享内存key是>0的数: 共享内存状态正常, 任意进程都可以和当前共享内存进行关联
      • key == 0:
        • 共享内存标记为被删除, 没有被马上删除是因为还有进程没有和它解除关联
        • 不相干的进程是没有权限和key为0的共享内存进行关联的

image-20220524083153140

  • 问题2: 如果多个进程都和同一共享内存进行关联, 其中一个进程将共享内存删除, 共享内存什么时候被删除?
    • 共享内存的引用计数为0 的时候, 共享内存被删除

shm和mmap的区别

  1. shm不需要磁盘文件, mmap需要磁盘文件
  2. shm效率高
  3. mmap操作的数据量比shm大 4. shm内存位置在内核只有一块, mmap内存在用户区, 每个进程都有各自的内存映射区
  4. shm和mmap的数据谁更安全?
  • mmap会通过映射的文件做备份
  1. 进程退出, 共享内存依然存在, 进程退出,内存映射区就不存在了

ftok函数

函数原型

1
2
3
4
5
6
7
key_t ftok(const char *pathname, int proj_id); 
- pathname: 路径或文件名, 必须存在, 对文件的权限没有要求
- /home/kevin/a.txt - /home/kevin/hello - 目录

- proj_id: 只用到了一个字节, 取值范围: 0-255, 也可以传递一字符
- 88
- 'a



思考:

pathname 是目录还是文件的具体路径,是否可以随便设置?
可以

pathname 指定的目录或文件的权限是否有要求?
没有

proj_id 是否可以随便设定,有什么限制条件?
取值范围 0-255

陷阱:

误解:
只要文件的路径,名称和子序列号不变,那么得到的key值永远就不会变。
正解:
如果pathname指向的文件或者目录被删除而且又重新创建,那么文件系统会赋予这个同名文件新
的inode 节点信息,于是这些进程调用的 ftok() 都能正常返回,但键值key却不一定相同了。





共享内存操作命令

ipcs 用法

1
2
3
4
5
ipcs -a // 打印当前系统中所有的进程间通信方式的信息 
ipcs -m // 打印出使用共享内存进行进程间通信的信息 == 常用 ==
============= 以下为了解内容 ================
ipcs -q // 打印出使用消息队列进行进程间通信的信息
ipcs -s // 打印出使用信号进行进程间通信的信息

man msgget //消息队列相关函数
man semget //信号量数组相关函数





ipcrm 用法

1
2
3
4
5
6
7
ipcrm -M shmkey // 移除用shmkey创建的共享内存段 
ipcrm -m shmid // 移除用shmid标识的共享内存段
================ 以下为了解内容 ================
ipcrm -Q msgkey // 移除用msqkey创建的消息队列
ipcrm -q msqid // 移除用msqid标识的消息队列
ipcrm -S semkey // 移除用semkey创建的信号
ipcrm -s semid // 移除用semid标识的信号





共享内存在项目中的使用

image-20220524083853111

与业务相关的共享内存结构图:

1
2
3
4
5
6
7
8

struct NodeSHMInfo {
int status; //密匙状态
int seckeyID;
char clientID[12];
char serverID[12];
char seckey[128]; //密匙
} NODE;

共享内存结构图: 头4个字节存放最大节点个数, 后面一共存放maxNode个秘钥信息

image-20220524084019939

在遍历共享内存的时候, 首先获得共享内存的头4个字节内容, 也就是最大节点个数, 可以作为遍历的最大次数.

遍历的时候查找共享内存根据clientID和serverID来进行查找.





myipc_shm.h

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65


// myipc_shm.h
#ifndef _WBM_MY_SHM_H_
#define _WBM_MY_SHM_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#ifdef __cplusplus
extern "C" {
#endif

//共享内存错误码
#define MYIPC_OK 0 //正确
#define MYIPC_ParamErr 301 //输入参数失败
#define MYIPC_NotEXISTErr 302 //共享内存不存在错误
#define MYIPC_CreateErr 303 //创建共享内存错误


//创建共享内存 若共享内存不存在,则创建
int IPC_CreatShm(int key, int shmsize, int *shmhdl);

//打开共享内存 若共享内存不存在,返回错误
int IPC_OpenShm(int key, int shmsize, int *shmhdl);


/***********************************************************************
功能描述: 创建共享内存 通过种子文件
参数说明: shmname [in] 是共享内存名,系统中唯一标志
shmsize [in] 是要创建的共享内存的大小;
shmhdl [out] 共享内存的句柄.
返回值: 返回0函数执行成功;非0返回错误码
************************************************************************/
int IPC_CreatShmBySeedName(char *shmname, int shmsize, int *shmhdl);

/***********************************************************************
功能描述: 关联共享内存
参数说明: shmhdl [in] 共享的句柄
mapaddr [out] 共享内存首地址
返回值: 返回0函数执行成功;非0返回错误码
************************************************************************/
int IPC_MapShm(int shmhdl, void **mapaddr);

/***********************************************************************
功能描述: 取消共享内存关联
参数说明: unmapaddr [in] 共享内存首地址
返回值: 返回0函数执行成功;非0返回错误码
************************************************************************/
int IPC_UnMapShm(void *unmapaddr);

/***********************************************************************
功能描述: 删除共享内存
参数说明: shmhdl [in] 共享的句柄
返回值: 返回0函数执行成功;非0返回错误码
************************************************************************/
int IPC_DelShm(int shmhdl);


#ifdef __cplusplus
}
#endif
#endif

myipc_shm.c

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

#define _OS_LINUX_

#if defined _OS_LINUX_
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <memory.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include "myipc_shm.h"

#endif

static int shmflag = 0;
static int shmkey;

//创建共享内存 若共享内存不存在,则创建 若存在使用原来的
int IPC_CreatShm(int key, int shmsize, int *shmhdl)
{
int tmpshmhdl = 0;
int ret = 0;
// 创建共享内存
// 若共享内存不存在则创建
// 若共享内存已存在使用原来的
tmpshmhdl = shmget(key, shmsize, IPC_CREAT | IPC_EXCL | 0666);
if (tmpshmhdl == -1) //创建失败
{
ret = MYIPC_ParamErr;
printf("func shmget() err :%d ", ret);
return ret;
}
*shmhdl = tmpshmhdl;
return ret;
}

//打开共享内存 若共享内存不存在,返回错误
//参数 无意义 可填写0
int IPC_OpenShm(int key, int shmsize, int *shmhdl)
{
int tmpshmhdl = 0;
int ret = 0;
// 创建共享内存
// 若共享内存不存在则创建
// 若共享内存已存在使用原来的
tmpshmhdl = shmget(key, 0, 0);
if (tmpshmhdl == -1) //打开失败
{
ret = MYIPC_NotEXISTErr;
//printf("func shmget() err :%d ", ret);
return ret;
}
*shmhdl = tmpshmhdl;
return ret;
}

/***********************************************************************
功能描述: 创建共享内存
参数说明: shmname [in] 是共享内存名,系统中唯一标志
shmsize [in] 是要创建的共享内存的大小;
shmhdl [out] 共享内存的句柄.
返回值: 返回0函数执行成功;非0返回错误码
************************************************************************/
int IPC_CreatShmBySeedName(char *shmseedfile, int shmsize, int *shmhdl)
{
if (shmflag == 0) //判断接口中共享内存key是否已经存在
{
shmkey = ftok(shmseedfile, 'c');
if (shmkey == -1)
{
perror("ftok");
return -1;
}

shmflag = 1;
}

//创建共享内存
*shmhdl = shmget(shmkey, shmsize, IPC_CREAT | 0666);
if (*shmhdl == -1) //创建失败
return -2;
return 0;

}
/***********************************************************************
功能描述: 关联共享内存
参数说明: shmhdl [in] 共享的句柄
mapaddr [out] 共享内存首地址
返回值: 返回0函数执行成功;非0返回错误码
************************************************************************/
int IPC_MapShm(int shmhdl, void **mapaddr)
{
void *tempptr = NULL;

//连接共享内存
tempptr = (void *)shmat(shmhdl, 0, 0);
if (tempptr == (void*)-1) //共享内存连接失败
return -1;
*mapaddr = tempptr; //导出共享内存首指针

return 0;
}
/***********************************************************************
功能描述: 取消共享内存关联
参数说明: unmapaddr [in] 共享内存首地址
返回值: 返回0函数执行成功;非0返回错误码
************************************************************************/
int IPC_UnMapShm(void *unmapaddr)
{
int rv;
//取消连接共享内存
rv = shmdt((char *)unmapaddr);
if (rv == -1) //取消连接失败
return -1;

return 0;
}
/***********************************************************************
功能描述: 删除共享内存
参数说明: shmhdl [in] 共享的句柄
返回值: 返回0函数执行成功;非0返回错误码
************************************************************************/
int IPC_DelShm(int shmhdl)
{
int rv;
//删除共享内存
rv = shmctl(shmhdl, IPC_RMID, NULL);
if (rv < 0) //删除共享内存失败
return -1;
return 0;
}

keymng_shmop.h

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
// keymng_shmop.h

#ifndef _KEYMNG_SHMOP_H_
#define _KEYMNG_SHMOP_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef __cplusplus
extern "C" {
#endif

//将网点密钥信息写共享内存, 网点共享内存结构体
typedef struct _NodeSHMInfo
{
int status; //密钥状态 0-有效 1无效
char clientId[12]; //客户端id
char serverId[12]; //服务器端id
int seckeyid; //对称密钥id
unsigned char seckey[128]; //对称密钥 //hash1 hash256 md5
}NodeSHMInfo;

//int KeyMng_ShmInit(int keyid, int keysize, void *shmid )
//打开共享内存 共享内存存在则使用 不存在则创建
int KeyMng_ShmInit(int key, int maxnodenum, int *shmhdl);

// 客户端已经存储了一个秘钥 - 覆盖
// ......还没有存储秘钥 - 找一个位置存储秘钥
int KeyMng_ShmWrite(int shmhdl, int maxnodenum, NodeSHMInfo *pNodeInfo);

int KeyMng_ShmRead(int shmhdl, char *clientId, char *serverId, int maxnodenum, NodeSHMInfo *pNodeInfo);

#ifdef __cplusplus
}
#endif
#endif

keymng_shmop.c

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#include <unistd.h>
#include <sys/types.h>
//#include <sys/socket.h>
//#include <netinet/in.h>
//#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>


#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#include "itcastlog.h"
#include "keymng_shmop.h"
#include "myipc_shm.h"

//#include "keymngclientop.h"
//#include "poolsocket.h"


//看共享内存是否存在
//若 存在使用旧
//若 不存在创建
int KeyMng_ShmInit(int key, int maxnodenum, int *shmhdl)
{
int ret = 0;

//打开共享内存
ret = IPC_OpenShm(key, maxnodenum * sizeof(NodeSHMInfo), shmhdl);
if (ret == MYIPC_NotEXISTErr)
{
printf("keymng监测到共享内存不存在 正在创建共享内存...\n");
ret = IPC_CreatShm(key, maxnodenum * sizeof(NodeSHMInfo), shmhdl);
if (ret != 0)
{
printf("keymng创建共享内存 err:%d \n", ret);
return ret;
}
else
{
void *mapaddr = NULL;
printf("keymng创建共享内存 ok...\n");

ret = IPC_MapShm(*shmhdl, (void **)&mapaddr);
if (ret != 0)
{
printf("fun IPC_MapShm() err:%d 清空共享内存失败\n", ret);
return ret;
}
memset(mapaddr, 0, maxnodenum * sizeof(NodeSHMInfo));
IPC_UnMapShm(mapaddr);
printf("keymng清空共享内存ok\n");
}
}
else if (ret == 0)
{
printf("keymng监测到共享内存存在 使用旧的共享内存...\n");
}
else
{
printf("fun IPC_OpenShm() err:%d\n", ret);
}
return ret;
}


//写网点密钥
//若存在 则修改
//若不存在 则找一个空的位置写入
int KeyMng_ShmWrite(int shmhdl, int maxnodenum, NodeSHMInfo *pNodeInfo)
{
int ret = 0, i = 0;
NodeSHMInfo tmpNodeInfo; //空结点
NodeSHMInfo *pNode = NULL;
bool flag = false;
void *mapaddr = NULL;


memset(&tmpNodeInfo, 0, sizeof(NodeSHMInfo));
//连接共享内存
ret = IPC_MapShm(shmhdl, (void **)&mapaddr);
if (ret != 0)
{
flag = true;
ITCAST_LOG(__FILE__, __LINE__, IC_ERROR_LEVEL, ret, "func IPC_MapShm() err");
goto End;
}
if(flag) break;

//判断传入的网点密钥 是否已经 存在
for (i = 0; i < maxnodenum; i++)
{
pNode = mapaddr + sizeof(NodeSHMInfo)*i;
if (strcmp(pNode->clientId, pNodeInfo->clientId) == 0 &&
strcmp(pNode->serverId, pNodeInfo->serverId) == 0)
{
ITCAST_LOG(__FILE__, __LINE__, IC_WARNING_LEVEL, ret, "系统检测到 共享内存中已经存在网点信息cliented:%s serverid%s", pNode->clientId, pNode->serverId);
memcpy(pNode, pNodeInfo, sizeof(NodeSHMInfo));
goto End;
}
}

//若不存在
for (i = 0; i < maxnodenum; i++)
{
pNode = mapaddr + sizeof(NodeSHMInfo)*i;
if (memcmp(&tmpNodeInfo, pNode, sizeof(NodeSHMInfo)) == 0)
{
ITCAST_LOG(__FILE__, __LINE__, IC_WARNING_LEVEL, ret, "系统检测到 有一个空的位置 ");
memcpy(pNode, pNodeInfo, sizeof(NodeSHMInfo));
goto End;
}
}

if (i == maxnodenum)
{
ret = 1111;
ITCAST_LOG(__FILE__, __LINE__, IC_ERROR_LEVEL, ret, "系统检测到共享内存已满 ");
goto End;
}

End:
IPC_UnMapShm(mapaddr);
return ret;
}

//根据clientid和serverid 去读网点信息
int KeyMng_ShmRead(int shmhdl, char *clientId, char *serverId, int maxnodenum, NodeSHMInfo *pNodeInfo)
{
int ret = 0, i = 0;
NodeSHMInfo tmpNodeInfo; //空结点
NodeSHMInfo *pNode = NULL;

void *mapaddr = NULL;

memset(&tmpNodeInfo, 0, sizeof(NodeSHMInfo));
//连接共享内存
ret = IPC_MapShm(shmhdl, (void **)&mapaddr);
if (ret != 0)
{
ITCAST_LOG(__FILE__, __LINE__, IC_ERROR_LEVEL, ret, "func IPC_MapShm() err");
goto End;
}

//遍历网点信息
for (i = 0; i < maxnodenum; i++)
{
pNode = mapaddr + sizeof(NodeSHMInfo)*i;

if (strcmp(pNode->clientId, clientId) == 0 &&
strcmp(pNode->serverId, serverId) == 0)
{
ITCAST_LOG(__FILE__, __LINE__, IC_WARNING_LEVEL, ret, "系统检测到 有一个空的位置 ");
memcpy(pNodeInfo, pNode, sizeof(NodeSHMInfo));
goto End;
}
}

if (i == maxnodenum)
{
ret = 1111;
ITCAST_LOG(__FILE__, __LINE__, IC_WARNING_LEVEL, ret, "系统检测到共享内存已满 ");
goto End;
}

End:
IPC_UnMapShm(mapaddr);
return ret;
}




进程间通讯的几种方式:

1 匿名管道pipe:

特点:

1 只能用于有血缘关系的进程间通信
2 管道有两端, 管道的数据流向是从管道的写端到管道的读端
3 管道的本质是一块内核缓冲区
4 数据从管道中读走之后就不存在了
5 管道的实现实际上是环形队列
6 默认情况下管道的读端和写端都是阻塞的

2 命名管道: fifo

特点:

1 有无血缘关系的进程间通信都可以
2 创建的fifo文件大小为0, 是linux文件类型之一
3 使用fifo需要先创建一个fifo文件
4 使用fifo完成通信两个进程必须打开相同的fifo文件
5 效率比pipe低

3 mmap

​ 共享映射区本质是将文件内容映射到内存.

特点:

​ 1 有无血缘关系都可以完成进程间通信
​ 2 如果完成没有血缘关系的进程间通信必须使用文件.
​ 3 若使用的是MAP_SHARED, 则对内存的修改会反映到文件中去
​ 4 需要注意mmap可能存在调用失败的情况
​ 5 匿名映射只能用于有血缘关系的进程间通信

4 信号

​ 进程A给进程B发送信号的实现机制: 本质上是进程A先给内核发送信号, 然后内核给进程B发送

​ 通signal或者sigaction注册信号
​ 通过kill函数给指定进程发送信号

特点:

​ 1 信号不能携带大量信息
​ 2 信号的优先级高, 产生信号之后会打断程序的执行
​ 3 不建议使用信号完成进程间通信.
​ 4 一般使用kill命令给一个进程发送信号, 进程收到信号之后调用信号处理函数完成操作.

信号的处理动作:

​ 1 忽略信号
​ 2 执行默认处理动作
​ 3 执行用户自定义的函数

5 本地socket通信

1
2
unix_socket = socket(AF_UNIX, SOCK_STREAM, 0);
unix_socket = socket(AF_UNIX, SOCK_DGRAM, 0);

​ 1 本地socket通信既可以使用TCP也可以使用UDP
​ 2 如果使用TCP通信, bind的时候需要指定一个文件, 若文件存在会保存, unlink删除.
​ 3 读或者写其实是通过文件描述符去操作内核的缓冲区
​ 4 编写流程, 可以直接参考TCP或者UDP开发流程.

6 共享内存

​ 共享内存的实质是将内核的一块内存映射到进程中的内存, 操作本地内存就相当于操作共享内存.

使用共享内存的步骤:

​ 1 创建共享内存
​ 2 关联共享内存
​ 3 使用共享内存–读写共享内存
​ 4 断开与共享内存的关联
​ 5 删除共享内存

共享内存和以上的管道, mmap和本地socket通信比较起来, 共享内存不需要文件描述符,
后者需要.
共享内存是进程间通信方式中效率最高的.