共享内存可以说是Linux下最快速、最有效的进程间通信。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间,进程A可以即时看到进程B对共享内存中数据的更新;反之,进程B也可以即时看到进程A对共享内存中数据的更新。
6.1 共享内存的概念
struct shmid_ds
{
struct ipc_perm shm_perm; /* 对应于共享内存的ipc_perm结构 */
size_t shm_segsz; /* 以字节表示的共享内存区域的大小 */
pid_t shm_lpid; /* 最近一次调用shmop函数的进行ID */
pid_t shm_cpid; /* 创建该共享内存的进程ID */
unsigned short shm_lkcnt; /* 共享内存区域被锁定的时间数 */
unsigned long shm_nattch; /* 当期使用该共享内存的进程数 */
time_t shm_atime; /* 最近一次附加操作的时间 */
time_t shm_dtime; /* 最近一次分离操作的时间 */
time_t shm_ctime; /* 最近一次修改的时间 */
};
结果体shmid_ds会根据不同的系统内核版本而略有不同,并且在不同的系统中会对共享存储段的大小有限制,在应用时请查询相应的系统手册。
6.2 共享内存的相关操作
6.2.1 创建或打开共享内存
#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>int shmget (key_t key, size_t size, int shmflg);
- 当key为IPC_PRIVATE时,创建一个新的共享内存,此时参数shmflg的取值无效。
- 当key不为IPC_PRIVATE时,且shmflg设置了IPC_CREAT位,而没有设置IPC_EXCL位,则执行操作由key取值决定。如果key为内核中某个已存在的共享内存的键值,则执行打开这个键的操作;反之,则执行创建共享内存的操作。
- 当key不为IPC_PRIVATE时,且shmflg设置了IPC_CREAT位和IPC_EXCL位,则只执行创建共享内存的操作。参数key的取值应与内核中已存在的任何共享内存的键值都不相同,否则函数调用失败,返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开共享内存的函数就可以了(即将shmflg设置为IPC_CREAT,而不设置IPC_EXCL)。
另外,当调用shmget函数创建一个新的共享内存时,此共享内存的shmid_ds结构被初始化。ipc_perm中的各个域被设置为相应的值,shm_lpid、shm_nattch、shm_atime、shm_dtime被初始化为0,shm_ctime被设置为系统当前时间。
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#define BUFSZ 1024
int main()
{
int shm_id;
shm_id = shmget(IPC_PRIVATE, BUFSZ, 0666);
if (shm_id < 0)
{
printf("shmget failed!\n");
exit(EXIT_FAILURE);
}
printf("create a shared memory segment successfully: %d\n", shm_id);
system("ipcs -m");
exit(EXIT_SUCCESS);
}
create a shared memory segment successfully: 3997706
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
0x00000000 294912 regan 600 524288 2 目标
0x00000000 3866625 regan 600 33554432 2 目标
0x00000000 3997706 regan 666 1024 0
$
6.2.2 附加
#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>void *shmat (int shmid, const void *shmaddr, int shmflg);
6.2.3 分离
#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>int shmdt (const viod *shmaddr);
6.2.4 共享内存的控制
#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>int shmctl (int shmid, int cmd, struct shmid_ds *buf);
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
取值 | 含义 |
IPC_STAT | 取shmid所指向内存共享段的shmid_ds结构,对参数buf指向的结构赋值 |
IPC_SET | 使用buf指向的结构对sh_mid段的相关结构赋值,只对以下几个域有作用,shm_perm.uid、shm_perm.gid及shm_perm.mode |
IPC_RMID | 删除shmid所指向的共享内存段,只有当shmid_ds结构的shm_nattch域为零时,才会真正执行删除命令,否则不会删除该段。注意此命令的请求规则与IPC_SET命令相同 |
SHM_LOCK | 锁定共享内存段在内存,此命令只能由超级用户请求 |
SHM_UNLOCK | 对共享内存段解锁,此命令只能由超级用户请求 |
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct
{
char name[4];
int age;
} people;
int main(int argc, char **argv)
{
int shm_id, i;
char tmp;
people *map;
if (argc != 2)
{
printf("Usage: atshm <identifier>");
exit(EXIT_FAILURE);
}
shm_id = atoi(argv[1]);
map = (people *)shmat(shm_id, NULL, 0);
tmp = 'a';
for (i = 0; i < 10; i++)
{
tmp += 1;
memcpy((*(map + i)).name, &tmp, 1);
(*(map + i)).age = 20 + i;
}
if (shmdt(map) == -1)
{
perror("detach error!\n");
}
exit(EXIT_SUCCESS);
}
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct
{
char name[4];
int age;
} people;
int main(int argc, char **argv)
{
int shm_id, i;
people *map;
if (argc != 2)
{
printf("Usage: atshm <identifier>");
exit(EXIT_FAILURE);
}
shm_id = atoi(argv[1]);
map = (people *)shmat(shm_id, NULL, 0);
for (i = 0; i < 10; i++)
{
printf("name: %s\t", (*(map + i)).name);
printf("age %d\n", (*(map + i)).age);
}
if (shmdt(map) == -1)
perror("detach error!\n");
exit(EXIT_SUCCESS);
}
$ ./shm_read 3997706
name: b age 20
name: c age 21
name: d age 22
name: e age 23
name: f age 24
name: g age 25
name: h age 26
name: i age 27
name: j age 28
name: k age 29
$
#define TEXT_SZ 2048
struct shared_use_st
{
int written_by_you;
char text[TEXT_SZ];
};
生产者程序shm_producer.c:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "shm_com.h"
int main()
{
int running = 1;
void *shared_memory = (void *)0;
struct shared_use_st *shared_stuff;
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("memory attached at %p\n", shared_memory);
shared_stuff = (struct shared_use_st *)shared_memory;
shared_stuff->written_by_you = 0;
while (running)
{
if (shared_stuff->written_by_you)
{
printf("You wrote: %s", shared_stuff->text);
sleep(rand() % 4);
shared_stuff->written_by_you = 0;
if (strncmp(shared_stuff->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);
}
消费者程序shm_customer.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "shm_com.h"
int main()
{
int running = 1;
void *shared_memory = (void *)0;
struct shared_use_st *shared_stuff;
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 attached at %p\n", shared_memory);
shared_stuff = (struct shared_use_st *)shared_memory;
while (running)
{
while (shared_stuff->written_by_you == 1)
{
sleep(1);
printf("waiting for client...\n");
}
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared_stuff->text, buffer, TEXT_SZ);
shared_stuff->written_by_you = 1;
if (strncmp(buffer, "end", 3) == 0)
running = 0;
}
if (shmdt (shared_memory) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
编译并运行:
$ ./shm_producer &
[1] 10229
memory attached at 0x7fc2c3060000
$ ./shm_customer
memory attached at 0x7fdaf18a0000
Enter some text: hello
You wrote: hello
waiting for client...
Enter some text: Welcome to my hometown!
You wrote: Welcome to my hometown!
waiting for client...
Enter some text: end
You wrote: end
$
shm_producer创建共享内存段,然后将它连接到自己的地址空间中,我们在共享内存的开始处使用了一个结构shared_use_st,该结构中有个标志written_by_you,当共享内存中有数据写入时就设置这个标志。这个标志被设置时,程序就从共享内存中读取文本并打印,然后清除这个标志表示已经读完数据,再用一个end特殊字符串来退出循环,接下来程序分离共享内存段并删除它。
shm_customer使用相同的键来取得并连接同一个共享内存段。然后它提示用户输入文本。如果标志written_by_you被设置,程序就知道客户进程还未读完上一次的数据,因此就继续等待。当其它进程清除了这个标志后,程序写入新数据并设置该标志。它也是使用end来终止并分离共享内存段。
注意,这里的同步标志(written_by_you)非常简陋,它包括一个非常缺乏效率的忙等待(不停地循环),实际编程中应该使用信号量或通过传递消息、生成信号的方法来提供应用程序读、写部分之间的一种更有效率的同步机制。
整理自 《Linux程序设计第4版》、《Linux C编程从初学到精通》。