System V 共享内存

共享内存是常用的进程通信,两个进程可以直接共享访问同一块内存区域,本文介绍System V的共享内存的概念,并介绍了mmap和shm共享内存。

共享内存

共享内存是最快的IPC形式,一旦这样的内存映射到共享它的进程的地址空间,这些就进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。


共享内存示意图


mmap函数

  • 功能:将文件或者设备空间映射到共享内存区
  • 原型:

    1
    void *mmap(void *addr, size_t len, int port, int flags, int fd, off_t offset);
  • 参数:

    • addr:要映射的起始地址,通常指定为NULL,让内核自主选择
    • len:要映射到进程地址空间的字节数
    • port:映射区保护方式
    • flags:标志
    • fd:文件描述符
    • offset:从文件头开始的偏移量
  • 返回值:成功返回映射到的内存区的起始地址;失败返回-1

保护方式:

port 说明
PROT_READ 页面可读
PROT_WRITE 页面可写
PROT_EXEC 页面可执行
PROT_NONE 页面不可访问

flags:

flags 说明
MAP_SHARED 变动是共享的
MAP_PRIVATE 变动是私有的
MAP_FIXED 准确解释addr参数
MAP_ANONYMOUS 建立匿名映射区,不涉及文件

注意点:

  • 映射不能改变文件大小
  • 可用于进程间通信的有效地址空间不完全受限于被映射文件的大小
  • 文件一旦被映射,所有对映射区域的访问实际上是对内存区域的访问。映射区域内容写回文件时,不会超过文件的大小。

munmap函数

  • 功能:取消mmap函数建立的映射
  • 原型:

    1
    int munmap(void *addr, size_t len);
  • 参数:

    • addr:映射的内存起始地址
    • len:映射到进程地址空间的字节数
  • 返回值:成功返回0,失败返回-1

mmap读写的例子:

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 <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/fcntl.h>
#include <string.h>
#include <sys/mman.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
typedef struct stu {
char name[4];
int age;
} STU;
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s <filename>\n", argv[0]);
exit(EXIT_FAILURE);
}
int fd;
if ((fd = open(argv[1], O_CREAT | O_RDWR | O_TRUNC, 0666)) == -1) {
ERR_EXIT("open");
}
lseek(fd, sizeof(STU)*5-1, SEEK_SET);
write(fd, "", 1);
STU *p;
p = mmap(NULL, sizeof(STU)*5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == NULL) {
ERR_EXIT("mmap");
}
char ch = 'a';
int i;
for (i=0; i<5; ++i) {
memcpy((p+i)->name, &ch, 1);
(p+i)->age = 20+i;
ch++;
}
printf("initialize over\n");
munmap(p, sizeof(STU)*5);
printf("exit\n");
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
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/fcntl.h>
#include <string.h>
#include <sys/mman.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while (0)
typedef struct stu {
char name[4];
int age;
} STU;
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s <filename>\n", argv[0]);
exit(EXIT_FAILURE);
}
int fd;
if ((fd = open(argv[1], O_RDWR)) == -1) {
ERR_EXIT("open");
}
lseek(fd, sizeof(STU)*5-1, SEEK_SET);
write(fd, "", 1);
STU *p;
p = mmap(NULL, sizeof(STU)*5, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == NULL) {
ERR_EXIT("mmap");
}
char ch = 'a';
int i;
for (i=0; i<5; ++i) {
printf("name = %s, age = %d\n", (p+i)->name, (p+i)->age);
}
printf("initialize over\n");
munmap(p, sizeof(STU)*5);
printf("exit\n");
return 0;
}

mmsyc函数

  • 功能:对映射的共享内存执行同步操作
  • 原型:

    1
    int msync(void *addr, size_t len, int flags);
  • 参数:

    • addr:内存起始地址
    • len:长度
    • flags:选项
  • 返回值:成功返回0,失败返回-1

flags:

flags 说明
MS_ASYNC 执行异步写
MS_SYNC 执行同步写
MS_INVALIDATE 使高速缓存的数据失效

System V 共享内存数据结构

1
2
3
4
5
6
7
8
9
10
11
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};

共享内存函数

1
2
3
4
5
6
7
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

shmget函数

  • 功能:用来创建共享内存
  • 原型:

    1
    int shmget(key_t key, size_t size, int shmflg);
  • 参数:

    • key:共享内存段名字
    • size:共享内存大小
    • shmflg:由九个权限表示构成,他们的用法和创建文件时使用的mode模式表示是一样的
  • 返回值:成功返回一个非负整数,即该共享内存段的标示码;失败返回-1

shmat函数

  • 功能:将共享内存段连接到进程地址空间
  • 原型:

    1
    void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 参数:

    • shmid:共享内存标识
    • shmaddr,指定连接的地址
    • shmflag:它的两个可能取值是SHM_RND和SHM_RDONLY
  • 返回值:成功返回一个指针,指向共享内存第一个节,失败返回-1

注意:

  • shmaddr为NULL,核心自动选择一个地址。
  • shmaddr不为NULL且shmflag无SHM_RND标识,则以shmaddr连接地址。
  • shmaddr不为NULL且shmflag设置了SHM_RND标志,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr-(shmaddr%SHMLBA)
  • shmflg=SHM_RDONLY,标识连接操作用来只读共享内存

shmdt函数

  • 功能:将共享内存段与当前进程脱离
  • 原型:

    1
    int shmdt(const void *shmaddr);
  • 参数:

    • shmaddr:有shmat所返回的指针
  • 返回值:成功返回0,失败返回-1
    注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

  • 功能:用来创建和访问一个消息队列
  • 原型:

    1
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 参数:

    • shmid:由shmget返回的共享内存标识码
    • cmd:将要采取的动作(有三个可取值)
    • buf:指向一个保存这共享内存的模式状态和访问权限的数据结构
  • 返回值:成功返回0,失败返回-1

cmd:

命令 说明
IPC_STAT 把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET 在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构体中给出的值
IPC_RMID 删除共享内存段

共享内存实例

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
/*shmwrite.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/fcntl.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#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 = shmget(1234, sizeof(STU), IPC_CREAT | 0666);
if (shmid == -1) {
ERR_EXIT("shmget");
}
STU *p;
p = shmat(shmid, NULL, 0);
if (p == (void *)-1) {
ERR_EXIT("shmat");
}
strcpy(p->name, "harlon");
p->age = 24;
while (1) {
if (memcmp(p, "quit", 4) == 0) {
break;
}
}
shmdt(p);
if (shmctl(shmid, IPC_RMID, NULL) < 0) {
ERR_EXIT("shmctl");
}
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
/*shmread.c*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/fcntl.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#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 = shmget(1234, 0, 0);
if (shmid == -1) {
ERR_EXIT("shmget");
}
STU *p;
p = shmat(shmid, NULL, 0);
if (p == (void *)-1) {
ERR_EXIT("shmat");
}
printf("name = %s, age = %d\n", p->name, p->age);
strcpy(p->name, "quit");
shmdt(p);
return 0;
}