【Linux】IPC主题 二-------- 共享内存(mmap)

一、mmap 函数

     1、函数原型:


   #include <sys/mman.h>

       void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
     2、返回值:

                       若执行成功: 返回创建的映射区的首地址;

                       若执行失败: 返回 MAP_FAILED  宏 
      3、参数:

                      addr  :    建立映射区的首地址,由 Linux 内核指定。使用时直接传递 NULL。

                      length:   欲创建的映射区大小。

                      prot    :   映射权限   PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE

                      flags   :   标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)

                                       MAP_SHARED:会将映射区所做的操作反映到物理设备上(磁盘上)

                                       MAP_PRIVATE:映射区所做的修改不会反映到物理设备上。


                      fd        :   用来建立映射区的文件描述符

                     offset    :  映射文件的偏移(4k的整数倍)

二、munmap 函数

       同 malloc 函数申请内存空间类似,mmap 建立的映射区在使用结束后也应调用类似的 free 的函数来解释。

       

       int munmap(void *addr, size_t length);
                    返回值:

                                 若成功,返回 0 ; 若失败,返回 -1。

#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>

void sys_error(char *str)
{
    perror(str);
    exit(1);
}


int main(void)
{
    char *mem;
    int len = 0;
    int fd = open("hello123",O_RDONLY|O_CREAT|O_TRUNC,0644);
    if( fd == -1 )
        sys_error("open error");

    ftruncate(fd,20);
/*
    len = lseek(fd,3,SEEK_SET);    // 获取文件大小,根据文件大小建立映射区
    write(fd,"e",1);               // 实质性完成文件拓展
    printf("The length of file = %d\n",len);
*/

    mem = mmap(NULL,20,PROT_READ,MAP_SHARED,fd,0);

    if( mem == MAP_FAILED )        // 出错判断
        sys_error("mmap error");
    mem++;
    close(fd);

    strcpy(mem,"xxx");

    printf("%s\n",mem);


    if( munmap(mem,4)<0 )
        sys_error("munmap error");

    return 0;
}


三、mmap 的使用注意事项

       1、创建映射区时,隐含着对映射文件的一次读操作。

       2、当 MAP_SHARED时,要求映射区的权限应 <= 文件的打开权限(出于对映射区的保护)。而 MAP_PRIVATE则无所谓,因为 mmap 中的权限是对内存的限制。

       3、映射区的释放与文件关闭无关。只要映射建立成功,则文件可以立即关闭。

       4、特别注意:当映射文件大小为 0 时,不能创建映射区。所以,用于映射的文件必须有实际大小!mmap 使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。

       5、munmap 传入的地址一定是 mmap 的返回地址。杜绝指针 ++ ,-- 操作。

       6、文件偏移量必须为 4k 的整数倍。

       7、mmap 创建映射区出错概率非常高,一定要检查返回值,确保映射区成功建立,并进行后续操作。

四、mmap 父子进程间通信

       父子等有血缘关系的进程之间也可以通过 mmap 建立的映射区来完成数据通信。但是相应的要在创建映射区的时候指定对应的标志位参数 flags:

                 MAP_PROVATE(私有映射):父子进程各自独占映射区

                 MAP_SHARED(共享进程)  :父子进程共享映射区

        结论:

                 父子进程共享:

                                         (1)、打开的文件

                                         (2)、mmap 建立的映射区(但必须使用 MAP_SHARED)

#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

int var = 100;

int main(void)
{
    int *p;
    pid_t pid;

    int fd;

    fd = open("temp",O_RDWR|O_CREAT|O_TRUNC,0644);

    if( fd == -1 )
    {
        perror("open error");
        exit(1);
    }
  
    // 这里程序在运行期间 temp 文件还存在,运行完成后,推出进程后,temp文件被删除掉
    unlink("temp");  // 删除临时文件目录项,使之具备被释放的条件  

    ftruncate(fd,4);
    p =(int*)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

    if( p == MAP_FAILED )
    {
        perror("mmap error");
        exit(1);
    }

    close(fd);       // 映射区建立完毕,即可关闭文件

    pid = fork();    // 创建子进程
    if( pid == 0 )
    {
        var = 1000;
        *p = 2000;
        printf("child,*p = %d,var = %d\n",*p,var);
    }
    else
    {
        sleep(1); 
    
        printf("parent,*p = %d,var = %d\n",*p,var);
        wait(NULL); 

        int ret = munmap(p,4);
        if( ret == -1 )
        {
            perror("munmao error");
            exit(1);
        }
    }

    return 0;
}



五、匿名映射

        使用 MAP_ANOYYMOUS(MAP_ANON),如:

       

         int *p = mmap(NULL,4,PROT_READ|PROT_WRITE,MAXP_SHARED|MAP_ANONYMOUS,-1,0);

#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

int var = 100;

int main(void)
{
    int *p;
    pid_t pid;
 
    p =(int*)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);

    if( p == MAP_FAILED )
    {
        perror("mmap error");
        exit(1);
    }

    pid = fork();    // 创建子进程
    if( pid == 0 )
    {
        var = 1000;
        *p = 2000;
        printf("child,*p = %d,var = %d\n",*p,var);
    }
    else
    {
        sleep(1); 
    
        printf("parent,*p = %d,var = %d\n",*p,var);
        wait(NULL); 

        int ret = munmap(p,4);
        if( ret == -1 )
        {
            perror("munmao error");
            exit(1);
        }
    }

    return 0;
}


       注意:

                  MAP_ANONYMOUS 和 MAP_ANON 这两个宏是 Linux操作系统特有的宏,在类 UNIX 系统中无该宏定义,可使用如下两步来完成匿名映射区的建立。

                  (1)、fd = open("/dev/zero",O_RDWR);

                  (2)、p = mmap(NULL,size,PROT_READ|PROT_WRTIE,MAP_SHARED,fd,0);


六、mmap 无血缘关系进程间通信

       实质上 mmap 是内核借助帮助文件帮我们创建了一个映射区,多个进程之间利用该映射区完成数据的传递。由于内核控件多进共享,因此,无血缘关系的进程间也可以使用 mmap 来完成通信。只要设置相应的标志位参数 flags 即可。若要实现共享,当然使用 MAP_SHARED 了。


非血缘关系写进程:

#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>

struct STU
{
    int id;
    char name[20];
    char sex;
};


void sys_err(char *error)
{
    perror(error);
    exit(1);
}

int main(int argc,char *argv[])
{
    int fd;
    struct STU student = { 10,"xiaoming",'m'};
    char *mm;

    if( argc < 2 )
    {
        printf("./a.out file_shared\n"); 
        exit(-1);
    }
    
    fd = open(argv[1],O_RDWR|O_CREAT,0644);
    if( fd == -1 )
        sys_err("open error");
    ftruncate(fd,sizeof(student));

    mm = mmap(NULL,sizeof(student),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

    if( mm == MAP_FAILED )
        sys_err("mmap error");

    close(fd);

    while(1)
    {
        memcpy(mm,&student,sizeof(student));
        student.id++;
        sleep(1);
    }

    int ret = munmap(mm,sizeof(student));
    if( ret == -1 )
        sys_err("munmap error");

    return 0;
}


非血缘关系读进程:


#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>

struct STU
{
    int id;
    char name[20];
    char sex;
};


void sys_err(char *error)
{
    perror(error);
    exit(1);
}

int main(int argc,char *argv[])
{
    int fd;
    struct STU student;
    struct STU *mm;

    if( argc < 2 )
    {
        printf("./a.out file_shared\n"); 
        exit(-1);
    }

    fd = open(argv[1],O_RDONLY);
    if( fd == -1 )
        sys_err("open error");

    mm = mmap(NULL,sizeof(student),PROT_READ,MAP_SHARED,fd,0);

    if( mm == MAP_FAILED )
        sys_err("mmap error");

    close(fd);

    while(1)
    {
        printf("id = %d\tname=%s\t%c\n",mm->id,mm->name,mm->sex);
        sleep(2);
    }

    int ret = munmap(mm,sizeof(student));
    if( ret == -1 )
        sys_err("munmap error");

    return 0;
}


七、文件通信

       两个非血缘关系的进程可以通过文件的方式来进行通信。

              strace + 文件名          能够追踪到当前程序执行过程中用到的系统调用。

          注意:文件的一些操作都是用 mmap 来实现的。


/*
    先执行,将数据写入文件 test.txt 
*/

#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

#define N 5

void sys_err(char *error)
{
    perror(error);
    exit(1);
}

int main(void)
{
    char buf[1024];

    char *str= "-------------successs--------------\n";
    int ret;

    int fd = open("test.txt",O_RDWR|O_TRUNC|O_CREAT,0644);

    if( fd == -1 )
        sys_err("open error");

    // 直接打开文件写数据

    write(fd,str,strlen(str));
    printf("test1 write into test.txt finish\n");

    sleep(N);

    lseek(fd,0,SEEK_SET);

    ret = read(fd,buf,sizeof(buf));

    if( ret <=0 )
        sys_err("read error");

    ret = write(STDOUT_FILENO,buf,ret);

    if( ret == -1 )
        sys_err("write error");

    close(fd);

    return 0;
}

/*
    先执行,将数据写入文件 test.txt 
*/

#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

#define N 5

void sys_err(char *error)
{
    perror(error);
    exit(1);
}

int main(void)
{
    char buf[1024];

    char *str= "-------------test2 write success--------------\n";
    int ret;

    sleep(2);   // 睡两秒,保证 test1 将数据写入 test.txt中
    int fd = open("test.txt",O_RDWR);

    if( fd == -1 )
        sys_err("open error");

    // 尝试读取 test.txt文件中 test1 写入数据

    ret = read(fd,buf,sizeof(buf));
    if( ret <= 0 )
        sys_err("read error");

    // 将读到的数据打入屏幕

    write(STDOUT_FILENO,buf,ret);

    // 写入数据到 test.txt 中,未修改读写位置

    write(fd,str,strlen(str));

    printf("test2 read/write finish\n");
    
    close(fd);

    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值