systemV 共享内存 测试

15.9 system V 共享内存
(1)概述
	1.共享内存允许两个或多个进程共享一个给定的存储区。
	2.共享内存是最快的一种IPC方式。
		1.因为数据不需要再客户进程和服务器进程之间复制
		2.一旦共享内存映射到进程的地址空间,进程之间的数据操作就不涉及内核了。
	3.信号量通常用于同步多个进程对共享内存的访问。
		因为服务进程在将数据完全写到共享内存之前,客户进程不应该去取这些数据。
	4.XSI 共享内存是内存的匿名端。
	5.XSI 共享内存和内存映射的区别是:前者没有相关文件,后者有相关文件。
	注意:	
		3.多个进程共存时,如果某个进程有多个线程,那么多个线程和其他进程一样可以执行semop操作。
			因为内核将线程当做进程一样执行调度。
(2)共享内存结构字段
	struct shmid_ds {
		struct ipc_perm shm_perm;	/* see 15.6.2*/ 包含SHM_DEST和SHM_LOCKED等字段
		size_t	shm_segsz;		// 共享内存的字节数
		pid_t	shm_lpid;		// 操作该共享内存的最后一个进程
		pid_t	shm_cpid;		// 最后一个修改该共享内存的进程
		shmatt_t	shm_nattch;	// attach该共享内存到地址空间的进程个数 
		time_t	shm_atime;		// attach共享内存的时间
		time_t	shm_dtime;		// 分离共享内存的时间
		time_t	shm_ctime;		// 调用IPC_SET的时间。
	}	
(2)创建新的/引用已经存在的共享内存
	int shmget(key_t key, size_t size, int flag)	
		key: 	XSI IPC键
		size:	共享内存的长度,以字节为单位。系统会取为页长的整数倍
			1.如果shmget函数引用一段共享内存,则size参数被忽略。
			2.创建一个新段时,共享内存区域被初始化为0;
		flag: 
			----------------------------------------------------------------
			oflag参数				key不存在(ENOENT)	key已经存在
			无特殊标志				出错,返回-1			成功,获取到已有标识符
			IPC_CREATE				成功				成功,获取到已有标识符
			IPC_CREATE|IPCEXCL		成功				出错,返回-1(EEXIST)
			----------------------------------------------------------------
(3)控制共享内存
	int shmctl(int shmid, int cmd, struct shmid_ds *buf)		
		shmid: 共享内存标识符
			IPC_STAT	取shmid_ds字段, 存储到buf中
			IPC_SET		按buf的结构设置该共享内存shmid_ds中的某些字段	
			IPC_RMID	从系统中删除该共享内存
			SHM_LOCK	在内存中对共享内存段加锁,此命令只能由超级用户执行。
			SHM_UNLOCK	解锁共享内存段,此命令只能由超级用户执行。
		注意:
			1.内核为system V共享内存的attach进程维护了引用计数,
			2.当使用IPC_RMID时,只有attach引用共享内存的进程数为1时,系统才会真正的删除共享内存。
				==》如果进程不使用共享内存,要及时调用shmdt函数对共享内存进行分离。
			
			
(4)进程使用共享内存				
	void *shmat(int shmid, const void *shmaddr, int shmflg)		
		shmid:  共享内存标识符
		返回值: 成功返回指向共享内存存储段的指针,出错返回-1。
		shmaddr:共享内存存放到虚拟地址空间的什么位置。
			1.shmaddr不为NULL
				1.shmflg指定了SHM_RND标志
					1)如果shmaddr是页地址的整数倍
						返回shmaddr
					2)如果shmaddr不是页地址的整数倍
						系统会找一个附近相应的页地址返回
					
				2.shmflg没有指定SHM_RND标志
					1)如果shmaddr是页地址的整数倍
						返回shmaddr
					2)如果shmaddr不是页地址的整数倍
						返回错误
			2.shmaddr为NULL	(UNNIX 推荐)
				共享内存要attach到本进程的地址由内核决定,地址通过返回值返回。
		flag:
			SHM_RND:
				attach到该进程的地址进行页地址取整
			SHM_RDONLY:
				1.如果指定了SHM_RDONLY标记,进程仅仅是读取共享内存段的内容。
				2.未指定SHM_RDONLY时,进程默认对共享内存拥有读写权限。
				
(5)进程分离共享内存
	1.当进程对共享内存操作结束时,则调用shmdt与共享内存段分离。
		注意:1.注意这不是从系统中删除共享内存标识符以及相应的数据结构
			  2.只有调用shmctl函数并传入IPC_RMID时,才会删除共享内存。 	
	2.shmdt(const void *shmaddr);
		1.该函数仅仅是使进程和共享内存脱离关系,并未删除共享内存
		2.shmdt的作用是将共享内存的引用计数shm_nattch减一
(6)测试
	1.有两个进程使用共享内存,进程二中有多个线程都执行PV操作(这是没问题的)线程与进程的调度方式一样
	2.每个进程读取共享内存中的数据后加1
	3.共享内存数据访问的保护,使用初值为1的信号量的PV操作进行。
	// 测试代码见博客

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/wait.h>
#include <sys/acct.h>
#include <errno.h>
#include <sys/times.h>
#include <pthread.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>

/*	
测试目的:
	1.有两个进程使用共享内存
	2.每个进程读取共享内存中的数据后加1
	3.共享内存数据访问的保护,使用初值为1的信号量的PV操作进行。

执行结果:
	child:0
	parent:1
	work:2
	child:3
	parent:4
	work:5
	child:6
	parent:7
	work:8
*/

#define SHMKEY_PATH "/data1/zhouhouping/nfs/testCode/chapter15/shm/systemV_shm/shmkey"
#define SEMKEY_PATH "/data1/zhouhouping/nfs/testCode/chapter15/shm/systemV_shm/semkey"
#define PRJ_NUM 45258793
#define PAGESIZE sysconf(_SC_PAGESIZE)


//注意: union semun联合体已经被内核注释掉了,需要自己定义一遍
union semun{
	int 			val;
	struct	senid_ds *buf;
	unsigned short *array;
	struct seminfo *__buf;
};

// 每个进程都有自己独特的副本,但是值肯定是相同的。这样写的目的是为了测试代码简洁。
static int semid;
static int shmid;
static key_t semkey;
static key_t shmkey;
static int *shmp;		// 共享内存的前4字节
static void *shmaddr;

void print_semop_errno()
{
	printf("E2BIG=%d\n", E2BIG);
	printf("EACCES=%d\n", EACCES);
	printf("EFAULT=%d\n", EFAULT);
	printf("EAGAIN=%d\n", EAGAIN);
	printf("EIDRM=%d\n", EIDRM);
	printf("EINTR=%d\n", EINTR);
	printf("EINVAL=%d\n", EINVAL);
	printf("ENOMEM=%d\n", ENOMEM);
	printf("ERANGE=%d\n", ERANGE);
}

void proc_init()
{


	// 生成键
	assert((key_t)-1 != (semkey = ftok(SEMKEY_PATH, PRJ_NUM)));
	assert((key_t)-1 != (shmkey = ftok(SHMKEY_PATH, PRJ_NUM)));

	// 创建IPC标识符,获取信号量和共享内存
	assert(-1 != (semid = semget(semkey, 1, IPC_CREAT | S_IRUSR| S_IWUSR))); // 没有就创建, 有就直接引用。
	assert(-1 != (shmid = shmget(shmkey, PAGESIZE, IPC_CREAT | S_IRUSR| S_IWUSR))); 


	// attach shm
	assert((void *)-1 != (shmaddr = shmat(shmid, NULL, SHM_RND))); // shmat

	shmp = (int *)shmaddr;
	*shmp = 0;
}
void semp()
{
	struct sembuf semoparry[1];
	semoparry[0].sem_num = 0;
	semoparry[0].sem_op = -1;
	semoparry[0].sem_flg = 0;
	semoparry[0].sem_flg |= SEM_UNDO;

	if(0 != semop(semid, semoparry, 1)){
		printf("ERROR: semp, errno=%d\n",errno);
		print_semop_errno();
		exit(0);
	}
}
void semv()
{
	int ret;
	
	struct sembuf semoparry[1];
	semoparry[0].sem_num = 0;
	semoparry[0].sem_op = +1;
	semoparry[0].sem_flg = 0;
	semoparry[0].sem_flg |= SEM_UNDO;

	if(0 != (ret == semop(semid, semoparry, 1))){
		printf("ERROR: semp, errno=%d\n",errno);
		exit(0);
	}
}
int get_semval()
{
	union semun getsemun;
	
	return semctl(semid, 0, GETVAL, getsemun);
}
void delete_sem()
{
	union semun ignsemun;
	
	semctl(semid, 0, IPC_RMID, ignsemun);
}

void delete_shm()
{
	struct shmid_ds ign;
	shmctl(shmid,  IPC_RMID, &ign);
}

void *work(void *arg)
{
	while(1)
	{
		// P操作,读取数据+1,V操作
		semp();
		printf("work:%d\n", *shmp);
		sleep(1);
		*shmp += 1;
		semv();
	}
	return NULL;
}
int main(int argc, char **argv)
{
	pid_t pid;
	pid =fork();
	union semun setsemun;
	pthread_t tid;
	
	assert(pid >= 0);
	if (pid == 0){		// child
		proc_init();
		// 初始化为0
		setsemun.val = 0;
		assert(0 == semctl(semid, 0, SETVAL, setsemun));
		semv(); // 设置初值为1
		while(1)
		{
			// P操作,读取数据+1,V操作
			semp();
			printf("child:%d\n", *shmp);
			sleep(1);
			*shmp += 1;
			semv();
		}
		shmdt(shmaddr);
		exit(0);
	}
	// parent
	else if ((pid > 0)){
		proc_init();
		sleep(1);
		pthread_create(&tid, NULL, work, NULL);
		while(1)
		{
			// P操作,读取数据+1,V操作
			semp();
			printf("parent:%d\n", *shmp);
			sleep(1);
			*shmp += 1;
			semv();
		}
	}
	pause();
	delete_sem();
	delete_shm();
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值