5 基于共享内存的进程间通信-实验1:使用内存映射文件实现进程间通信

5 基于共享内存的进程间通信-实验1:使用内存映射文件实现进程间通信

一.实验目的

·使用内存映射文件实现进程通信的方法
·使用内存映射文件方法加速IO操作的原理

二.实验背景

·共享内存的基础是内存映射
·用户进程建立内存映射的操作函数时mmap,其原型:
#include <sys/mman.h>
在这里插入图片描述

·调用msync来执行同步,其函数原形:
在这里插入图片描述
** ·stat和fstat 两个函数获取文件的信息:**
在这里插入图片描述
·POSIX提供两种在无关进程间共享内存区的方法:内存文件映射、共享内存对象

三.关键代码及分析

1.单个进程的内存映射文件

int main(int argc, char *argv[])
{
    char *addr;
    int fd;
    struct stat sb;
    off_t offset, pa_offset;
    size_t length;
    ssize_t s;

    if (argc < 3 || argc > 4) {
        fprintf(stderr, "%s file offset [length]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    fd = open(argv[1], O_RDONLY); //打开指定的文件
    if (fd == -1)
        handle_error("open");

    if (fstat(fd, &sb) == -1)           /* To obtain file size */
        handle_error("fstat");

    offset = atoi(argv[2]);  //获取文件信息
    pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1); //转换成页面的整数倍
        /* offset for mmap() must be page aligned */

    if (offset >= sb.st_size) {
        fprintf(stderr, "offset is past end of file\n");
        exit(EXIT_FAILURE);
    }

    if (argc == 4) {   //命令行第4个参数表示文件映射的长度
        length = atoi(argv[3]);
        if (offset + length > sb.st_size)
            length = sb.st_size - offset;  //文件长度不能超过文件的末尾
} else {    //没有第4个参数则映射到文件末尾
        length = sb.st_size - offset;
    }
    printf("the inputted offset is %d, and the length is %d\n", atoi(argv[2]),atoi(argv[3]));
    printf("the page size is %ld, the pa_offset is %ld, and the actual length is %ld\n\n", sysconf(_SC_PAGE_SIZE),  pa_offset, length + offset - pa_offset);

    addr = mmap(NULL, length + offset - pa_offset, PROT_READ,
                MAP_PRIVATE, fd, pa_offset);
    if (addr == MAP_FAILED)
        handle_error("mmap");

    s = write(STDOUT_FILENO, addr + offset - pa_offset, length);
    if (s != length) {
        if (s == -1)
            handle_error("write");

        fprintf(stderr, "partial write");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

2.多个进程间的内存映射文件的同步

#define   SEM_NAME         "sem"
#define   FILE_MODE   (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define   COUNTNO 20
//共享内存结构
struct data
{
    sem_t sem;       //信号量
    int count;         //计数器
}data;

int main(int argc,char* argv[])
{
    int     fd,i,nloop;
    struct  data *pdata;
    if(argc == 2)
    {
	nloop = atoi(argv[1]);
    }
    else 
	nloop = COUNTNO;
    fd = open("shm_test01",O_RDWR|O_CREAT,FILE_MODE); //	打开文件
    write(fd,&data,sizeof(struct data));  // 向文件写入数据,全0
pdata=map(NULL,sizeof(struct data),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(pdata == MAP_FAILED)
    {
        error("mmap() error");
        exit(1);
    }
    close(fd);   //关闭文件描述符,不影响内存映射文件的使用
    sem_init(&pdata->sem,1,1);
    setbuf(stdout,NULL);
    if(fork() == 0)
    {
        for(i = 0;i<nloop;++i)
        {
            sem_wait(&pdata->sem);
	    pdata->count++;	
            //printf("child: %d\n",pdata->count);
            sem_post(&pdata->sem);
        }
        exit(0);
    }
    for(i = 0;i<nloop;++i)
    {
        sem_wait(&pdata->sem);
	pdata->count++;	
        //printf("child: %d\n",pdata->count);
        sem_post(&pdata->sem);
    }    
    printf("the final result of count is %d\n",pdata->count);
    wait(NULL);
    printf("the final result of count is %d\n",pdata->count);
    exit(0);
}

程序父、子进程都对一个名为count的全局变量进行加1操作,次数为均为nloop次。如果不使用内存共享机制,仅仅使用信号量加锁,由fork产生的子进程则拥有一个单独的count副本,最终得到的count值将与nloop相等。程序首先创建一个临时文件,并以共享数据的结构来初始化文件内容为0;然后把该文件映射到一个共享内存区域,映射标志为MAP_SHARED,表示可以在多个进程之间共享该内存映射文件。最后,对内存映射区域中的信号量进行初始化值为1,并把信号量的进程间共享标志设为1.
在程序中定义了一个数据结构data,该结构包括共享变量以及用于保护该共享变量的信号量。这种把要保护的数据和保护该数据的同步工具集成在一个数据结构中的方法是Linux内核经常采用的方法,可以较好地避免在访问数据时忘记加锁的问题。在使用无名信号量的场景中,通过内存映射文件把共享数据和保护该数据的无名信号量一起映射到共享内存区域,确保了无名信号量在使用时必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程(线程)的共享变量。

3.匿名内存映射

#define COUNTNO 5
sem_t sem;
sem_t *psem = NULL;
int main(int argc, char *argv[])
{
    int pid,i;
    int val;

    psem = (sem_t *)mmap(NULL, sizeof(sem_t), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);        
    if (psem == NULL)
    {
        printf("Share memory failed!\n");
        return -1;
    }   
    if (sem_init(psem, 1, 1) == -1)
    {
        printf("Init unamed sem failed!\n");
        return -1;
    }
    sem_getvalue(psem,&val);		
    printf("this is the main function the psem value is %d\n",val); 

    pid = fork();
    if(pid==0){  
        for (i=1;i<COUNTNO;i++) 
        {    
        	sem_wait(psem);  
	      	sem_getvalue(psem,&val);		
        	printf("this is child,the sem value is %d\n",val);	        	
	        sem_post(psem);  		
	        usleep(1000); 
        }  
    } 
    else { 
        for (i=1;i<COUNTNO;i++) 
	{	
	    sem_wait(psem);  
	    sem_getvalue(psem,&val);		
       	    printf("this is fahter,the sem value is %d\n",val);  	    
	    sem_post(psem);  
   	    usleep(3000);  
	}
    }	
    sem_destroy(psem);
    return 0;
}

4.多个进程间的匿名内存映射文件的同步

#define   SEM_NAME         "sem"
#define   FILE_MODE   (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define   COUNTNO 20
//共享内存结构
struct data
{
    sem_t sem;       //信号量
    int count;         //计数器
}data;

int main(int argc,char* argv[])
{
    int     fd,i,nloop;
    struct  data *pdata = NULL;
    if(argc == 2)
    {
        nloop = atoi(argv[1]);
    }
    else
        nloop = COUNTNO;
   // fd = open("shm_test01",O_RDWR|O_CREAT,FILE_MODE);
    //write(fd,&data,sizeof(struct data));
    pdata = (struct data *)mmap(NULL,sizeof(struct data),PROT_READ| PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
    if(pdata == NULL)
    {
        error("mmap() error");
        exit(1);
    }
    //close(fd);
    sem_init(&pdata->sem,1,1);
    setbuf(stdout,NULL);
    if(fork() == 0)
    {
        for(i = 0;i<nloop;++i)
        {
            sem_wait(&pdata->sem);
            pdata->count++;
            //printf("child: %d\n",pdata->count);
            sem_post(&pdata->sem);
        }
        exit(0);
    }
    for(i = 0;i<nloop;++i)
    {
        sem_wait(&pdata->sem);
        pdata->count++;
        //printf("child: %d\n",pdata->count);
        sem_post(&pdata->sem);
    }
    printf("the final result of count is %d\n",pdata->count);
    wait(NULL);
    printf("the final result of count is %d\n",pdata->count);
    exit(0);
}

四.实验结果与分析

1.单个进程的内存映射文件
· gcc -o shm01 shm01.c //编译连接生成可执行程序

在这里插入图片描述
·//执行生产的可执行程序
· ./shm01 shm_usem02.c 0 100 // 表示把文件shm_user02.c 从0开始长度为100B 的内容映射到共享内存中
· ./shm01 shm_usem02.c 100 200 // 表示把文件shm_user02.c 从100开始长度为200B 的内容映射到共享内存中

在这里插入图片描述

在调用mmap 函数时传入的长度参数和偏移量并不是直接从命令行输入的数值,而是根据系统页面大小和文件大小重新计算过的数据,由printf的打印输出结果可以看出,在调用mmap时对长度和偏移量参数的重新计算是必须的。

2.多个进程间的内存映射文件的同步
· gcc -o shm_usem02 shm_usem02.c -lpthread //编译连接生成可执行程序
在这里插入图片描述

· //执行生产的可执行程序
在这里插入图片描述

sem_init(&pdata->sem,1,1);表示初始化一个无名信号量,其初值为1,且任何可以访问到这个信号量的进程都可以使用该信号量。由于该无名信号量和count变量都是在共享内存区域,且映射标志位多个进程之间共享该内存映射文件,所以父子进程在访问共享内存文件的时候不会同时访问,会先后的访问,实现了父子进程的同步,故最后输出的结果都是所设定的值的整数倍。

3.匿名内存映射
· gcc -o shm_usem03 shm_usem03.c -lpthread //编译连接生成可执行程序
在这里插入图片描述
· ./shm_usem03 //执行生产的可执行程序
在这里插入图片描述

Value的值为0表示有进程阻塞在该信号量,由于最开始信号量为1,当父子进程当中的一个进程进入临界区后,信号量的只就是变为0,当后面再有进程想进入临界区就会阻塞在该信号量上,又有与信号初值1,进程一进入临界区信号量就变为0,有进程阻塞在该信号量的时候sem_getvalue()函数会返回0,所以结果中输出的value的值都为0.

4.多个进程间的匿名内存映射文件的同步
在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Hello Spring

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值