cpp-doc-共享内存

概念

是允许两个或多个进程(不相关或有亲缘关系)访问同一个逻辑内存的机制。它是共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。

共享内存查看命令 ipcs -m

共享内存删除命令 ipcrm -m [shmid]

system v共享内存

相关函数

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
// 创建或访问共享内存段
// @key:这个共享内存段的名字
// @size:共享内存的大小,为4k的整数倍;
// @shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
// return:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
int shmget(key_t key, size_t size, int shmflg)

// 将共享内存段连接到进程地址空间
// @shmid:共享内存标识
// @shmaddr:指定连接的地址
// shmaddr为NULL,核心自动选择一个地址
// shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址
// shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址不会自动向下调整为SHMLBA的整数倍。公式:shmaddr-(shmaddr%SHMLBA)
// @shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
// shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
// return:成功返回一个指针,指向共享内存第一个字节;失败返回-1
void* shmat(int shmid, const void* shmaddr, int shmflg)

// 将共享内存段与当前进程脱离
// 注意:将共享内存段与当前进程脱离不等于删除共享内存段
// @shmaddr:由shmat所返回的指针
// return:成功返回0;失败返回-1
int shmdt(const void* shmaddr)

//用来控制共享内存
// @shmid:由shmget返回的共享内存的标识码
// @cmd:将要采取的动作(有三个可取值)
// IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值
// IPC_SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
// IPC_RMID:删除共享内存段
// @buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
// return:成功返回0;失败返回-1
int shmctl(int shmid, int cmd, struct shmid_ds* buf)

简单共享内存读写

写数据

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
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>

struct Conn_stat
{
int count;
char ip[64];
};


int main()
{
void *shm = NULL;

int shmid = 0, i = 0;
struct Conn_stat stat = {0,"127.0.0.1"};

// 创建共享内存
shmid = shmget((key_t)1234, sizeof(struct Conn_stat), 0666|IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(1);
}

// 将共享内存连接到当前进程的地址空间
shm = shmat(shmid, (void*)0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(2);
}
printf("Memory attached at %p\n", shm);

// 设置共享内存
struct Conn_stat *p = (struct Conn_stat*)shm;
memcpy(p,&stat,sizeof(struct Conn_stat));

// 修改共享内存中写数据
while((i++) < 30)
{
p->count++;
sleep(1);
}

// 把共享内存从当前进程中分离
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(3);
}

exit(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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include <string.h>
#include <errno.h>

struct Conn_stat
{
int count;
char ip[64];

};

int main()
{
void *shm = NULL;// 分配的共享内存的原始首地址
struct Conn_stat *stat = NULL;// 指向shm
int shmid;// 共享内存标识符

// 创建共享内存
shmid = shmget((key_t)1234, sizeof(struct Conn_stat), 0666|IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(0);
}
// 将共享内存连接到当前进程的地址空间
shm = shmat(shmid, 0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(1);
}
printf("\nMemory attached at %p\n", shm);
// 设置共享内存
stat = (struct Conn_stat*)shm;


int i = 0;
while((i++) < 10)
{

printf("ip = %s ,count: %d\t\t\n", stat->ip, stat->count);
sleep(1);
}

// 把共享内存从当前进程中分离
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(2);
}
// 删除共享内存
if(shmctl(shmid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "shmctl(IPC_RMID) failed, reason: %s\n",strerror(errno));
exit(3);
}
exit(0);
}

共享内存与信号量共用

ipc.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
#ifndef _IPC_H_
#define _IPC_H_

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>

#define ERR_EXIT(m) \
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)

int sem_create(key_t key);

int sem_open(key_t key);

int sem_setval(int semid, int val);

int sem_getval(int semid);

int sem_d(int semid);

int sem_p(int semid);

int sem_v(int semid);

#endif // _IPC_H_

ipc.cpp

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
#include "ipc.h"


#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>

union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};



int sem_create(key_t key)
{
int semid;
semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
if(semid == -1)
ERR_EXIT("semget");

return semid;
}

int sem_open(key_t key)
{
int semid;
semid = semget(key, 0, 0);
if(semid == -1)
ERR_EXIT("semget");

return semid;
}

int sem_setval(int semid, int val)
{
union semun su;
su.val = val;
int ret;
ret = semctl(semid, 0, SETVAL, su);
if(ret == -1)
ERR_EXIT("sem_setval");

return 0;
}

int sem_getval(int semid)
{
int ret;
ret = semctl(semid, 0, GETVAL, 0);
if(ret == -1)
ERR_EXIT("sem_getval");

return ret;
}

int sem_d(int semid)
{
int ret;
ret = semctl(semid, 0, IPC_RMID, 0);
if(ret == -1)
ERR_EXIT("sem_d");

return 0;
}

int sem_p(int semid)
{
struct sembuf sb = {0, -1, 0};
int ret;
ret = semop(semid, &sb, 1);
if(ret == -1)
ERR_EXIT("semop");

return ret;
}

int sem_v(int semid)
{
struct sembuf sb = {0, 1, 0};
int ret;
ret = semop(semid, &sb, 1);
if(ret == -1)
ERR_EXIT("semop");

return ret;
}

shmfifo.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
#ifndef _SHM_FIFO_H_
#define _SHM_FIFO_H_

#include "ipc.h"

typedef struct shmfifo shmfifo_t;
typedef struct shmhead shmhead_t;

struct shmhead
{
unsigned int blksize; // 块大小
unsigned int blocks; // 总块数
unsigned int rd_index; // 读索引
unsigned int wr_index; // 写索引
};

struct shmfifo
{
shmhead_t* p_shm; // 共享内存头部指针
char* p_payload; // 有效负载的起始地址

int shmid; // 共享内存ID
int sem_mutex; // 用来互斥用的信号量
int sem_full; // 用来控制内存是否满的信号量
int sem_empty; // 用来控制内存是否空的信号量
};

shmfifo_t* shmfifo_init(int key, int blksize, int blocks);
void shmfifo_put(shmfifo_t* fifo, void* buf);
void shmfifo_get(shmfifo_t* fifo, void* buf);
void shmfifo_destroy(shmfifo_t *fifo);

#endif //_SHM_FIFO_H_

shmfifo.cpp

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
#include "shmfifo.h"
#include <assert.h>

shmfifo_t* shmfifo_init(int key, int blksize, int blocks)
{
shmfifo_t *fifo = (shmfifo_t*) malloc(sizeof(shmfifo_t));
assert(fifo != NULL);
memset(fifo, 0, sizeof(shmfifo_t));

int shmid;
shmid = shmget(key, 0, 0);
int size = sizeof(shmhead_t) + blksize*blocks;
if(shmid == -1)
{
fifo->shmid = shmget(key, size, IPC_CREAT | 0666);
if(fifo->shmid == -1)
ERR_EXIT("shmget");

fifo->p_shm = (shmhead_t*)shmat(fifo->shmid, NULL, 0);
if(fifo->p_shm == (shmhead_t*)-1)
ERR_EXIT("shmat");

fifo->p_payload = (char*)(fifo->p_shm + 1);

fifo->p_shm->blksize = blksize;
fifo->p_shm->blocks = blocks;
fifo->p_shm->rd_index = 0;
fifo->p_shm->wr_index = 0;

fifo->sem_mutex = sem_create(key);
fifo->sem_full = sem_create(key+1);
fifo->sem_empty = sem_create(key+2);

sem_setval(fifo->sem_mutex, 1);
sem_setval(fifo->sem_full, blocks);
sem_setval(fifo->sem_empty, 0);
}
else
{
fifo->shmid = shmid;
fifo->p_shm = (shmhead_t*)shmat(fifo->shmid, NULL, 0);
if(fifo->p_shm == (shmhead_t*)-1)
ERR_EXIT("shmat");

fifo->p_payload = (char*)(fifo->p_shm + 1);

fifo->sem_mutex = sem_open(key);
fifo->sem_full = sem_open(key+1);
fifo->sem_empty = sem_open(key+2);
}

return fifo;
}

void shmfifo_put(shmfifo_t* fifo, void* buf)
{
sem_p(fifo->sem_full);
sem_p(fifo->sem_mutex);

memcpy(fifo->p_payload+fifo->p_shm->blksize*fifo->p_shm->wr_index, buf, fifo->p_shm->blksize);
fifo->p_shm->wr_index = (fifo->p_shm->wr_index + 1)%fifo->p_shm->blocks;
sem_v(fifo->sem_mutex);
sem_v(fifo->sem_empty);
}

void shmfifo_get(shmfifo_t* fifo, void* buf)
{
sem_p(fifo->sem_empty);
sem_p(fifo->sem_mutex);

memcpy(buf, fifo->p_payload+fifo->p_shm->blksize*fifo->p_shm->rd_index, fifo->p_shm->blksize);
fifo->p_shm->rd_index = (fifo->p_shm->rd_index + 1)%fifo->p_shm->blocks;
sem_v(fifo->sem_mutex);
sem_v(fifo->sem_full);
}

void shmfifo_destroy(shmfifo_t* fifo)
{
sem_d(fifo->sem_mutex);
sem_d(fifo->sem_full);
sem_d(fifo->sem_empty);

shmdt(fifo->p_shm);
shmctl(fifo->shmid, IPC_RMID, 0);
free(fifo);
}

shmfifo_send.cpp

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
#include "shmfifo.h"

typedef struct stu
{
char name[32];
int age;
} STU;

int main(void)
{
shmfifo_t* fifo = shmfifo_init(1234, sizeof(STU), 3);

STU s;
memset(&s, 0, sizeof(STU));
s.name[0] = 'A';
int i;
for(i=0;i<5;++i)
{
s.age = 20+i;
shmfifo_put(fifo, &s);
s.name[0] = s.name[0] + 1;

printf("send ok\n");
}

return 0;
}

shmfifo_recv.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "shmfifo.h"

typedef struct stu
{
char name[32];
int age;
} STU;

int main(void)
{
shmfifo_t* fifo = shmfifo_init(1234, sizeof(STU), 3);

STU s;
memset(&s, 0, sizeof(STU));
int i;
for(i=0;i<5;++i)
{
shmfifo_get(fifo, &s);

printf("name = %s age = %d\n", s.name, s.age);
}

return 0;
}

shmfifo_free.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "shmfifo.h"

typedef struct stu
{
char name[32];
int age;
} STU;


int main(void)
{
shmfifo_t* fifo = shmfifo_init(1234, sizeof(STU), 3);
shmfifo_destroy(fifo);

return 0;
}

文件映射

原理:将一个文件或其他对象映射到内存

  1. 使用普通文件提供的内存映射
  2. 使用特殊文件提供内存映射

相关函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 用途:将文件或者设备空间映射到共享内存去
// addr:要映射的起始地址,通常指定为NULL,让内核自动选择
// len:代表将文件中多大的部分映射到内存
// prot:映射区保护方式
// PROT_READ:页面可读
// PROT_WRITE:页面可写
// PROC_EXEC:页面可执行
// PROT_NONE:页面不可访问
// flags:标志,必须要指定MAP_SHARED 或MAP_PRIVATE
// MAP_SHARED:变动是共享的
// MAP_PRIIVATE:变动是私有的
// MAP_FIXED:准确解释addr参数
// MAP_ANONYMOUS:建立匿名映射区,不涉及文件,而且映射区域无法和其他进程共享。
// MAP_DENYWRITE:允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
// MAP_LOCKED:将映射区域锁定住,这表示该区域不会被置swap
// fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。
// offset:从文件头开始的偏移量,必须是分页大小的整数倍。
// 成功返回映射到的内存区的起始地址,失败返回-1
void* mmap(void* addr, size_t len, int prot, int flasg, int fd, off_t offset)

简单文件映射读写

写数据

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
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<string.h>

struct Conn_stat
{
int count;
char ip[64];
};


int main(int argc,char *argv[])
{
const char *file_name = "./counter.txt";

struct Conn_stat stat = {0,"127.0.0.1"};

int fd = open(file_name,O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd < 0)
{
perror("open");
exit(2);
}

ftruncate(fd,sizeof(struct Conn_stat));

// 创建一个结构体大小的共享映射区。共享映射区我们可以当做数组区看待。
struct Conn_stat *p = (struct Conn_stat*)mmap(NULL,sizeof(struct Conn_stat),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(p == MAP_FAILED)
{
perror("mmap");
exit(3);
}
// 关闭不用的文件描述符。
close(fd);

memcpy(p,&stat,sizeof(struct Conn_stat));

int i;
for (i = 0; i < 20; i++) {
p->count++;
sleep(1);
}

int ret = munmap(p,sizeof(struct Conn_stat));
if(ret < 0)
{
perror("mmumap");
exit(4);
}

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
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>

struct Conn_stat
{
int count;
char ip[64];
};

int main(int argc,char *argv[])
{
const char *file_name = "./counter.txt";

int fd = open(file_name, O_RDONLY,0644);
if(fd < 0)
{
perror("open");
exit(2);
}

struct Conn_stat stat;

struct Conn_stat *p = (struct Conn_stat*)mmap(NULL,sizeof(struct Conn_stat),PROT_READ,MAP_SHARED,fd,0);
if(p == MAP_FAILED)
{
perror("mmap");
exit(3);
}

close(fd);

int i = 0;
while((i++) < 20)
{

printf("ip = %s ,count: %d\t\t\n",p->ip,p->count);
sleep(1);
}

int ret = munmap(p,sizeof(stat));
if(ret < 0)
{
perror("mmumap");
exit(4);
}

return 0;
}

posix 共享内存

相关函数

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
// 用来创建或打开一个共享内存对象
// @name:共享内存对象的名字
// @oflag:与open函数类似,可以是O_RDONLY、O_RDWR、O_CREAT、O_EXCL、O_TRUNC等
// @mode:此参数总是需要设置,如果oflag没有指定了O_CREAT,可以指定为0
// return:成功返回非负整数文件描述符;失败返回-1
int shm_open(const char* name, int oflag, mode_t mode);

// 修改共享内存对象大小
// @fd:文件描述符
// @length:长度
// return:成功返回0;失败返回-1
int ftruncate(int fd, off_t length);

// 获取共享内存对象信息
// @fd:文件描述符
// @buf:返回共享内存状态
// return:成功返回0;失败返回-1
int fstat(int fd, struct stat* buf);

// 删除一个共享内存对象
// @name:共享内存对象的名字
// return:成功返回0;失败返回-1
int shm_unlink(const char* name);

// 将共享内存对象映射到进程地址空间
// @addr:要映射的起始地址,通常要指定为NULL,让内核自动选择
// @len:映射到进程地址空间的字节数
// @prot:映射区保护方式
// @flags:标志
// @fd:文件描述符
// @offset:从文件头开始的偏移量
// return:成功返回映射到的内存区的起始地址;失败返回-1
void* mmap(void* addr, size_t len, int prot, int flags, int fd, off_t offset);

简单共享内存读写

写数据

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
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */

#define ERR_EXIT(m) \
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)

typedef struct stu
{
char name[32];
int age;
} STU;

int main(int argc, char* argv[])
{
int shmid;
shmid = shm_open("/xyz", O_RDWR, 0666);

if(shmid == -1)
ERR_EXIT("shm_open");

printf("shm_open succ\n");

struct stat buf;
if(fstat(shmid, &buf) == -1)
ERR_EXIT("fstat");

printf("size=%ld mode=%o\n", buf.st_size, buf.st_mode & 07777);

STU* p;
p = (STU*)mmap(NULL, buf.st_size, PROT_WRITE, MAP_SHARED, shmid, 0);
if(p == MAP_FAILED)
ERR_EXIT("mmap");

strcpy(p->name, "test");
p->age = 20;

close(shmid);

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */

#define ERR_EXIT(m) \
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)

typedef struct stu
{
char name[32];
int age;
} STU;

int main(int argc, char* argv[])
{
int shmid;
shmid = shm_open("/xyz", O_RDONLY, 0666);

if(shmid == -1)
ERR_EXIT("shm_open");

printf("shm_open succ\n");

struct stat buf;
if(fstat(shmid, &buf) == -1)
ERR_EXIT("fstat");

printf("size=%ld mode=%o\n", buf.st_size, buf.st_mode & 07777);

STU* p;
p = (STU*)mmap(NULL, buf.st_size, PROT_READ, MAP_SHARED, shmid, 0);
if(p == MAP_FAILED)
ERR_EXIT("mmap");

printf("name=%s age=%d\n", p->name, p->age);

close(shmid);

return 0;
}