14.2 共享内存
共享内存是3个IPC机制中的第二个,它允许两个不相关的进程访问同一个逻辑内存.共享内存是在两个正在运行的进程之间传递数据的一种非常有效的方式.大多数共享内存的 具体实现,都把由不同进程之间共享的内存安排为同一段物理内存.共享内存是由IPC为进程创建的一个特殊的地址访问,它将出现在该进程的地址空间中,其他进程可以将同一段共享内存连接到它们自己的地址空间中.所有进程都可以访问共享内存中的地址,就好像它们是由malloc分配的一样.如果某个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的其他进程看到.
共享内存为在多个进程之间共享和传递数据提供了一种有效的方式.由于它并未提供同步机制,所以通常需要用其他的机制来同步对共享内存的访问.一般是用共享内存来提供对大块内存区域的有效访问,同时通过传递小消息来同步对该内存的访问.
在第一个进程结束对共享内存的写操作之前,并无自动的机制可以阻止第二个进程开始对它进程读取.对共享内存访问的同步控制必须由程序员来负责.
共享内存使用的函数类似于信号量的函数,它们的定义定义如下:
#include <sys/shm.h>
void *shmat(int shm_id, const void *shm_addr, int shmflg);
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
int shmdt(const void *shm_addr);
int shmget(key_t key, size_t size, int shmflg);
与信号量的情况一样,头文件sys/types.h和sys/ipc.h通常被shm.h自动包含进程序.
14.2.1 shmget函数
函数作用
shmget函数用于创建共享内存函数原型
int shmget(key_t key, size_t size, int shmflg);
函数参数
第一个参数key,它有效地为共享内存段命名.第二个参数size以字节为单位指定需要共享的内存容量
第三个参数shmflg包含9个比特的权限标志,它们的作用与创建文件时使用的mode标志一样.由IPC_CREAT定义的一个特殊比特必须和权限标志按位或才能创建一个新的共享内存段.
权限标志对共享内存非常有用,因为它们允许一个进程创建的共享内存可以被共享内存的创建者所拥有的进程写入,同时其他用户创建的进程只能读取该共享内存.可以用这个功能来提供一种有效的对数据进行只读访问的方法,通过将数据放入共享内存并设置它的权限,就可以避免数据被其他用户修改.
函数返回值
如果成功,则返回一个共享内存标识符(将用于后续的共享内存函数).如果失败,则返回-114.2.2 shmat函数
函数作用
第一次创建共享内存时(shmget函数创建),它不能被任何进程访问.要想启用对该共享内存的访问,必须将其连接到一个进程的地址空间中,这项工作由shmat函数完成.函数原型
void *shmat(int shm_id, const void *shm_addr, int shmflg);
函数参数
第一个参数shm_id是由shmget函数返回的共享内存标识符第二个参数shm_addr指定的是共享内存连接到当前进程中的地址位置,通常是一个空指针,表示让系统来选择共享内存出现的地址.
第三个参数shmflg是一组位标志.它的两个可能值是SHM_RND(这个标志与shm_addr联合使用,用来控制共享内存连接的地址)和SHM_RDONLY(它使得连接的内存只读).很少需要控制共享内存连接的地址,通常都是让系统来选择一个地址,否则就会使应用程序对硬件的依赖性过高.
函数返回值
如果成功,则返回一个指向共享内存第一个字节的指针.如果失败,则返回-1.共享内存的读写权限由它的属主(共享内存的创建者),它的访问权限和当前进程的属主决定.共享内存的访问类似于文件的访问权限.
这个规则的一个例外是,当shmflg&SHM_RDONLY为true时,即使该共享内存的访问权限允许写操作,它都不能被写入.
14.2.3 shmdt函数
函数作用
shmdt函数的作用是将共享内存从当前进程中分离函数原型
int shmdt(const void *shm_addr);
函数参数
第一个参数是shmat函数返回的指针.函数返回值
如果成功,则返回0.如果失败,则返回-1.注意,将共享内存分离并未删除,只是使得该共享内存对当前进程不再可用.
14.2.4 shmctl函数
函数作用
shmctl函数用于控制共享内存函数原型
int shmctl(int shm_id, int command, struct shmid_ds *buf);
函数参数
第一个参数shm_id是shmget返回的共享内存标识符第二个参数command是将要采取的动作,它可以取3个值,如下表所示:
命令 说明
IPC_STAT 把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET 如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID 删除共享内存段
第三个参数buf是一个指针,它指向包含共享内存模式和访问权限的结构shmid_ds.
shmid_ds结构至少包含以下成员:
struct shmid_ds {
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
函数返回值
如果成功,则返回0.如果失败,则返回-1编写程序shm1.c和shm2.c.
/*************************************************************************
> File Name: shm1.c
> Description: shm1.c是消费者程序
> Author: Liubingbing
> Created Time: 2015年07月18日 星期六 13时07分46秒
> Other: shm1.c
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
/* sys/shm.h中定义了共享内存使用的函数 */
#include <sys/shm.h>
#include "shm_com.h"
int main()
{
int running = 1;
void *shared_memory = (void*)0;
/* 定义了指向结构shared_use_st的指针shared_stuff */
struct shared_use_st *shared_stuff;
int shmid;
srand((unsigned int)getpid());
/* shmget创建共享内存段
* 第一个参数有效地为共享内存段命名
* 第二个参数以字节为单位指定需要共享的内存容量
* 第三个参数包含9个比特的权限标志
* 如果成功,则返回一个进程标识符(将用于后续的共享内存函数).如果失败,则返回-1 */
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
if (shmid == -1) {
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
/* shmat函数启动对共享内存的访问,它将共享内存连接到一个进程的地址空间
* 第一个参数shmid是shmget函数返回的共享内存标识符
* 第二个参数指定的是共享内存连接到当前进程中的地址位置,通常是空指针,表示让系统来选择共享内存出现的地址
* 第三个参数一组位标志,一般取0
* 如果成功,则返回一个指向共享内存第一个字节的指针.如果失败,则返回-1 */
shared_memory = shmat(shmid, (void *)0, 0);
/* 判断shmat函数是否调用成功 */
if (shared_memory == (void *)-1) {
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
/* shared_memory是指向共享内存第一个字节的指针 */
printf("Memory attached at %X\n", (int)shared_memory);
/* shared_stuff是指向结构shared_use_st的指针,转换后它指向共享内存 */
shared_stuff = (struct shared_use_st *)shared_memory;
/* 当数据写入结构shared_use_st时,标志writen_by_you通知消费者 */
shared_stuff->written_by_you = 0;
while (running) {
if (shared_stuff->written_by_you) {
/* 如果标志written_by_you为1,则输出some_text(这其实也意味着有新的数据读入到some_text ) */
printf("You wrote: %s", shared_stuff->some_text);
sleep(rand() % 4);
/* 输出some_text的数据之后,将written_by_you置为0 */
shared_stuff->written_by_you = 0;
if (strncmp(shared_stuff->some_text, "end", 3) == 0) {
running = 0;
}
}
}
/* shmdt函数将共享内存从当前进程中分离 */
if (shmdt(shared_memory) == -1) {
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
/* shmctl函数用于控制共享内存
* 第一个参数shmid是shmget函数返回的共享内存标识符
* 第二参数command是将要采取的动作,IPC_RMID表示删除共享内存段
* 第三个参数buf是一个指针,它指向包含共享内存模式和访问权限的结构 */
if (shmctl(shmid, IPC_RMID, 0) == -1) {
fprintf(stderr, "shmctrl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
shm2.c
/*************************************************************************
> File Name: shm2.c
> Description: shm2.c程序是生产者程序,通过它向消费者程序shm1.c输入数据
> Author: Liubingbing
> Created Time: 2015年07月18日 星期六 13时40分15秒
> Other: shm2.c
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
/* sys/shm.h文件定义了共享内存使用的函数 */
#include <sys/shm.h>
#include "shm_com.h"
int main()
{
int running = 1;
void *shared_memory = (void *)0;
/* shared_stuff是指向shared_use_st结构的指针
* shared_use_st结构在消费者和生产者程序中都会用到,当有数据写入这个结构时,written_by_you通知消费者 */
struct shared_use_st *shared_stuff;
char buffer[BUFSIZ];
int shmid;
/* shmget函数用于创建创建共享内存
* 第一个参数key有效地为共享内存段命名
* 第二个参数size_t以字节为单位指定需要共享的内存容量
* 第三个参数shmflg包含9个比特的权限标志,类似文件访问权限
* 如果成功,则返回非负整数,即共享内存标识符.如果失败,则返回-1 */
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
/* 判断共享内存是否已经创建成功 */
if (shmid == -1) {
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
/* shmat函数启用对共享内存的访问,它将共享内存连接到一个进程的地址空间
* 第一个参数shmid是由shmget返回的共享内存标识符
* 第二个参数shm_addr指定的是共享内存连接到当前进程中的地址位置,通常是空指针,表示让系统来选择共享内存出现的地址
* 第三个参数shmflg是一组位标志
* 如果成功,则返回一个指向共享内存第一个字节的指针.如果失败,则返回-1 */
shared_memory = shmat(shmid, (void *)0, 0);
/* 判断启动对共享内存的访问是否成功 */
if (shared_memory == (void *)-1) {
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
/* shared_memory是指向共享内存第一个字节的指针 */
printf("Memory attached at %X\n", (int)shared_memory);
shared_stuff = (struct shared_use_st *)shared_memory;
while (running) {
/* 判断标志written_by_you是否为1,如果为1,表示some_text中的数据还未被读取,则该进程继续sleep等待some_text被读取 */
while (shared_stuff->written_by_you == 1) {
sleep(1);
printf("waiting for client...\n");
}
printf("Enter some text: ");
/* 从标准输入stdin中的读入至多BUFSIZ个字节的数据到buffer指向的内存中 */
fgets(buffer, BUFSIZ, stdin);
/* strncpy从buffer指向的内存中拷贝至多TEXT_SZ个字节的数据到share_stuff->some_text指向的内存中 */
strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
/* 设置标志written_by_you为1 */
shared_stuff->written_by_you = 1;
/* 判断buffer的前三个字节是否为"end" */
if (strncmp(buffer, "end", 3) == 0) {
running = 0;
}
}
/* shmdt函数将共享内存从当前进程中分离
* 参数shared_memroy是shmat函数(启用共享内存的访问)返回的地址指针
* 如果成功,则返回0.如果失败,则返回-1
* 注意,将共享内存分离并未删除它,只是使得该共享内存对当前进程不再可用 */
if (shmdt(shared_memory) == -1) {
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
第一个程序是消费者,它将创建一个共享内存段,然后把写到它里面的数据都显示出来.第二个程序是生产者,它将连接一个已有的共享内存段,并允许向其中输入数据.
首先创建一个公共的头文件,来定义将要分发的共享内存,命名为shm_com.h
/*************************************************************************
> File Name: shm_com.h
> Description: shm_com.h定义将要分发的共享内存
> Author: Liubingbing
> Created Time: 2015年07月18日 星期六 13时02分43秒
> Other: shm_com.h被消费者(shm1.c)和生产者(shm2.c)程序都会用到.当有数据写入到这个结构时,用该结构中的written_by_you通知消费者.
************************************************************************/
#ifndef _SHM_COM_H
#define _SHM_COM_H
#define TEXT_SZ 2048
struct shared_use_st {
int written_by_you;
char some_text[TEXT_SZ];
};
#endif
这里定义的结构在消费者和生产者程序中都会用到.当有数据写入这个结构时,则用该结构中的一个整型标志written_by_you来通知消费者.需要传输的文件长度2K是随意的.
运行程序,得到如下结果:
程序解析
第一个程序 shm1创建共享内存段,然后将它连接到自己的地址空间中.在共享内存的开始处使用了一个结构shared_use_st.该结构中有个标志written_by_you,当共享内存中有数据写入时,就设置这个标志为1.程序就从共享内存中读取文本,将它打印出来后,设置这个标志为0.用一个特殊字符串end来退出循环,程序分离共享内存段并删除它.第二个程序 shm2使用相同的键1234来取得并连接同一个共享内存段,然后它提示用户输入这些文本.如果标志written_by_you被设置为1,shm2就知道客户进程还未读完上一次的数据,因此就继续等待.当其他进程设置written_by_you为0时,shm2写入新数据并设置标志written_by_you为0,它还使用字符串end来终止并分离共享内存段.
注意,程序中非常简陋的同步标志written_by_you它包含一个非常缺乏效率的忙等待(不停地循环),这个可以使得程序非常简单,但在实际编程中,应该使用信号量或传递消息(使用管道或IPC消息),生成信号的方法来提供应用程序读,写部分之间的一种更有效率的同步机制.