Linux系统编程---5(共享存储映射,存储映射I/O,mmap函数,父子进程间通信,匿名映射)

共享存储映射

文件进程间通信

使用文件也可以完成 IPC,理论依据是,fork 后,父子进程共享文件描述符。也就共享打开的文件。
编程:父子进程共享打开的文件。借助文件进行进程间通信。
测试代码

/*
 *
 * 父子进程共享打开的文件描述符----使用文件完成进程间通信
 *
 */

#include
#include
#include
#include
#include
#include

int main(void)
{
    int fd1,fd2;
    pid_t pid;
    char buf[1024];                                                                
    char *str="----------test for shared fd in parent child process -----\n";

    pid=fork();
    if(pid<0){
        perror("fork error");
        exit(1);
    }else if(pid==0){

        fd1=open("test.txt",O_RDWR);
        if(fd1<0){
            perror("open error");
            exit(1);
        }   
        write(fd1,str,strlen(str));
        printf("child wrote over...\n");

    }else{

        fd2=open("test.txt",O_RDWR);
        if(fd2<0){
            perror("open error");
         exit(1);
        }
        sleep(1);   //保证子进程写入数据
        
        int len =read(fd2,buf,sizeof(buf));
        write(STDOUT_FILENO,buf,len);

        wait(NULL);//回收防止僵尸进程
    }

    return 0;
}

在这里插入图片描述

思考,无血缘关系的进程可以打开同一个文件进行通信吗?为什么?
可以,,无血缘关系的进程也可以打开同一个文件进行通信,方法一样,因为这些进程打开的是同一个进程。其实在打开文件时(调用open时),操作系统内核就调用了mmap。因为一个文件只有一个文件结构体(FILE),打开时位于内核,被打开这个文件的多个进程共享。

存储映射 I/O

存储映射 I/O(Memory-mappedI/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取 数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可 在不适用 read 和 write 函数的情况下,使用地址(指针)完成 I/O 操作。
使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过 mmap 函数来实
现。
Linux系统编程---5(共享存储映射,存储映射I/O,mmap函数,父子进程间通信,匿名映射)_第1张图片

mmap 函数

void* mmap(void* adrr,size_t length,int prot,int flags,int fd,off_toffset); 返回:成功:返回创建的映射区首地址;失败:MAP_FAILED 宏
参数:

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

  2. length: 欲创建映射区的大小

  3. prot: 映射区权限 PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE

  4. flags: 标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区) MAP_SHARED: 会将映射区所做的操作反映到物理设备(磁盘)上。 MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。

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

  6. offset: 映射文件的偏移(4k 的整数倍)
    Linux系统编程---5(共享存储映射,存储映射I/O,mmap函数,父子进程间通信,匿名映射)_第2张图片

     #include
     #include
     #include
     #include
     #include
     #include
     int main(void)
     {
         int len,ret;
         char *p=NULL;
         int fd = open("mytest.txt",O_CREAT|O_RDWR,0644);
         if(fd<0){
             perror("open error");
             exit(1);
         }   
         //确定文件大小
          len = ftruncate(fd,4);
         if(len == -1){
             perror("ftruncate error:");
             exit(1);
         }   
     
         p = mmap(NULL, 4 , PROT_READ|PROT_WRITE , MAP_SHARED, fd, 0); 
         if(p == MAP_FAILED){
             perror("mmap error:");
             exit(1);
         }   
     
         strcpy(p,"abc");//写数据
     
         ret = munmap(p,4);//释放映射区                                                 
         if(ret==-1){
             perror("munmap error:");
             exit(1);
         }   
         close(fd);
     
         return 0;
     }
    

在这里插入图片描述

munmap 函数

同 malloc 函数申请内存空间类似的,mmap 建立的映射区在使用结束后也应调用类似 free 的函数来释放。 int munmap(void *addr,size_t length); 成功:0; 失败:-1

mmap 注意事项

思考:

  1. 可以 open 的时候 O_CREAT 一个新文件来创建映射区吗?
    答:可以,但新CREATE出来的文件不行,必须要有实际的大小
  2. 如果 open 时 O_RDONLY,mmap 时 PROT 参数指定 PROT_READ|PROT_WRITE 会怎样?
    答:不行,创建映射区的权限小于等于打开文件的权限,映射区创建的过程中存在一次读文件操作权限不足。
  3. 文件描述符先关闭,对 mmap 映射有没有影响?
    答: 没有影响,文件描述符是操作文件的句柄,有映射区了,就是地址的方式操作,所以文件描述符就没意义了。
  4. 如果文件偏移量为 1000 会怎样?
    答:不行,偏移量必须是一页的大小(4k)
  5. 对 mem 越界操作会怎样?
    答:不行,释放映射区可能会失败,只有创建映射区的地址和释放时的地址要是同一个地址
  6. 如果 mem++,munmap 可否成功?
    答:不能,释放映射区的时候要传首地址和映射区大小给munmap,但++后首地址就不是创建时的首地址,只有创建映射区的地址和释放时的地址要是同一个地址
  7. mmap 什么情况下会调用失败? 8. 如果不检测 mmap 的返回值,会怎样?
    答:返回值必须检查,空间不能为0,文件大小和映射区要匹配等等情况下会失败

总结

  1. 创建映射区的过程中,隐含着一次对映射文件的读操作。
  2. 当 MAP_SHARED 时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护)。而 MAP_PRIVATE 则无所谓,因为 mmap 中的权限是对内存的限制。
  3. 映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。
  4. 特别注意,当映射文件大小为 0 时,不能创建映射区。所以:用于映射的文件必须要有实际大小!! mmap 使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。
  5. munmap 传入的地址一定是 mmap 的返回地址。坚决杜绝指针++操作。
  6. 如果文件偏移量必须为 4K 的整数倍 7. mmap 创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

mmap 父子进程通信

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

  1. MAP_PRIVATE: (私有映射) 父子进程各自独占映射区;
  2. MAP_SHARED: (共享映射) 父子进程共享映射区;
    编程:父进程创建映射区,然后 fork 子进程,子进程修改映射区内容,而后,父进程读取映射区内容,查验是 否共享

Linux系统编程---5(共享存储映射,存储映射I/O,mmap函数,父子进程间通信,匿名映射)_第3张图片

    #include                                                                  
    #include
    #include
    #include
    #include
    #include
    
    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<0){
            perror("open error:");
            exit(1);
        }
    
        unlink("temp");          //删除临时文件目录项,使之具备被释放条件,所有使用该文>件的进程结束后该文件才释放
        ftruncate(fd,4);        //创建文件大小
        
        p=(int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//父子进程共享映射区
    
       // p=(int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);//对映射区各自独>占    if(p==MAP_FAILED){              //不是p==NULL时出错
            perror("mmap,error");       
            exit(1);
        }
        close(fd);              //映射区建立完毕,即可关闭文件
    
        //完成数据传递
        pid=fork();
        if(pid==0){
            *p=2000;
          var=1000;
            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("munmap error");
                exit(1);
            }
        }
        return 0;
    }   

Linux系统编程---5(共享存储映射,存储映射I/O,mmap函数,父子进程间通信,匿名映射)_第4张图片

结论:父子进程共享:

  1. 打开的文件
  2. mmap 建立的映射区(但必须要使用 MAP_SHARED)

匿名映射

通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创 建映射区一定要依赖一个文件才能实现。通常为了建立映射区要 open 一个 temp 文件,创建好了再 unlink、close 掉,比较麻烦。 可以直接使用匿名映射来代替。其实 Linux 系统给我们提供了创建匿名映射区的方法,无需依赖一 个文件即可创建映射区。同样需要借助标志位参数 flags 来指定。
使用 MAP_ANONYMOUS(或 MAP_ANON),

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

"4"随意举例,该位置表大小,可依实际需要填写。

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

  1. fd=open("/dev/zero",O_RDWR);
  2. p=mmap(NULL,size,PROT_READ|PROT_WRITE,MMAP_SHARED,fd,0);

示例代码:

#include
#include
#include
#include
#include
#include

int var=100;

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


    
    //不使用文件参数传-1                                                           
    p=(int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);//对映射区>各自独占
    if(p==MAP_FAILED){              //不是p==NULL时出错
        perror("mmap,error");    
        exit(1);
    }   

    //完成数据传递
    pid=fork();
    if(pid==0){
        *p=2000;
        var=1000;
        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("munmap error");
          exit(1);
        }
    }
    return 0;
}

在这里插入图片描述
Linux下这两个文件无大小
Linux系统编程---5(共享存储映射,存储映射I/O,mmap函数,父子进程间通信,匿名映射)_第5张图片
第一个文件好比聚宝盆,可以随意映射
第二个文件,一般错误洗脑洗重定向到这个文件中

mmap 无血缘关系进程间通信

实质上 mmap 是内核借助文件帮我们创建了一个映射区,多个进程之间利用该映射区完成数据传递。由于内核 空间多进程共享,因此无血缘关系的进程间也可以使用 mmap 来完成通信。只要设置相应的标志位参数 flags 即可。 若想实现共享,当然应该使用 MAP_SHARED 了。
Linux系统编程---5(共享存储映射,存储映射I/O,mmap函数,父子进程间通信,匿名映射)_第6张图片
读数据

/*
 * 非血缘关系进程间通信
 */
#include
#include
#include
#include
#include
#include
#include

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

//出错处理函数
void sys_err(char *str)
{
    perror(str);
    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);
    }
    munmap(mm,sizeof(student));

    return 0;
}        

写数据

/*                                                                                 
 * 非血缘关系之间的通信
 */
#include
#include
#include
#include
#include
#include
#include
#include

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

void sys_err(char *str)
{
    perror(str);
    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);
    ftruncate(fd,sizeof(student));

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

    close(fd);

    while(1){
        memcpy(mm,&student,sizeof(student));
        student.id++; //循环向内存复制
        sleep(1);
    }
    
    munmap(mm,sizeof(student));
    return 0;
}

在这里插入图片描述
Linux系统编程---5(共享存储映射,存储映射I/O,mmap函数,父子进程间通信,匿名映射)_第7张图片
Linux系统编程---5(共享存储映射,存储映射I/O,mmap函数,父子进程间通信,匿名映射)_第8张图片

你可能感兴趣的:(Linux,系统编程)