站长资讯网
最全最丰富的资讯网站

linux mmap 内存映射 mmap() vs read()/write()/lseek()的实例演示

通过strace统计系统调用的时候,经常可以看到mmap()与mmap2()。系统调用mmap()可以将某文件映射至内存(进程空间),如此可以把对文件的操作转为对内存的操作,以此避免更多的lseek()与read()、write()操作,这点对于大文件或者频繁访问的文件而言尤其受益。但有一点必须清楚:mmap的addr与offset必须对齐一个内存页面大小的边界,即内存映射往往是页面大小的整数倍,否则maaped_file_size%page_size内存空间将被闲置浪费。

演示一下,将文件/tmp/file_mmap中的字符转成大写,分别使用mmap与read/write二种方法实现。

  /*  * @file: t_mmap.c  */  #include   #include   #include  /*mmap munmap*/  #include   #include   #include   #include     int main(int argc, char *argv[])  {   int fd;   char *buf;   off_t len;   struct stat sb;   char *fname = "/tmp/file_mmap";     fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);   if (fd == -1)   {    perror("open");    return 1;   }   if (fstat(fd, &sb) == -1)   {    perror("fstat");    return 1;   }     buf = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);   if (buf == MAP_FAILED)   {    perror("mmap");    return 1;   }     if (close(fd) == -1)   {    perror("close");    return 1;   }     for (len = 0; len < sb.st_size; ++len)   {    buf[len] = toupper(buf[len]);    /*putchar(buf[len]);*/   }     if (munmap(buf, sb.st_size) == -1)   {    perror("munmap");    return 1;   }   return 0;  }  #gcc –o t_mmap t_mmap.c  #strace ./t_mmap  open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open,返回fd=3  fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 即文件大小18  mmap2(NULL, 18, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0xb7867000 //mmap文件fd=3  close(3)  = 0 //close文件fd=3  munmap(0xb7867000, 18)= 0  //munmap,移除0xb7867000这里的内存映射

虽然没有看到read/write写文件操作,但此时文件/tmp/file_mmap中的内容已由www.perfgeeks.com改变成了WWW.PERFGEEKS.COM .这里mmap的addr是0(NULL),offset是18,并不是一个内存页的整数倍,即有4078bytes(4kb-18)内存空间被闲置浪费了。

  #include   #include   #include   #include   #include   #include   #include   #include     int main(int argc, char *argv[])  {   int fd, len;   char *buf;   char *fname = "/tmp/file_mmap";   ssize_t ret;   struct stat sb;     fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);   if (fd == -1)   {    perror("open");    return 1;   }   if (fstat(fd, &sb) == -1)   {    perror("stat");    return 1;   }     buf = malloc(sb.st_size);   if (buf == NULL)   {    perror("malloc");    return 1;   }   ret = read(fd, buf, sb.st_size);   for (len = 0; len < sb.st_size; ++len)   {    buf[len] = toupper(buf[len]);    /*putchar(buf[len]);*/   }     lseek(fd, 0, SEEK_SET);   ret = write(fd, buf, sb.st_size);   if (ret == -1)   {    perror("error");    return 1;   }     if (close(fd) == -1)   {    perror("close");    return 1;  }  free(buf);   return 0;  }  #gcc –o t_rw t_rw.c  open("/tmp/file_mmap", O_RDWR|O_CREAT, 0600) = 3 //open, fd=3  fstat64(3, {st_mode=S_IFREG|0644, st_size=18, ...}) = 0 //fstat, 其中文件大小18  brk(0) = 0x9845000  //brk, 返回当前中断点  brk(0x9866000)  = 0x9866000  //malloc分配内存,堆当前最后地址  read(3, "www.perfgeeks.comn", 18)= 18 //read  lseek(3, 0, SEEK_SET) = 0 //lseek  write(3, "WWW.PERFGEEKS.COMn", 18)  = 18 //write  close(3)  = 0 //close

这里通过read()读取文件内容,toupper()后,调用write()写回文件。因为文件太小,体现不出read()/write()的缺点:频繁访问大文件,需要多个lseek()来确定位置。每次编辑read()/write(),在物理内存中的双份数据。当然,不可以忽略创建与维护mmap()数据结构的成本。需要注意:并没有具体测试mmap vs read/write,即不能一语断言谁孰谁劣,具体应用场景具体评测分析。你只是要记住:mmap内存映射文件之后,操作内存即是操作文件,可以省去不少系统内核调用(lseek, read, write)。

mmap() vs malloc()

使用strace调试的时候,通常可以看到通过mmap()创建匿名内存映射的身影。比如启用dl(‘apc.so’)的时候,就可以看到如下语句。

mmap2(NULL, 31457280, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0xb5ce7000 //30M

通常使用mmap()进行匿名内存映射,以此来获取内存,满足一些特别需求。所谓匿名内存映射,是指mmap()的时候,设置了一个特殊的标志MAP_ANONYMOUS,且fd可以忽略(-1)。某些操作系统(像FreeBSD),不支持标志MAP_ANONYMOUS,可以映射至设备文件/dev/zero来实现匿名内存映射。使用mmap()分配内存的好处是页面已经填满了0,而malloc()分配内存后,并没有初始化,需要通过memset()初始化这块内存。另外,malloc()分配内存的时候,可能调用brk(),也可能调用mmap2()。即分配一块小型内存(小于或等于128kb),malloc()会调用brk()调高断点,分配的内存在堆区域,当分配一块大型内存(大于128kb),malloc()会调用mmap2()分配一块内存,与堆无关,在堆之外。同样的,free()内存映射方式分配的内存之后,内存马上会被系统收回,free()堆中的一块内存,并不会马上被系统回收,glibc会保留它以供下一次malloc()使用。

这里演示一下malloc()使用brk()和mmap2()。

  /*  * file:t_malloc.c  */  #include   #include   #include     int main(int argc, char *argv)  {   char *brk_mm, *mmap_mm;     printf("-----------------------n");   brk_mm = (char *)malloc(100);   memset(brk_mm, '', 100);   mmap_mm = (char *)malloc(500 * 1024);   memset(mmap_mm, '', 500*1024);   free(brk_mm);   free(mmap_mm);   printf("-----------------------n");     return 1;  }    #gcc –o t_malloc t_malloc.c  #strace ./t_malloc  write(1, "-----------------------n", 24-----------------------) = 24  brk(0) = 0x85ee000  brk(0x860f000)  = 0x860f000//malloc(100)  mmap2(NULL, 516096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7702000 //malloc(5kb)  munmap(0xb7702000, 516096)  = 0 //free(), 5kb   write(1, "-----------------------n", 24-----------------------) = 24

通过malloc()分别分配100bytes和5kb的内存,可以看出其实分别调用了brk()和mmap2(),相应的free()也是不回收内存和通过munmap()系统回收内存。

mmap()共享内存,进程通信

内存映射mmap()的另一个外常见的用法是,进程通信。相较于管道、消息队列方式而言,这种通过内存映射的方式效率明显更高,它不需要任务数据拷贝。这里,我们通过一个例子来说明mmap()在进程通信方面的应用。我们编写二个程序,分别是master和slave,slave根据master不同指令进行不同的操作。Master与slave就是通过映射同一个普通文件进行通信的。

  /*   *@file master.c   */  root@liaowq:/data/tmp# cat master.c   #include   #include   #include   #include   #include   #include   #include   #include     void listen();    int main(int argc, char *argv[])  {   listen();   return 0;  }    void listen()  {   int fd;   char *buf;   char *fname = "/tmp/shm_command";     char command;   time_t now;     fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);   if (fd == -1)   {    perror("open");    exit(1);   }   buf = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);   if (buf == MAP_FAILED)   {    perror("mmap");    exit(1);   }   if (close(fd) == -1)   {    perror("close");    exit(1);   }     *buf = '0';   sleep(2);   for (;;)   {    if (*buf == '1' || *buf == '3' || *buf == '5' || *buf == '7')    {  if (*buf > '1')   printf("%ldtgood job [%c]n", (long)time(&now), *buf);  (*buf)++;    }    if (*buf == '9')    {  break;    }    sleep(1);   }     if (munmap(buf, 4096) == -1)   {    perror("munmap");    exit(1);   }  }    /*   *@file slave.c   */  #include   #include   #include   #include   #include   #include   #include   #include     void ready(unsigned int t);  void job_hello();  void job_smile();  void job_bye();  char get_command(char *buf);  void wait();    int main(int argc, char *argv[])  {   wait();   return 0;  }    void ready(unsigned int t)  {   sleep(t);  }    /* command 2 */  void job_hello()  {   time_t now;   printf("%ldthello worldn", (long)time(&now));  }    /* command 4 */  void job_simle()  {   time_t now;   printf("%ldt^_^n", (long)time(&now));  }    /* command 6 */  void job_bye()  {   time_t now;   printf("%ldt|<--n", (long)time(&now));  }    char get_command(char *buf)  {   char *p;   if (buf != NULL)   {    p = buf;   }   else   {    return '0';   }   return *p;  }    void wait()  {   int fd;   char *buf;   char *fname = "/tmp/shm_command";     char command;   time_t now;     fd = open(fname, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);   if (fd == -1)   {    perror("open");    exit(1);   }   buf = mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);   if (buf == MAP_FAILED)   {    perror("mmap");    exit(1);   }   if (close(fd) == -1)   {    perror("close");    exit(1);   }     for (;;)   {    command = get_command(buf);    /*printf("%cn", command);*/    switch(command)    {  case '0':   printf("%ldtslave is ready...n", (long)time(&now));   ready(3);   *buf = '1';   break;  case '2':   job_hello();   *buf = '3';   break;  case '4':   job_simle();   *buf = '5';   break;  case '6':   job_bye();   *buf = '7';   break;  default:   break;    }    if (*buf == '8')    {  *buf = '9';  if (munmap(buf, 4096) == -1)  {   perror("munmap");   exit(1);  }  return;    }    sleep(1);   }   if (munmap(buf, 4096) == -1)   {    perror("munmap");    exit(1);   }  }

执行master与slave,输出如下

root@liaowq:/data/tmp# echo “0″ > /tmp/shm_command

root@liaowq:/data/tmp# ./master

1320939445 good job [3]

1320939446 good job [5]

1320939447 good job [7]

root@liaowq:/data/tmp# ./slave

1320939440 slave is ready…

1320939444 hello world

1320939445 ^_^

1320939446 |<–

master向slave发出job指令2,4,6。slave收到指令后,执行相关逻辑操作,完成后告诉master,master知道slave完成工作后,打印good job并且发送一下job指令。master与slave通信,是通过mmap()共享内存实现的。

总结

1、 Linux采用了投机取巧的分配策略,用到时,才分配物理内存。也就是说进程调用brk()或mmap()时,只是占用了虚拟地址空间,并没有真正占用物理内存。这也正是free –m中used并不意味着消耗的全都是物理内存。

2、 mmap()通过指定标志(flag) MAP_ANONYMOUS来表明该映射是匿名内存映射,此时可以忽略fd,可将它设置为-1。如果不支持MAP_ANONYMOUS标志的类unix系统,可以映射至特殊设备文件/dev/zero实现匿名内存映射。

3、 调用mmap()时就决定了映射大小,不能再增加。换句话说,映射不能改变文件的大小。反过来,由文件被映射部分,而不是由文件大小来决定进程可访问内存空间范围(映射时,指定offset最好是内存页面大小的整数倍)。

4、通常使用mmap()的三种情况.提高I/O效率、匿名内存映射、共享内存进程通信。

相关链接

1.高性能网络编程

2.内存管理内幕

3.C语言指针与内存泄漏

4.read系统调用剖析

5.linux环境进程间通信:共享内存

6. <<linux系统编程>> <<unix网络编程2>></unix网络编程2></linux系统编程>

赞(0)
分享到: 更多 (0)
网站地图   沪ICP备18035694号-2    沪公网安备31011702889846号