一、共享内存
共享内存允许两个不相关的进程访问同一个逻辑内存。它是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。
IPC 为进程创建一个特殊的地址范围,它将出现在该进程的地址空间中。其他进程可以将同一段共享内存连接到它们自己的地址空间中。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
注意:共享内存并不提供同步机制,在一个进程结束对共享内存的写操作之前,并没有自动的机制阻止其他进程开始对它进行读取。因此需要程序员提供同步控制,通常用共享内存来提供对大块内存区域的有效访问,通过传递小消息来同步对该内存的访问。
二、Linux 共享内存机制
Linux 中提供了一组函数接口用于使用共享内存,使用共享共存的接口与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h 中。
1、shmget函数
该函数用来创建共享内存,它的原型为:
int shmget(key_t key, size_t size, int shmflg);
- key(非0整数),共享内存段命名,shmget 函数成功时返回一个与 key 相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1。
不相关的进程可以通过该函数的返回值访问同一共享内存,程序对所有共享内存的访问都是通过调用 shmget 函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget函数的返回值)。
- size 以字节为单位指定需要共享的内存容量。
- shmflg 是包含 9 个比特的权限标志,可以与 IPC_CREAT 做或操作创建一个新的共享内存段,如果传递的是已存在的键,设置 IPC_CREAT 标识时并不是错误。
2、shmat函数
用来启动对共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:
void *shmat(int shm_id, const void *shm_addr, int shmflg);
- shm_id 是由 shmget 函数返回的共享内存标识。
- shm_addr 指定共享内存连接到当前进程中的地址位置,通常为 0,表示让系统来选择共享内存的地址。
- shm_flg 是一组标志位,通常为0。
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1。
3、shmdt函数
将共享内存从当前进程中分离,使该共享内存对当前进程不再可用。它的原型如下:
int shmdt(const void *shmaddr);
4、shmctl函数
用来控制共享内存,它的原型如下:
int shmctl(int shm_id, int command, struct shmid_ds *buf);
- shm_id 是 shmget 函数返回的共享内存标识符。
- command 是要采取的操作,它可以取下面的三个值 :
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值;
IPC_RMID:删除共享内存段。
- buf 是一个 shmid_ds 结构指针,它指向共享内存模式和访问权限的结构。
该结构至少包括以下成员:
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
三、进程间使用共享内存通信
下面的例子中,生产者和消费者运行过程如下图:
消费者:创建共享内存段,等待生产者连接,并写入数据,然后读取共享内存段数据。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#define TEXT_SZ 1024
struct shared_use {
int written; //生产者写完数据通知消费者
char some_text[TEXT_SZ]; //传送的文本
};
int main()
{
int running = 1;
void *shared_memory = (void *)0;
struct shared_use *shared_stuff;
int shmid;
srand((unsigned int)getpid());
shmid = shmget((key_t)1234, sizeof(struct shared_use), 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);
}
shared_stuff = (struct shared_use *)shared_memory;
shared_stuff->written_by = 0;
while(running) { //输出shared_use 中的文本
if(shared_stuff->written_by) { //判断生产者是否已写入数据
printf("you receiced :%s", shared_stuff->some_text);
sleep( rand() % 4 );
shared_stuff->written_by = 0;
if(strncmp(shared_stuff->some_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);
}
生产者:连接消费者创建的共享内存段,并向共享内存段写入数据,设置写入标识,等待消费者读取数据。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#define TEXT_SZ 1024
struct shared_use {
int written; //生产者写完数据通知消费者
char some_text[TEXT_SZ]; //传送的文本
};
int main()
{
int running = 1;
void *shared_memory = (void *)0;
struct shared_use *shared_stuff;
int shmid;
char buffer[BUFSIZ];
shmid = shmget((key_t)1234, sizeof(struct shared_use), 0666|IPC_CREAT); //获取共享内存段
if(shmid == -1) {
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
shared_memory = shmat(shmid, (void *)0, 0); //通过键 1234 连接到共享内存段
if(shared_memory == (void *)-1) {
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("memory attached at %X\n", (int)shared_memory);
shared_stuff = (struct shared_use *)shared_memory;
while(running) {
while(shared_stuff->written_by == 1) { //判断消费者是否读取了数据
sleep(1);
printf("waiting for client ...\n");
}
printf("enter some text:");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
shared_stuff->written_by = 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);
}
运行结果:
分析:上面的程序使用很简单的 written 变量作为两个进程读写同一共享内存的同步标志,这种方法缺乏效率,同时在应对多消费者进程时安全性也没有保障。
四、使用信号量同步的共享内存机制
信号量的操作都是是原子操作,即使在多个访问进程中也可以保证同一时刻只有一个进程访问同一共享内存段。关于信号量的知识可以参考Linux 进程间通信:信号量。
生产者 write.c 创建和初始化共享内存和信号量,获得共享内存的信号量权限后写入数据,释放共享内存权限,等待消费者读取数据。
消费者 read.c 连接共享内存和信号量,等待生产者释放共享内存权限,获取共享内存信号量权限,读取数据,释放共享内存权限。
信号量函数定义:sem_con.h
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sem.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
static int set_semvalue(int sem_id, int value); //初始化话信号量值
static void del_semvalue(int sem_id); //删除信号量
static int semaphore_p(int sem_id); //信号量等待
static int semaphore_v(int sem_id); //信号量可用
static int set_semvalue(int sem_id, int value)
{
union semun sem_union;
sem_union.val = value;
if(semctl(sem_id, 0, SETVAL, sem_union) == -1) return(0);
return(1);
}
static void del_semvalue(int sem_id)
{
union semun sem_union;
if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
fprintf(stderr, "failed to delete semaphore\n");
}
static int semaphore_p(int sem_id)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1; /* P() */
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1) {
fprintf(stderr, "semaphore_p failed\n");
return(0);
}
return(1);
}
static int semaphore_v(int sem_id)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1; /* V() */
sem_b.sem_flg = SEM_UNDO;
if(semop(sem_id, &sem_b, 1) == -1) {
fprintf(stderr, "semaphore_v failed\n");
return(0);
}
return(1);
}
生产者:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "sem_con.h"
#define TEXT_SZ 1024
struct shared_use {
char some_text[TEXT_SZ]; //传送的文本
};
int main()
{
int running = 1;
void *shared_memory = (void *)0;
struct shared_use *share;
int shmid;
int sem_id;
char buffer[BUFSIZ];
//创建一个 key 为 1234 的信号量
sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
//初始化信号量值为 1
if(!set_semvalue(sem_id, 1)) {
fprintf(stderr, "failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
//创建共享内存段
shmid = shmget((key_t)1235, sizeof(struct shared_use), 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);
}
share = (struct shared_use *)shared_memory;
while(running) {
//获取共享内存信号量权限
if(!semaphore_p(sem_id))
exit(EXIT_FAILURE);
printf("enter some text:");
fgets(buffer, BUFSIZ, stdin);
strncpy(share->some_text, buffer, TEXT_SZ);
//释放权限,离开共享内存
if(!semaphore_v(sem_id))
exit(EXIT_FAILURE);
sleep(rand() % 2);
if(strncmp(buffer, "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);
}
sleep(10);
del_semvalue(sem_id); //删除信号量
exit(EXIT_SUCCESS);
}
消费者:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "sem_con.h"
#define TEXT_SZ 1024
struct shared_use {
char some_text[TEXT_SZ]; //传送的文本
};
int main()
{
int running = 1;
void *shared_memory = (void *)0;
struct shared_use *share;
int shmid;
int sem_id;
//连接一个 key 为 1234 的信号量
sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
//初始化信号量值为 0,等待生产者写入数据
if(!set_semvalue(sem_id, 0)) {
fprintf(stderr, "failed to initialize semaphore\n");
exit(EXIT_FAILURE);
}
shmid = shmget((key_t)1235, sizeof(struct shared_use), 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 %X\n", (int)shared_memory);
share = (struct shared_use *)shared_memory;
while(running) { //输出 some_text 中的文本
//进入共享内存,信号量等待
if(!semaphore_p(sem_id))
exit(EXIT_FAILURE);
printf("%d receiced :%s", getpid(), share->some_text);
sleep( rand() % 4 );
//离开共享内存,信号量可用
if(!semaphore_v(sem_id))
exit(EXIT_FAILURE);
sleep(rand() % 2);
if(strncmp(share->some_text, "end", 3) == 0) running = 0;
}
if(shmdt(shared_memory) == -1) { //分离共享内存
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
运行结果:
分析:上面的程序利用信号量实现了共享内存的读写同步机制,但是只限于一对一进程,对于多个消费者进程或者多个生成者进程并没有进行深入,还存在一定的缺陷。
参考文献:http://blog.csdn.net/ljianhui/article/details/10253345
Linux 程序设计第 4 版