精华内容
下载资源
问答
  • mmap函数参数讲解

    千次阅读 2018-05-27 12:14:47
    1.mmap-创建内存映射 作用:将磁盘文件的数据映射到内存,用户通过内存就能修改磁盘文件 函数原型:void *mmap{ void *addr; //映射区首地址,传NULL size_t length; //映射区的大小 //会自动调为4k的整数倍 ...

    1.mmap-创建内存映射

        作用:将磁盘文件的数据映射到内存,用户通过内存就能修改磁盘文件

        函数原型:

    void *mmap{
    	void *addr;	//映射区首地址,传NULL
    	size_t length;	//映射区的大小
    		//会自动调为4k的整数倍
    		//不能为0
    		//一般文件多大,length就指定多大
    	int prot;	//映射区权限
    		//PROT_READ 映射区比必须要有读权限
    		//PROT_WRITE
    		//PROT_READ | PROT_WRITE
    	int flags;	//标志位参数
    		//MAP_SHARED 修改了内存数据会同步到磁盘
    		//MAP_PRIVATE 修改了内存数据不会同步到磁盘
    	int fd;	//要映射的文件对应的fd
    	off_t offset;  //映射文件的偏移量,从文件的哪里开始操作
    		//映射的时候文件指针的偏移量
    		//必须是4k的整数倍
    		//一般设置为0
    
    }

    返回值:

    映射区的首地址-调用成功

    调用失败:MAP_FALED

    2.munmap-释放内存映射区

    函数原型:int munmap(void *addr,size_t length);

        addr-mmap的返回值

        length-mmap的第二个参数

    3.注意事项

    问:如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功?
    答:不能
    问:如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
    答mmap调用失败
    open文件指定权限应该大于等于mmap第三个参数prot指定的权限
    问:如果文件偏移量为1000会怎样?
    答:必须是4096的整数倍
    问:mmap什么情况下会调用失败?
    第二个参数length = 0
    第三个参数必须指定PROT_READ
    fd对应的打开权限必须大于等于port权限
    偏移量:必须是4096的整数倍
    问:可以open的时候O_CREAT一个新文件来创建映射区吗?
    答:可以,需要做文件拓展
    lseek
    truncate(path,length)
    问:mmap后关闭文件描述符,对mmap映射有没有影响?
    答:没有
    问:对ptr越界操作会怎样?
    答:段错误

    4.mmap使用

    (1)基本代码

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <string.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    
    int main(int argc, const char* argv[])
    {
            //打开一个文件
            int fd=open("english.txt",O_RDWR);
            int len=lseek(fd,0,SEEK_END);
    
            //创建内存映射区
            void *ptr=mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
            if(ptr==MAP_FAILED)
            {
                    perror("mmap error");
                    exit(1);
            }
    
            printf("%s",(char*)ptr);
    
            //释放内存映射区
            munmap(ptr,len);
            close(fd);
            return 0;
    }
    

    运行结果:

    wangkai@wangkai-HP-242-G1-Notebook-PC:~/0110/6Day$ ./mmap 
    sadaskdkasj
    sdasdasd
    asdasdsadasds
    dasdas
    

    (2)使用mmap进行有血缘关系的进程间通信

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <string.h>
    #include <sys/mman.h>
    #include <sys/wait.h>
    #include <fcntl.h>
    
    /*
    利用mmap进行父子间通信,效率比文件I/O高,
    数据不是从磁盘上读,而是从内存上读写
    
    不阻塞,所以读之前一定要写好
    */
    int main(int argc, const char* argv[])
    {
            //打开一个文件
            int fd=open("english.txt",O_RDWR);
            int len=lseek(fd,0,SEEK_END);
    
            //创建内存映射区
            void *ptr=mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
            if(ptr==MAP_FAILED)
            {
                    perror("mmap error");
                    exit(1);
            }
        
            close(fd);
            //创建子进程
            pid_t pid=fork();
            if(pid==-1)
            {
                    perror("fork error");
                    exit(1);
            }
            if(pid>0)
            {
                    //写数据
                    strcpy((char*)ptr,"我是你爸爸");
                    //回收子进程
                    wait(NULL);
            }
            else if(pid==0)
            {
                    //读数据
                    printf("%s\n",(char*)ptr);
            }
            printf("%s",(char*)ptr);
    
            //释放内存映射区
            munmap(ptr,len);
    
            return 0;
    }

    (3)创建匿名映射区

            int len=4096;
            //创建匿名内存映射区,不指定文件
            void *ptr=mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANON,-1,0);
    

    效果和(2)一样。

    (4)没有血缘关系的进程间通信

    写代码

    //mmap_w_ipc.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    
    
    int main(int argc, char *argv[])
    {
    	int fd = open("temp", O_RDWR | O_CREAT, 0664);
    
    	void* ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    	if(ptr == MAP_FAILED)
    	{
    		perror("mmap");
    		exit(1);
    	}
    
    	while(1)
    	{
            char*p = (char*)ptr;
            p += 1024;
    		strcpy(p, "hello parent, i am your 朋友!!!\n");
    		sleep(2);
    	}
    
    	// 释放
    	int ret = munmap(ptr, 8192);
    	if(ret == -1)
    	{
    		perror("munmap");
    		exit(1);
    	}
    
    	return 0;
    }
    
    

    读代码

    //mmap_r_ipc.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    
    
    int main(int argc, char *argv[])
    {
    	int fd = open("temp", O_RDWR | O_CREAT, 0664);
        ftruncate(fd, 4096);
        int len = lseek(fd, 0, SEEK_END);
    
    	void* ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    	if(ptr == MAP_FAILED)
    	{
    		perror("mmap");
    		exit(1);
    	}
    
    	while(1)
    	{
    		sleep(1);
    		printf("%s\n", (char*)ptr+1024);
    	}
    
    	// 释放
    	int ret = munmap(ptr, len);
    	if(ret == -1)
    	{
    		perror("munmap");
    		exit(1);
    	}
    
    	return 0;
    }
    
    
    

    小结

    1. 进程间通信
        a. 有血缘关系的
    父子进程共享内存映射区
        b. 没有血缘关系的进程间通信
    i. 如何通信?
    不能使用匿名映射的方式
    只能借助磁盘文件创建映射区 - hello
    不阻塞
    ii. a(a.c)  b(b.c)
    a.c
    int fd = open("hello");
    void* ptr = mmap(,,,,,fd, 0);
    对映射区进行读写操作

    b.c
    int fd1 = open("hello");
    void* ptr1 = mmap(,,,,fd1, 0);

    对映射区做读写操作

    2. mmap 实现内存映射
    a. 必须有一个文件
    b. 文件数据什么时候有用:
    i. 单纯文件映射
    ii. 进程间通信:
    1) 文件数据是没有用的
    3. 如果创建匿名映射区
    mmap的时候:
        第二个参数:指定映射区大小
        第四个参数:需要添加MAP_ANON宏
        第五个参数:-1
    4. 父子进程永远共享的东西?
    文件描述符
    内存映射区


    展开全文
  • mmap参数详解

    千次阅读 2013-01-31 10:57:13
    mmap 功能描述:   mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特定地址区域的对象...
    mmap 功能描述: 
      mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特定地址区域的对象映射。 
      基于文件的映射,在mmap和munmap执行过程的任何时刻,被映射文件的st_atime 可能被更新。如果st_atime字段在前述的情况下没有得到更新,首次对映射区的第一个页索引时会更新该字段的值。用PROT_WRITE 和 MAP_SHARED标志建立起来的文件映射,其st_ctime 和 st_mtime
      在对映射区写入之后,但在msync()通过MS_SYNC 和 MS_ASYNC两个标志调用之前会被更新。
      用法: 

      #include
    1.   void *mmap(void *start, size_t length, int prot, int flags,
    2.   int fd, off_t offset);
    3.   int munmap(void *start, size_t length);


      参数: 
      start:映射区的开始地址。
      length:映射区的长度。
      prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
      PROT_EXEC //页内容可以被执行
      PROT_READ //页内容可以被读取
      PROT_WRITE //页可以被写入
      PROT_NONE //页不可访问
      flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
      MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
      MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
      MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。

      MAP_DENYWRITE //这个标志被忽略。
      MAP_EXECUTABLE //同上
      MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
      MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
      MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
      MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
      MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。

      MAP_FILE //兼容标志,被忽略。
      MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
      MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
      MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
      fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。
      offset:被映射对象内容的起点。
      返回说明: 
      成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值 
      EACCES:访问出错
      EAGAIN:文件已被锁定,或者太多的内存已被锁定
      EBADF:fd不是有效的文件描述词
      EINVAL:一个或者多个参数无效
      ENFILE:已达到系统对打开文件的限制
      ENODEV:指定文件所在的文件系统不支持内存映射
      ENOMEM:内存不足,或者进程已超出最大内存映射数量
      EPERM:权能不足,操作不允许
      ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
      SIGSEGV:试着向只读区写入
      SIGBUS:试着访问不属于进程的内存区
    展开全文
  • mmap

    2019-10-07 20:02:28
     void *mmap(void *adrr,size_t length,int prot, int flags, int fd, off_t offset);  返回:成功:返回创建的映射区首地址;失败:MAP_FAILED宏  参数:  addr: 建立映射区的首地址,由Linux内核指定。使用...

    mmap函数

      void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);

        返回:成功:返回创建的映射区首地址;失败:MAP_FAILED

      参数

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

      length: 欲创建映射区的大小,也就是文件大小

      prot 映射区权限PROT_READPROT_WRITEPROT_READ|PROT_WRITE

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

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

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

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

      offset 映射文件的偏移(4k的整数倍,内核中的mmu进行的映射),从文件开始往后移offset个再开始映射

    示例:

    #include <stdio.h>
    #include <sys/mmap.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    
    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);    //将文件大小设置为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;
    }

     

     注意:不能创建大小为0的映射区

       创建映射区得到的地址和最后释放的地址要相同

       文件的打开权限和映射区创建的权限要一致或大于映射区权限,映射区的创建过程中包含一次对文件的读操作。使用MAP_PRIVATE无所谓,只要能创建成功

       映射文件的偏移必须是4k的整数倍

       文件描述符再mmap后关闭,对映射区的操作无影响

    所以一定要检查mmap的返回值。

     

    段错误:可以用gdb调试,run停止的地方就是错误位置

     

    mmap父子进程通信

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

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

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

     

    结论:父子进程共享:1. 打开的文件  2. mmap建立的映射区(但必须要使用MAP_SHARED)

     

      创建时使用的文件没有存在的必要,作用只有创建映射区,close后就可以unlink掉

     

     

    匿名映射

     

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

     

      使用MAP_ANONYMOUS (MAP_ANON)(只适用于linux), 如:

     

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

     

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

      

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

     

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

     

      ② p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);

     

    映射区当数组看待

     

    strace 可执行文件

      追踪可执行文件的系统调用

     

    两个进程可以打开同一文件进行数据的读写,和数据的传递。其底层使用mmap进行实现。

     

    转载于:https://www.cnblogs.com/lr1402585172/p/10519539.html

    展开全文
  • mmap()函数参数详解

    千次阅读 2019-04-17 15:20:41
    mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。 munmap执行相反的操作,删除特定地址区域的对象映射。 基于文件的映射,在...

    author : wfs
    time : 2019.4.17


    功能描述

    • mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。 munmap执行相反的操作,删除特定地址区域的对象映射。
    • 基于文件的映射,在mmap和munmap执行过程的任何时刻,被映射文件的st_atime可能被更新。
    • 在对映射区写入之后,调用msync()函数写回。
    用法
      #include <sys/mman.h>
      void *mmap(void *start, size_t length, int prot, int flags,int fd, off_t offset);
      int munmap(void *start, size_t length); 
    
    

    参数:

    start:映射区的开始地址。
    length:映射区的长度。
    prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
    PROT_EXEC //页内容可以被执行
    PROT_READ //页内容可以被读取
    PROT_WRITE //页可以被写入
    PROT_NONE //页不可访问

    flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
      MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
      MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
      MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
      MAP_DENYWRITE //这个标志被忽略。
      MAP_EXECUTABLE //同上
      MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
      MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。
      MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。
      MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。
      MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。
      MAP_FILE //兼容标志,被忽略。
      MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
      MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
      MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。
      fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。
      offset:被映射对象内容的起点。
      返回说明:
      成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值
      EACCES:访问出错
      EAGAIN:文件已被锁定,或者太多的内存已被锁定
      EBADF:fd不是有效的文件描述词
      EINVAL:一个或者多个参数无效
      ENFILE:已达到系统对打开文件的限制
      ENODEV:指定文件所在的文件系统不支持内存映射
      ENOMEM:内存不足,或者进程已超出最大内存映射数量
      EPERM:权能不足,操作不允许
      ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志
      SIGSEGV:试着向只读区写入
      SIGBUS:试着访问不属于进程的内存区

    共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。

    采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

    Linux的2.2.x 内核支持多种共享内存方式,如mmap()系统调用,Posix共享内存,以及系统V共享内存。linux发行版本如Redhat 8.0支持mmap()系统调用及系统V共享内存,但还没实现Posix共享内存,本文将主要介绍mmap()系统调用及系统V共享内存API的原理及应用。

    一、内核怎样保证各个进程寻址到同一个共享内存区域的内存页面

    1、 page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。

    2、文件与 address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个 address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。

    3、进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。

    4、对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区 (swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。
    注:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新。

    5、所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。
    注:一个共享内存区域可以看作是特殊文件系统shm中的一个文件,shm的安装点在交换区上。

    上面涉及到了一些数据结构,围绕数据结构理解问题会容易一些。

        回页首
    

    二、mmap()及其相关系统调用

    mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

    注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

    1、mmap()系统调用形式如下:

    void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
    参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读) , PROT_WRITE (可写), PROT_EXEC (可执行), PROT_NONE(不可访问)。flags由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。offset参数一般设为0,表示从文件头开始映射。参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。这里不再详细介绍mmap()的参数,读者可参考mmap()手册页获得进一步的信息。

    2、系统调用mmap()用于共享内存的两种方式:

    (1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下:

        fd=open(name, flag, mode);
    

    if(fd<0)

    ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,我们将在范例中进行具体说明。

    (2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。
    对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可,参见范例2。

    3、系统调用munmap()

    int munmap( void * addr, size_t len )
    该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。

    4、系统调用msync()

    int msync ( void * addr , size_t len, int flags)
    一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

        回页首
    

    三、mmap()范例

    下面将给出使用mmap()的两个范例:范例1给出两个进程通过映射普通文件实现共享内存通信;范例2给出父子进程通过匿名映射实现共享内存。系统调用 mmap()有许多有趣的地方,下面是通过mmap()映射普通文件实现进程间的通信的范例,我们通过该范例来说明mmap()实现共享内存的特点及注意事项。

    范例1:两个进程通过映射普通文件实现共享内存通信

    范例1包含两个子程序:map_normalfile1.c及map_normalfile2.c。编译两个程序,可执行文件分别为 map_normalfile1及map_normalfile2。两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。 map_normalfile2试图打开命令行参数指定的一个普通文件,把该文件映射到进程的地址空间,并对映射后的地址空间进行写操作。 map_normalfile1把命令行参数指定的文件映射到进程地址空间,然后对映射后的地址空间执行读操作。这样,两个进程通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。

    下面是两个程序代码:

    /-------------map_normalfile1.c-----------/
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    typedef struct{
    char name[4];
    int age;
    }people;

    main(int argc, char** argv) // map a normal file as shared mem:
    {
    int fd,i;
    people *p_map;
    char temp;

        fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
        lseek(fd,sizeof(people)*5-1,SEEK_SET);
        write(fd,"",1);
       
        p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );
        close( fd );
        temp = 'a';
        for(i=0; i<10; i++)
        {
                temp += 1;
                memcpy( ( *(p_map+i) ).name, &temp,2 );
                ( *(p_map+i) ).age = 20+i;
        }
        printf(" initialize over /n ");
        sleep(10);
    
        munmap( p_map, sizeof(people)*10 );
        printf( "umap ok /n" );
    

    }

    /-------------map_normalfile2.c-----------/
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    typedef struct{
    char name[4];
    int age;
    }people;

    main(int argc, char** argv) // map a normal file as shared mem:
    {
    int fd,i;
    people p_map;
    fd=open( argv[1],O_CREAT|O_RDWR,00777 );
    p_map = (people
    )mmap(NULL,sizeof(people)10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    for(i = 0;i<10;i++)
    {
    printf( “name: %s age %d;/n”,(
    (p_map+i)).name, (*(p_map+i)).age );

        }
        munmap( p_map,sizeof(people)*10 );
    

    }

    map_normalfile1.c 首先定义了一个people数据结构,(在这里采用数据结构的方式是因为,共享内存区的数据往往是有固定格式的,这由通信的各个进程决定,采用结构的方式有普遍代表性)。map_normfile1首先打开或创建一个文件,并把文件的长度设置为5个people结构大小。然后从mmap()的返回地址开始,设置了10个people结构。然后,进程睡眠10秒钟,等待其他进程映射同一个文件,最后解除映射。

    map_normfile2.c只是简单的映射一个文件,并以people数据结构的格式从mmap()返回的地址处读取10个people结构,并输出读取的值,然后解除映射。

    分别把两个程序编译成可执行文件map_normalfile1和map_normalfile2后,在一个终端上先运行./map_normalfile2 /tmp/test_shm,程序输出结果如下:

    initialize over
    umap ok

    在map_normalfile1输出initialize over 之后,输出umap ok之前,在另一个终端上运行map_normalfile2 /tmp/test_shm,将会产生如下输出(为了节省空间,输出结果为稍作整理后的结果):

    name: b age 20; name: c age 21; name: d age 22; name: e age 23; name: f age 24;
    name: g age 25; name: h age 26; name: I age 27; name: j age 28; name: k age 29;

    在map_normalfile1 输出umap ok后,运行map_normalfile2则输出如下结果:

    name: b age 20; name: c age 21; name: d age 22; name: e age 23; name: f age 24;
    name: age 0; name: age 0; name: age 0; name: age 0; name: age 0;

    从程序的运行结果中可以得出的结论

    1、 最终被映射文件的内容的长度不会超过文件本身的初始大小,即映射不能改变文件的大小;

    2、可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小,但不完全受限于文件大小。打开文件被截短为5个people结构大小,而在 map_normalfile1中初始化了10个people数据结构,在恰当时候(map_normalfile1输出initialize over 之后,输出umap ok之前)调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值,后面将给出详细讨论。
    注:在linux中,内存的保护是以页为基本单位的,即使被映射文件只有一个字节大小,内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时,进程可以对从mmap()返回地址开始的一个页面大小进行访问,而不会出错;但是,如果对一个页面以外的地址空间进行访问,则导致错误发生,后面将进一步描述。因此,可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。

    3、文件一旦被映射后,调用mmap()的进程对返回地址的访问是对某一内存区域的访问,暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义,只有在调用了munmap()后或者msync()时,才把内存中的相应内容写回磁盘文件,所写内容仍然不能超过文件的大小。

    范例2:父子进程通过匿名映射实现共享内存

    #include <sys/mman.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    typedef struct{
    char name[4];
    int age;
    }people;
    main(int argc, char** argv)
    {
    int i;
    people p_map;
    char temp;
    p_map=(people
    )mmap(NULL,sizeof(people)10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
    if(fork() == 0)
    {
    sleep(2);
    for(i = 0;i<5;i++)
    printf(“child read: the %d people’s age is %d/n”,i+1,(
    (p_map+i)).age);
    (p_map).age = 100;
    munmap(p_map,sizeof(people)10); //实际上,进程终止时,会自动解除映射。
    exit();
    }
    temp = ‘a’;
    for(i = 0;i<5;i++)
    {
    temp += 1;
    memcpy((
    (p_map+i)).name, &temp,2);
    (
    (p_map+i)).age=20+i;
    }

        sleep(5);
        printf( "parent read: the first people,s age is %d/n",(*p_map).age );
        printf("umap/n");
        munmap( p_map,sizeof(people)*10 );
        printf( "umap ok/n" );
    

    }

    考察程序的输出结果,体会父子进程匿名共享内存:

    child read: the 1 people’s age is 20
    child read: the 2 people’s age is 21
    child read: the 3 people’s age is 22
    child read: the 4 people’s age is 23
    child read: the 5 people’s age is 24

    parent read: the first people,s age is 100
    umap
    umap ok

        回页首
    

    四、对mmap()返回地址的访问

    前面对范例运行结构的讨论中已经提到,linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明:

    注意:文件被映射部分而不是整个文件决定了进程能够访问的空间大小,另外,如果指定文件的偏移部分,一定要注意为页面大小的整数倍。下面是对进程映射地址空间的访问范例:

    #include <sys/mman.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    typedef struct{
    char name[4];
    int age;
    }people;

    main(int argc, char** argv)
    {
    int fd,i;
    int pagesize,offset;
    people *p_map;

        pagesize = sysconf(_SC_PAGESIZE);
        printf("pagesize is %d/n",pagesize);
        fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
        lseek(fd,pagesize*2-100,SEEK_SET);
        write(fd,"",1);
        offset = 0;        //此处offset = 0编译成版本1;offset = pagesize编译成版本2
        p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);
        close(fd);
       
        for(i = 1; i<10; i++)
        {
                (*(p_map+pagesize/sizeof(people)*i-2)).age = 100;
                printf("access page %d over/n",i);
                (*(p_map+pagesize/sizeof(people)*i-1)).age = 100;
                printf("access page %d edge over, now begin to access page %d/n",i, i+1);
                (*(p_map+pagesize/sizeof(people)*i)).age = 100;
                printf("access page %d over/n",i+1);
        }
        munmap(p_map,sizeof(people)*10);
    

    }

    如程序中所注释的那样,把程序编译成两个版本,两个版本主要体现在文件被映射部分的大小不同。文件的大小介于一个页面与两个页面之间(大小为:pagesize2-99),版本1的被映射部分是整个文件,版本2的文件被映射部分是文件大小减去一个页面后的剩余部分,不到一个页面大小(大小为:pagesize-99)。程序中试图访问每一个页面边界,两个版本都试图在进程空间中映射pagesize3的字节数。

    版本1的输出结果如下:

    pagesize is 4096
    access page 1 over
    access page 1 edge over, now begin to access page 2
    access page 2 over
    access page 2 over
    access page 2 edge over, now begin to access page 3
    Bus error //被映射文件在进程空间中覆盖了两个页面,此时,进程试图访问第三个页面

    版本2的输出结果如下:

    pagesize is 4096
    access page 1 over
    access page 1 edge over, now begin to access page 2
    Bus error //被映射文件在进程空间中覆盖了一个页面,此时,进程试图访问第二个页面

    结论:采用系统调用mmap()实现进程间通信是很方便的,在应用层上接口非常简洁。内部实现机制区涉及到了linux存储管理以及文件系统等方面的内容,可以参考一下相关重要数据结构来加深理解。

    用法讲解2

    函数mmap是linux的一个系统函数。如下:

    函数原型:void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);

    配套函数原型:int munmap(void *addr, size_t length);

    头文件:#include <sys/mman.h>

    返回值:成功返回创建的映射区的首地址;失败返回宏MAP_FAILED。

    参数介绍:

    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的函数来释放。

    返回值:成功:0; 失败:-1

    接下来来使用mamp函数创建一个映射区:代码如下

    #include <stdio.h>
    
    #include <stdlib.h>
    
    #include <sys/mman.h>
    
    #include <sys/types.h>
    
    #include <unistd.h>
    
    #include <sys/stat.h>
    
    #include <fcntl.h>
    
    #include <string.h>
    
     
    
    void sys_err(char p[])//处理错误
    
    {
    
        perror(p);
    
        exit(1);
    
    }
    
    int main()
    
    {
    
        int o_ret = open("my_mmap.txt", O_CREAT | O_RDWR, 0644);//创建一个新文件
    
        if (-1 == o_ret) //错误检查是一个很好的习惯
    
        {
    
            sys_err("open ");
    
        }
    
     
    
        int f_ret = ftruncate(o_ret, 512);//在此文件大小的基础上扩展512字节大小,即文件现有大小为512字节(是个好函数,该记着。)
    
        if (-1 == f_ret)//成功返回0
    
        {
    
            sys_err("ftruncate ");
    
        }
    
     
    
        //———————————————————————————————————————————
    
        struct stat statbuf;
    
        int s_ret = stat("my_mmap.txt", &statbuf);
    
        if (-1 == s_ret)
    
        {
    
            sys_err("stat ");
    
        }
    
        //———————————————————————————————————————————
    
        /*
    
        上面这段代码是我用来获取文件大小的方法,并没有一个系统函数能够直接获取文件的大小。所以我利用stat结构体存储文件的相关信息,然后获取到文件的大小:statbuf.st_size
    
        */
    
        char *const address = (char *)mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, o_ret, 0);
    
        //这里注意,我写的是char *const address这保证了address的值不变。
    
        if (MAP_FAILED == address)//若是mmap函数调用失败
    
        {
    
            sys_err("mmap ");
    
        }
    
        strcpy(address, "hallo, mmap");//能做这个操作是有先决条件的:映射区拥有写权限。
    
        int c_ret = close(o_ret);
    
        if (-1 == c_ret)
    
        {
    
            sys_err("close ");
    
        }
    
     
    
        int mun_ret = munmap(address, statbuf.st_size);//释放掉映射区。
    
        if (-1 == mun_ret)
    
        {
    
            sys_err("munmap ");
    
        }
    
     
    
        //printf("hello from mmap_test!\n");
    
        return 0;
    
    }
    

    很简单,这段代码编译之后就可以良好的运行下去。现在,我们来思考几个问题:

    可以open的时候O_CREAT一个新文件来创建映射区吗?

    如果open时O_RDONLY, mmap时PROT参数指定PROT_READ|PROT_WRITE会怎样?

    文件描述符先关闭,对mmap映射有没有影响?

    若是length参数大于文件的大小会怎样?

    对mmap函数的返回值进行越界操作(++,–)会怎样?

    如果文件偏移量为1000会怎样?

    如果不检测mmap的返回值,会怎样?

    答案:

    可以,只要是正确的文件描述符传递给mmap都行

    O_RDONLY代表只读,PROT_READ|PROT_WRITE代表读写,通过测试,我们就知道这是不行的,文件的权限代表着能对文件执行的操作;这里有一个结论:当第四个参数为MAP_SHARED时,映射区的权限应小于等于文件打开的权限。这样做的原因是出于对映射区的保护。而当第四个参数为MAP_PRIVATE时,就无所谓了,因为mmap中的权限时对内存的限制(映射区时在缓存区中创建的,缓存区是由内核管理调动的,不属于内存区域)。

    只要mmap调用成功,文件就可以立即关闭,映射区的释放、操作与文件的关闭无关,因为映射区创建成功后时通过另一种方式(指针)来管理的。

    注意:当用于创建映射区的文件大小为0时,不能创建映射区,会提示总线错误。所以:用于创建映射区的文件必须有实际的文件大小,并且创建的映射区大小不能超过文件大小。当出现总线错误的时候,多半是由于共享文件储存空间大小不正确引起的。

    会造成调用munmap函数失败,不能释放映射区。所以推荐使用const关键字限定指针的值。

    会出错,前面说过,文件的偏移量必须是4096(4k)的整数倍。

    会死的很难看,因为使用mmap函数出错的几率很高。

    在补充一下:

    p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);看到标颜色的字词了吗?其中MAP_ANON是MAP_ANONYMOUS(MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。)的别称,不再被使用。因为我们创建一个匿名映射,所以我们不需要传递文件描述符。

    展开全文
  • mmap映射需要文件描述符的读权限(即open时需要加上读权限),不然不能使用。 所有内存保护组合与使用O_RDWR标记打开文件是兼容的。 一些硬件架构不允许对一个分页的只写访问。 prot值 描述 PROT_NONE ...
  • Linux mmap

    千次阅读 2018-07-03 17:50:24
    mmap基础概念1.mmap内存映射原理mmap是一种内存映射文件的方法,即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系;实现这样的映射关系后,进程就...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,421
精华内容 4,968
关键字:

length参数mmap