共享内存(SYSTEM-V IPC) (和信号量搭配使用(生产者--消费者))

原理:
     

              [进程1]    [进程2]
         strcpy ↓          ↑ printf
        ________↓__________↑_____
        |     | 共享内存      |  |
        |     |(PAGE_SIZE)   |  |    (内存空间)
        |_____|______________|__|
             (共享内存原理)


(1).共享内存概述
    1.管道、 FIFO 和消息队列,任意两个进程之间想要交换信息,都必须通过内核,
      内核在其中发挥了中转站的作用:

        -------------------------------------------------------------------------  
        |    [进程A]                        [进程B]                              |    
        |        \write(msgsnd)              /read/msgrcv        用户层          |
        |-----------------------------------------------------------------------|               
        |                                                                       |                                         
        |          \                        /                    内核层          |                   |                                                                       |
        |          ↓                       ↑                                    |                 |                                                                       |          
        |         [IPC(管道.FIFO.消息队列)]                                      |    
        |-------------------------------------------------------------------------
                                 (管道、 FIFO 和消息队列    )
            缺点: 一个通信周期内,上述过程至少牵扯到两次内存拷贝和两次系统调用    

                2.共享内存: 内核搭台, 进程唱戏。
   步骤:1.内核负责构建出一片内存区域
              2.两个或多个进程可以将这块内存区域映射到自己的虚拟地址空间
              3.从此之后内核不再参与双方通信,进程之间使用共享内存通信
                (当进程使用共享内存时,可能会发生缺页,引发缺页中断,这种情况下,内核还是会参与进来的。)
              4.共享内存所有进程间通信方式中效率最高的一种.
              5.进程可以像操作普通进程的地址空间一样操作这块共享内存,一个进程可以将信息写入这片内存区域,
                 而另一个进程也可以看到共享内存里面的信息,从而达到通信的目的
              6.注意: 
                要防止多个进程同时操作同一块共享内存(使用信号量(生产者-消费者模型)搭配使用,来控制共享内存资源的互斥访问)
  
  

        --------------------------------------------------
        |                            用户空间:            |    
        |    |--------|    |--------|                    |        
        |    | 进程A   |   |进程B   |                     |                    
        |    |--------|    |--------|                    |                                
        |    | map    |    | map    |                    |                    
        |    |--------|    |--------|                    |                
        |     ↓      ↑       ↓   ↑                       |                
        |    [    共  享  内  存    ]                     |                            
        --------------------------------------------------   


(2)创建或打开共享内存        
    int shmget(key_t key, size_t size, int shmflg);
        size: 必须是正整数,实际 size 会被向上取整为页面大小的整数倍。
        shmflg:支持 IPC_CREAT 和 IPC_EXCL 标志位 
        单个共享内存段的最大字节数为 SHMMAX //cat /proc/sys/kernel/shmmax


(3)使用共享内存: 将共享内存映射到进程的虚拟地址空间
    void *shmat(int shmid, const void *shmaddr, int shmflg);
        通常会将第二个参数设置为 NULL,表示由内核分配共享内存区域
        shmid: 为shmget返回的共享内存标识符ID
        shmflg: 如果进程仅仅是读取共享内存段的内容,并不修改,则可以指定 SHM_RDONLY 标志位。
    // SHM_KEY所在的共享内存不存在时创建他, 若存在返回错误
    第一个进程: shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0777);
    // 打开SHM_KEY所在的共享内存
    第二个进程: shmid = shmget(SHM_KEY, SHM_SIZE, 0777);


(4)    分离共享内存
    int shmdt(const void *shmaddr);
    1.shmdt 函数仅仅是使进程和共享内存脱离关系,并未删除共享内存
    2.shmdt 函数的作用是将共享内存的引用计数减 1 
    3.只有共享内存的引用计数为 0 时,调用 shmctl 函数的 IPC_RMID 命令才会真
        正地删除共享内存。


(5)控制共享内存:
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
        IPC_STAT: 获取 shmid 对应的共享内存的信息(struct shmid_ds信息)
        IPC_SET:  只能修改 shm_perm 中的 uid 、 gid 及 mode 。    
        IPC_RMID: 如果共享内存的引用计数 shm_nattch 等于 0 ,则可以立即删除共享内存。但是如果仍然存在进程
                  attach 该共享内存,则并不执行真正的删除操作,而仅仅是设置 SHM_DEST 标记。待所有进程都执行过
                  分离操作之后,再执行真正的删除操作。
                  //,共享内存处于 SHM_DEST 状态的情况下,依然允许新的进程调用 shmat 函数来 attach该共享内存。
        ...........


(6)key的获取方法(密钥)
    方法1: (1-5000)内的任意整数
    方法2: key_t ftok(const char *pathname, int proj_id); // 任意合法路径,任意整数

(7)程序范例(使用共享内存搭配信号量, 构造两个进程的聊天程序)

1.semaphore_lib.c是对System v型号量的常用封装

2.shm1.c shm2.c 是两个聊天进程

3.semaphore_lib.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/sem.h>

/* 控制信号量 */ 
union semun {
	int              val;    /* Value for SETVAL */
	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
	unsigned short  *array;  /* Array for GETALL, SETALL */
	struct seminfo  *__buf;  /* Buffer for IPC_INFO	 (Linux-specific) */
};


// 获取信号量集中指定信号量的值
int sema_getval(int semid, int index)
{	union semun ignore_arg;
	return semctl(semid, index, GETVAL, ignore_arg);
}
// 设置信号量集中指定信号量为value
int sema_setval(int semid, int index, int val)
{	union semun arg;
	arg.val = val;
	return semctl(semid, index, SETVAL, arg);
}
// 立即删除信号量
int sema_rmid(int semid)
{	union semun ignore_arg;
	int ignore_index;
	return semctl(semid, ignore_index, IPC_RMID, ignore_arg);
}

/* 操作信号量 */ 
// 申请资源(操作一个信号量)
int semaphore_wait (int semid, int index)
{
	struct sembuf operations[1];
	operations[0].sem_num = index;			// 操作一组信号量中的哪一个信号量
	operations[0].sem_op = -1;				// 信号量0的值 + 1
	operations[0].sem_flg |= SEM_UNDO;		// 进程退出后恢复信号量(成对出现)
	return semop(semid, operations, 1);		// 信号量集合操作
}

// 释放资源(操作一个信号量)
int semaphore_post(int semid, int index)
{
	struct sembuf operations[1];
	operations[0].sem_num = index;			// 操作一组信号量中的哪一个信号量
	operations[0].sem_op = 1;				// 信号量0的值 - 1
	operations[0].sem_flg |= SEM_UNDO;		// 进程退出后恢复信号量(成对出现)
	return semop(semid, operations, 1);
}


void test(void)
{
	printf("test\n");
}

5.shm1.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#include "semaphore_lib.h"

#define SHM_KEY 0x123
#define SEM_KEY 0x234
#define SHM_SIZE 1024
#define SEM0 0
#define SEM1 1
#define CUR printf("cur: %s %d\n",  __FILE__, __LINE__)
#define ERR printf("Err in: %s %d\n", __FILE__, __LINE__)

int main(int argc, char **argv)	// 简化代码,不判断返回值
{	test();
	int shmid, semid, ret;
	unsigned char *addr;
	char buf[50];
	
	// 创建共享内存
	shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0777);	
	if (shmid < 0)
	{	shmid = shmget(SHM_KEY, SHM_SIZE, 0777);
		if (shmid < 0)
			ERR;
	}
	// 映射共享内存到主进程虚拟地址空间
	addr = shmat(shmid, NULL, 0);
	
	// 创建信号量
	semid = semget(SEM_KEY, 2,  IPC_CREAT |  IPC_EXCL | 0777);  // 创建信号量:信号量级中有2个信号量
	if (semid < 0)	{									
		semid = semget(SEM_KEY, 0,  0777);	// 打开信号量					
			if (semid < 0)
				ERR;
	}

	// 初始化信号量的资源
	sema_setval(semid, SEM0, 0);	// 消费者初始状态没有资源
	sema_setval(semid, SEM1, 0);	// 消费者初始状态没有资源
	ret = fork();
	if (ret < 0)
		//ERR;
		CUR;
	else if (!ret)	//child
	{	
		// 映射共享内存到子进程虚拟地址空间
		addr = shmat(shmid, NULL, 0);	// addr和主进程addr的可不一样
		bzero(buf, 50);
		while(1)	// 存储数据(生产者模型)
		{	strcpy(addr, fgets(buf, 50, stdin));	// 未在终端键入时,陷入阻塞
			semaphore_post(semid, SEM0);			// 生产资源
		}
		exit(0);	//这里往下的代码不会被子进程执行, 所以主进程没有判断返回值
	}
	// parent 
	while(1)	//读出数据(消费者模型)
	{	semaphore_wait(semid, SEM1);	// 消费资源(没有资源时,陷入阻塞)
		printf("shm1 recv: %s\n", addr);
	}
	
	return 0;
}





6.shm2.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#include "semaphore_lib.h"

#define SHM_KEY 0x123
#define SEM_KEY 0x234
#define SHM_SIZE 1024
#define SEM0 0
#define SEM1 1
#define CUR printf("cur: %s %d\n",  __FILE__, __LINE__)
#define ERR printf("Err in: %s %d\n", __FILE__, __LINE__)

int main(int argc, char **argv)	// 简化代码,不判断返回值
{	test();
	int shmid, semid, ret;
	unsigned char *addr;
	char buf[50];
	
	// 创建共享内存
	shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0777);	
	if (shmid < 0)
	{	shmid = shmget(SHM_KEY, SHM_SIZE, 0777);
		if (shmid < 0)
			ERR;
	}
	// 映射共享内存到主进程虚拟地址空间
	addr = shmat(shmid, NULL, 0);
	
	// 创建信号量
	semid = semget(SEM_KEY, 2,  IPC_CREAT |  IPC_EXCL | 0777);  // 创建信号量:信号量级中有2个信号量
	if (semid < 0)	{									
		semid = semget(SEM_KEY, 0,  0777);	// 打开信号量					
			if (semid < 0)
				ERR;
	}

	// 初始化信号量的资源
	sema_setval(semid, SEM0, 0);	// 消费者初始状态没有资源
	sema_setval(semid, SEM1, 0);	// 消费者初始状态没有资源
	ret = fork();
	if (ret < 0)
		//ERR;
		CUR;
	else if (!ret)	//child
	{	
		// 映射共享内存到子进程虚拟地址空间
		addr = shmat(shmid, NULL, 0);	// addr和主进程addr的可不一样
		bzero(buf, 50);
		while(1)	// 存储数据(生产者模型)
		{	strcpy(addr, fgets(buf, 50, stdin));	// 未在终端键入时,陷入阻塞
			semaphore_post(semid, SEM0);			// 生产资源
		}
		exit(0);	//这里往下的代码不会被子进程执行, 所以主进程没有判断返回值
	}
	// parent 
	while(1)	//读出数据(消费者模型)
	{	semaphore_wait(semid, SEM1);	// 消费资源(没有资源时,陷入阻塞)
		printf("shm1 recv: %s\n", addr);
	}
	
	return 0;
}





7.编译执行结果

终端1 执行shm1, 终端2 执行shm2,

book@gui_hua_shu$ ./shm1
nihao woshi shm1
shm1 recv: nihao woshi shm2

book@gui_hua_shu$ ./shm2
shm1 recv: nihao woshi shm1
nihao woshi shm2
 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值