1、Posix共享内存区
第十二章,在父子进程间使用内存映射文件提供共享内存。其实共享内存还可应用在无亲缘关系进程间。以下有两种无亲缘关系进程间共享内存的方法。
i、内存映射文件:由open函数打开,由mmap函数把得到的描述符映射到当前进程地址空间中的一个文件。
ii、共享内存区对象:由shm_open打开一个Posix.1IPC名字(也许中在文件系统中的一个路径名,貌似行不通),所返回的描述符由mmap函数映射到当前进程的地址空间。
两者的使用特点见下图:
2、shm_open、shm_unlink、ftruncate和fstat函数
Posix共享内存区涉及以下两个步骤要求:
i、指定一个名字参数调用shm_open,以 创建一个新的共享内存区对象或打开一个已存在的共享内存区对象。
ii、调用mmap把这个共享内存区映射到调用进程的地址空间。
#include<sys/mman.h>
int shm_open(const char* name, int oflag, mode_t mode);
//返回:若成功则为非负描述符,若出错则为-1
oflag参数必须或者含有O_RDONLY标志,或者含有O_RDWR,还可以指定O_CREAT、O_EXCL或O_TRUNC和O_EXCL标志。注意,如果随O_RDWR指定O_TRUNC标志,而且所需的共享内存区对象已经存在,那么它将被截短成0长度。
mode参数指定权限位,它在指定了O_CREAT标志的前提下使用。值得注意是,与mq_open和sem_open函数不同,shm_open的mode参数总是必须指定。如果没有指定O_CREAT标志,那么该参数可以指定为0。
针对shm_unlink函数,删除一个共享内存区对象的名字(注意存在引用计数器哈)。
#include<sys/mman.h>
int shm_unlink(const char* name);
//返回:若成功则为0,若出错则为-1
针对ftruncate函数,在处理mmap的时候,普通文件或共享内存区对象的大小都可以通过调用ftruncate修改。
#include<unistd.h>
int ftruncate(int fd, off_t length);
//返回:若成功则为0,若出错则为-1
i、对于一个普通文件:如果该文件的大小大于length参数,额外的数据就被丢弃掉。如果该文件的大小小于length,那么该文件是修改以及其大小是否增长是未加说明的。
ii、对于一个共享内存区对象:ftruncate把该对象的大小设置成length字节。
针对fstat函数:当打开一个已存在的共享内存区对象时,可调用fstat来获取有关该对象的信息。
#include<sys/type.h>
#include<sys/stat.h>
int fstat(int fd, struct stat* buf);
//返回:若成功则为0,若出错则为-1
struct stat {
...
mode_t st_mode;/* mode: S_I{RW}{USR,GRP,OTH}*/
uid_t st_uid;/* USER ID of owner*/
gid_t st_gid;/* group ID of owner*/
off_t st_size;/* size in bytes*///此参数可获取内存共享区对象大小
...
};
3、上述函数简单函数的应用
shmcreate程序
#include "unpipc.h"
int
main(int argc, char **argv)
{
int c, fd, flags;
char *ptr;
off_t length;
flags = O_RDWR | O_CREAT;
while ( (c = Getopt(argc, argv, "e")) != -1) {
switch (c) {
case 'e':
flags |= O_EXCL;
break;
}
}
if (optind != argc - 2)
err_quit("usage: shmcreate [ -e ] <name> <length>");
length = atoi(argv[optind + 1]);
fd = Shm_open(argv[optind], flags, FILE_MODE);//打开共享内存区对象
Ftruncate(fd, length);//指定长度
ptr = Mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//进行内存映射到调用进程的地址空间
exit(0);
}
shmunlink程序
#include "unpipc.h"
int
main(int argc, char **argv)
{
if (argc != 2)
err_quit("usage: shmunlink <name>");
Shm_unlink(argv[1]);//从系统中主动删除共享内存区对象的名字,存在引用计数被删除限制
exit(0);
}
shmwrite程序
#include "unpipc.h"
int
main(int argc, char **argv)
{
int i, fd;
struct stat stat;
unsigned char *ptr;
if (argc != 2)
err_quit("usage: shmwrite <name>");
/* 4open, get size, map */
fd = Shm_open(argv[1], O_RDWR, FILE_MODE);//创建以读写权限打开内存共享区对象
Fstat(fd, &stat);//获取内存共享区对象状态
ptr = Mmap(NULL, stat.st_size, PROT_READ | PROT_WRITE,//内存映射
MAP_SHARED, fd, 0);
Close(fd);
/* 4set: ptr[0] = 0, ptr[1] = 1, etc. */
for (i = 0; i < stat.st_size; i++)
*ptr++ = i % 256;//给共享内存写入数据
exit(0);
}
shmread程序
#include "unpipc.h"
int
main(int argc, char **argv)
{
int i, fd;
struct stat stat;
unsigned char c, *ptr;
if (argc != 2)
err_quit("usage: shmread <name>");
/* 4open, get size, map */
fd = Shm_open(argv[1], O_RDONLY, FILE_MODE);
Fstat(fd, &stat);
ptr = Mmap(NULL, stat.st_size, PROT_READ,
MAP_SHARED, fd, 0);
Close(fd);
/* 4check that ptr[0] = 0, ptr[1] = 1, etc. */
for (i = 0; i < stat.st_size; i++)
if ( (c = *ptr++) != (i % 256))//验证共享内存写入的数据模式
err_ret("ptr[%d] = %d", i, c);
exit(0);
}
4、多个客户向一个服务器发送消息(多个生产者、单个消费者,举例代码)
背景:服务器启动后创建一个共享内存区对象,各个客户进程向其中放置消息。而服务器只是负责输出这些消息。
以下定义了一个给出共享内存区对象布局的结构的头文件:
#include "unpipc.h"
#define MESGSIZE 256 /* max #bytes per message, incl. null at end */
#define NMESG 16 /* max #messages */
struct shmstruct { /* struct stored in shared memory */
sem_t mutex; /* three Posix memory-based semaphores *///基于内存的信号量
sem_t nempty;
sem_t nstored;
int nput; /* index into msgoff[] for next put */
long noverflow; /* #overflows by senders *///溢出计数器
sem_t noverflowmutex; /* mutex for noverflow counter */
long msgoff[NMESG]; /* offset in shared memory of each message *///指出各个消息的起始位置
char msgdata[NMESG * MESGSIZE]; /* the actual messages */
};
从共享内存区中取得并输出消息的服务器程序:
#include "cliserv2.h"
int
main(int argc, char **argv)
{
int fd, index, lastnoverflow, temp;
long offset;
struct shmstruct *ptr;
if (argc != 2)
err_quit("usage: server2 <name>");
/* 4create shm, set its size, map it, close descriptor */
shm_unlink(Px_ipc_name(argv[1])); /* OK if this fails */
fd = Shm_open(Px_ipc_name(argv[1]), O_RDWR | O_CREAT | O_EXCL, FILE_MODE);
ptr = Mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
Ftruncate(fd, sizeof(struct shmstruct));//调整共享内存区对象的大小(这一步是必须的,否则大小未给定)
Close(fd);
/* 4initialize the array of offsets */
for (index = 0; index < NMESG; index++)
ptr->msgoff[index] = index * MESGSIZE;//每个消息的偏移量不允许超出256字节
/* 4initialize the semaphores in shared memory */
Sem_init(&ptr->mutex, 1, 1);//第个二参数为1,表明在进程间使用信号量
Sem_init(&ptr->nempty, 1, NMESG);
Sem_init(&ptr->nstored, 1, 0);
Sem_init(&ptr->noverflowmutex, 1, 1);
/* 4this program is the consumer */
index = 0;
lastnoverflow = 0;
for ( ; ; ) {
Sem_wait(&ptr->nstored);//等待缓存中有消息进来
Sem_wait(&ptr->mutex);
offset = ptr->msgoff[index];//读取每个存储消息起始地址的偏移量
printf("index = %d: %s\n", index, &ptr->msgdata[offset]);//输出相应的消息
if (++index >= NMESG)//输出消息数超过NMESG时,取模
index = 0; /* circular buffer */
Sem_post(&ptr->mutex);
Sem_post(&ptr->nempty);
Sem_wait(&ptr->noverflowmutex);
temp = ptr->noverflow; /* don't printf while mutex held */
Sem_post(&ptr->noverflowmutex);
if (temp != lastnoverflow) {
printf("noverflow = %d\n", temp);//只显示溢出消息的数量,但并无数据(由于溢出,肯定是无数据啦!)
lastnoverflow = temp;
}
}
exit(0);
}
在共享内存区中给服务器存放消息的客户程序:
#include "cliserv2.h"
int
main(int argc, char **argv)
{
int fd, i, nloop, nusec;
pid_t pid;
char mesg[MESGSIZE];
long offset;
struct shmstruct *ptr;
if (argc != 4)
err_quit("usage: client2 <name> <#loops> <#usec>");
nloop = atoi(argv[2]);
nusec = atoi(argv[3]);
/* 4open and map shared memory that server must create */
fd = Shm_open(Px_ipc_name(argv[1]), O_RDWR, FILE_MODE);
ptr = Mmap(NULL, sizeof(struct shmstruct), PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
Close(fd);
pid = getpid();
for (i = 0; i < nloop; i++) {
Sleep_us(nusec);
snprintf(mesg, MESGSIZE, "pid %ld: message %d", (long) pid, i);
if (sem_trywait(&ptr->nempty) == -1) {//对互及锁非阻塞型的消息,是由于没有位置存放消息,却由不愿意等待产生溢出
if (errno == EAGAIN) {
Sem_wait(&ptr->noverflowmutex);
ptr->noverflow++;
Sem_post(&ptr->noverflowmutex);
continue;
} else
err_sys("sem_trywait error");
}
Sem_wait(&ptr->mutex);
offset = ptr->msgoff[ptr->nput];//获取下一数据输入的地址偏移量
if (++(ptr->nput) >= NMESG)
ptr->nput = 0; /* circular buffer */
Sem_post(&ptr->mutex);
strcpy(&ptr->msgdata[offset], mesg);//由于是向缓冲空间写入消息,此时写入数据过程不需要被&ptr->mutex锁住
Sem_post(&ptr->nstored);
}
exit(0);
}
综上特点:
Posix共享内存对象不必作为一个文件来实现。
共享内存区对象是由描述符来表示,其大小是使用ftruncate来设置。
以上知识点来均来自steven先生所著UNP卷二(version2),刚开始学习网络编程,如有不正确之处请大家多多指正。