UNP卷二 chapter13 Posix共享内存区

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),刚开始学习网络编程,如有不正确之处请大家多多指正。

猜你喜欢

转载自blog.csdn.net/TT_love9527/article/details/81383605