linux文件映射内存(转载+整理)

本文探讨了处理大型文件的不同方法,包括直接使用Linux API、C标准库以及文件内存映射技术。通过实例对比了这些方法在统计日志行数时的性能差异,展示了内存映射在提高I/O效率方面的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

次再次讨论文件的操作,只是成了大型文件

之所以讨论大型文件,是因当今的大数据是老牌的搜索引擎,都会涉及到大型文件的理效率。

比如前一生成了50000行的日志,那么如果是300万行,甚至更多的候,前一提到的函数是否能达到我接受的效率呢。

统计日志行数,分LinuxAPI C库实现,可以看到直接API,耗72秒,用C20,因为标使用了存,所以速度上快了三倍。

当我们换成最后一种方法的候,文件内存映射,速度再次提高了一个级别,不到1秒。相当惊的成

 

文件:<sys/mman.h>

void*mmap( void *addr, size_t length, intprot, int flag, intfd, off_t offset );

第一个参数addr,表示自己制定的映射后的内存地址,一般不指定,除非你想制定确定的内存地址。

第二个参数,内存映射的大小,入的是文件的大小,因文件不是很大,所以全部大小,如果文件太大,可以分次分批映射。

第三个参数代表限,PROT_READ/PROT_WRITE/ PROT_EXEC

第四个参数flag, 代表映射的方式,MAP_PRIVATE/ MAP_SHARED 个参数主要是多个程映射使用,主要是写入行保PRIVATE代表自己又自己的份。

最后一个参数代表文件的偏移,和第二个参数搭配使用可以分段映射大文件。

 

文件映射之所以快,是因采用了系中的虚内存技。我的内存一般都比硬小,当内存不足的候,系会使用硬来做交分区,对页进出。虚内存既然可以使用硬来作内存的份,文件映射提供了很好的实现方式。只需要把文件本身当作我的交分区,那么随分区的算法(中断算法)靠硬件来完成文件的取操作

strace统计统调用的候,常可以看到mmap()mmap2()。系统调mmap()可以将某文件映射至内存(程空),如此可以把文件的操作转为对内存的操作,以此避免更多的lseek()read()write()操作,于大文件或者访问的文件而言尤其受益。

但有一点必清楚:mmapaddroffset须对齐一个内存面大小的界,即内存映射往往是面大小的整数倍,否maaped_file_size%page_size内存空将被置浪

 

你只是要住:mmap内存映射文件之后,操作内存即是操作文件,可以省去不少系内核(lseek, read,write)

 

[cpp]read

1. <P>#include <stdio.h>  

2. #include <time.h>   

3. #include <fcntl.h>   

4. /* 

5. [chenndao@localhost log]$ gcc -o logreadapi logreadapi.c 

6. [chenndao@localhost log]$ ./logreadapi 3000000.txt 

7. Finished, total 3000000 lines 

8. Cost time: 72 sec 

9. [chenndao@localhost log]$  

10.*/</P><P>int main( int argc, char *argv[] )  

11.{  

12.  //Caculate how many lines in the log file.  

13.  if ( argc != 2 )  

14.    {  

15.      printf("usage: \n readlog log.txt\n");  

16.      return 0;  

17.    }  

18.  int lineNum = 0;  

19.  char buff[256] = { 0 };  

20.  time_t timeStart = time( NULL );  

21.  char *data = NULL;  

22.  int fd = open( argv[1], O_RDONLY );  

23.  if ( fd > 0 )  

24.    {  

25.      int len = 0;  

26.      do  

27. {  

28.   len = read( fd, buff, 256 );  

29.   if ( len > 0 )  

30.     {  

31.       int i = 0;  

32.       for( ; i<len; i++)  

33.  {  

34.    if ( buff[i] == '\n' )  

35.      {  

36.        lineNum++;  

37.      }  

38.  }  

39.     }  

40. }  

41.      while ( len > 0 );  

42.      printf("Finished, total %d lines\n", lineNum);  

43.      printf("Cost time: %d sec\n", time(NULL)-timeStart);  

44.    }  

45.  else  

46.    {  

47.      printf("Open file %s error\n", argv[1]);  

48.    }</P><P>  return 1;  

49.}  

50.</P>  

#include <stdio.h>

#include <time.h>

#include <fcntl.h>

/*

[chenndao@localhost log]$ gcc -o logreadapi logreadapi.c

[chenndao@localhost log]$ ./logreadapi 3000000.txt

Finished, total 3000000 lines

Cost time: 72 sec

[chenndao@localhost log]$

*/

int main( int argc, char *argv[] )

{

  //Caculate how many lines in the log file.

  if ( argc != 2 )

    {

      printf("usage: \nreadlog log.txt\n");

      return 0;

    }

  int lineNum = 0;

  char buff[256] = { 0 };

  time_t timeStart = time( NULL );

  char *data = NULL;

  int fd = open( argv[1], O_RDONLY );

  if ( fd > 0 )

    {

      int len = 0;

      do

 {

   len = read( fd, buff, 256 );

   if ( len > 0 )

     {

       int i = 0;

       for( ; i<len;i++)

  {

    if ( buff[i] == '\n' )

      {

        lineNum++;

      }

  }

     }

 }

      while ( len > 0 );

      printf("Finished,total %d lines\n", lineNum);

      printf("Cost time: %dsec\n", time(NULL)-timeStart);

    }

  else

    {

      printf("Open file %serror\n", argv[1]);

    }

  return 1;

}

[cpp]fgets

1. <P>#include <stdio.h>  

2. #include <time.h>   

3. /* 

4. [chenndao@localhost log]$ ./logread 3000000.txt 

5. Finished, total 3000000 lines 

6. Cost time: 20 sec 

7. */</P><P>int main( int argc, char *argv[] )  

8. {  

9.   //Caculate how many lines in the log file.  

10.  if ( argc != 2 )  

11.    {  

12.      printf("usage: \n readlog log.txt\n");  

13.      return 0;  

14.    }  

15.  int lineNum = 0;  

16.  char buff[256] = { 0 };  

17.  time_t timeStart = time( NULL );  

18.  char *data = NULL;  

19.  FILE *fd = fopen( argv[1], "r" );  

20.  if ( fd != NULL )  

21.    {  

22.      int len = 0;  

23.      do  

24. {  

25.   data = fgets( buff, 256, fd );  

26.   if ( data != NULL )  

27.     {  

28.       int i = 0;  

29.       for( ; i<256; i++)  

30.  {  

31.    if ( buff[i] == '\n' )  

32.      {  

33.        lineNum++;  

34.        break;  

35.      }  

36.  }  

37.     }  

38. }  

39.      while ( data );  

40.      printf("Finished, total %d lines\n", lineNum);  

41.      printf("Cost time: %d sec\n", time(NULL)-timeStart);  

42.    }  

43.  else  

44.    {  

45.      printf("Open file %s error\n", argv[1]);  

46.    }</P><P>  return 1;  

47.}  

48.</P>  

#include <stdio.h>

#include <time.h>

/*

[chenndao@localhost log]$ ./logread 3000000.txt

Finished, total 3000000 lines

Cost time: 20 sec

*/

int main( int argc, char *argv[] )

{

  //Caculate how many lines in the log file.

  if ( argc != 2 )

    {

      printf("usage: \nreadlog log.txt\n");

      return 0;

    }

  int lineNum = 0;

  char buff[256] = { 0 };

  time_t timeStart = time( NULL );

  char *data = NULL;

  FILE *fd = fopen( argv[1], "r" );

  if ( fd != NULL )

    {

      int len = 0;

      do

 {

   data = fgets( buff, 256, fd );

   if ( data != NULL )

     {

       int i = 0;

       for( ; i<256;i++)

  {

    if ( buff[i] == '\n' )

      {

        lineNum++;

        break;

      }

  }

     }

 }

      while ( data );

      printf("Finished,total %d lines\n", lineNum);

      printf("Cost time: %dsec\n", time(NULL)-timeStart);

    }

  else

    {

      printf("Open file %serror\n", argv[1]);

    }

  return 1;

}

 

 

[cpp]mmap

1. #include <stdio.h>   

2. #include <fcntl.h>   

3. #include <time.h>   

4. #include <sys/mman.h>   

5.   

6. /* 

7. [chenndao@localhost log]$ ./maplogread 3000000.txt 

8. Finished, total lines is 3000000  

9. total costed time 0 sec 

10.[chenndao@localhost log]$ ./maplogread 3000000.txt 

11.Finished, total lines is 3000000  

12.total costed time 1 secs 

13.*/  

14.  

15.int main( int argc, char *argv[] )  

16.{  

17.  if ( argc != 2 )  

18.    {  

19.      printf("usage: \n maplogread 50000.txt\n");  

20.      return 0;  

21.    }  

22.    

23.  char *memory = NULL;  

24.  int file_length = 0;  

25.  char *start_address = 0;  

26.  int line_num = 0;  

27.  int time_start = time(NULL);  

28.  int fd = open( argv[1], O_RDONLY );  

29.  if ( fd > 0 )  

30.    {  

31.      file_length = lseek(fd, 1, SEEK_END);  

32.      memory = mmap( start_address, file_length, PROT_READ, MAP_SHARED, fd, 0 );  

33.  

34.      int i=0;  

35.      for ( ; i<file_length; i++ )  

36.    {  

37.      if ( memory[i] == '\n' )  

38.        {  

39.          ++line_num;  

40.        }  

41.    }  

42.      close( fd );  

43.      munmap( memory, file_length );  

44.      printf("Finished, total lines is %d \n", line_num);  

45.      printf("total costed time %d sec\n", time(NULL) - time_start);  

46.    }  

47.  return 0;  

48.}  

 

-------------------------------------------------------------------------------------------------------------------------

 

linuxmmap内存映射

mmap()vs read()/write()/lseek()

strace统计统调用的候,常可以看到mmap()mmap2()统调mmap()可以将某文件映射至内存(程空),如此可以把文件的操作转为对内存的操作,以此避免更多的lseek()read()write()操作,于大文件或者访问的文件而言尤其受益。但有一点必清楚:mmapaddroffset须对齐一个内存面大小的界,即内存映射往往是面大小的整数倍,否maaped_file_size%page_size内存空将被

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

/*

* @file:t_mmap.c

*/

#include <stdio.h>

#include<ctype.h>

#include<sys/mman.h> /*mmapmunmap*/

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<unistd.h>

 

int main(intargc, char *argv[])

{

intfd;

char *buf;

off_tlen;

struct stat sb;

char *fname = "/tmp/file_mmap";

 

fd = open(fname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);

if(fd == -1)

{

perror("open");

return1;

}

if(fstat(fd, &sb) == -1)

{

perror("fstat");

return1;

}

 

buf = mmap(0, sb.st_size, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);

if(buf == MAP_FAILED)

{

perror("mmap");

return1;

}

 

if(close(fd) == -1)

{

perror("close");

return1;

}

 

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");

return1;

}

return0;

}

#gcc –ot_mmapt_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.mmapaddr0(NULL)offset18,并不是一个内存的整数倍,即有4078bytes4kb-18)内存空置浪了。

#include<stdio.h>

#include<string.h>

#include<stdlib.h>

#include<ctype.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<unistd.h>

 

int main(intargc, char *argv[])

{

intfd, len;

char *buf;

char *fname = "/tmp/file_mmap";

ssize_tret;

struct stat sb;

 

fd = open(fname, O_CREAT|O_RDWR, S_IRUSR|S_IWUSR);

if(fd == -1)

{

perror("open");

return1;

}

if(fstat(fd, &sb) == -1)

{

perror("stat");

return1;

}

 

buf =malloc(sb.st_size);

if(buf == NULL)

{

perror("malloc");

return1;

}

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");

return1;

}

 

if(close(fd) == -1)

{

perror("close");

return1;

}

free(buf);

return0;

}

#gcc –ot_rwt_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.com\n", 18)      = 18//read

lseek(3, 0, SEEK_SET)                  = 0//lseek

write(3, "WWW.PERFGEEKS.COM\n", 18)     = 18//write

close(3)                                = 0//close

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

mmap()vsmalloc()

使用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<stdio.h>

#include<string.h>

#include<stdlib.h>

 

int main(intargc, char *argv)

{

char *brk_mm, *mmap_mm;

 

printf("-----------------------\n");

brk_mm = (char *)malloc(100);

memset(brk_mm, '\0', 100);

mmap_mm = (char *)malloc(500 * 1024);

memset(mmap_mm, '\0', 500*1024);

free(brk_mm);

free(mmap_mm);

printf("-----------------------\n");

 

return1;

}

 

#gcc –ot_malloct_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()分配100bytes5kb的内存,可以看出其别调用了brk()mmap2(),相free()也是不回收内存和通munmap()回收内存。

mmap()共享内存,程通信

内存映射mmap()的另一个外常的用法是,程通信。相于管道、消息列方式而言,种通内存映射的方式效率明更高,它不需要任数据拷里,我一个例子来mmap()程通信方面的用。我们编写二个程序,分masterslaveslave根据master不同指令行不同的操作。Masterslave就是通映射同一个普通文件行通信的。

/*

 *@file master.c

 */

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

#include<stdio.h>

#include <time.h>

#include<stdlib.h>

#include<sys/mman.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<unistd.h>

 

void listen();

 

int main(intargc, char *argv[])

{

listen();

return0;

}

 

void listen()

{

intfd;

char *buf;

char *fname = "/tmp/shm_command";

 

char command;

time_tnow;

 

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("%ld\tgood 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<stdio.h>

#include<time.h>

#include<stdlib.h>

#include <sys/mman.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<unistd.h>

 

void ready(unsignedint t);

voidjob_hello();

voidjob_smile();

voidjob_bye();

charget_command(char *buf);

void wait();

 

int main(intargc, char *argv[])

{

wait();

return0;

}

 

void ready(unsignedint t)

{

sleep(t);

}

 

/* command 2*/

voidjob_hello()

{

time_tnow;

printf("%ld\thello world\n", (long)time(&now));

}

 

/* command 4*/

voidjob_simle()

{

time_tnow;

printf("%ld\t^_^\n", (long)time(&now));

}

 

/* command 6*/

voidjob_bye()

{

time_tnow;

printf("%ld\t|<--\n", (long)time(&now));

}

 

charget_command(char *buf)

{

char *p;

if(buf != NULL)

{

        p = buf;

}

else

{

return'0';

}

return *p;

}

 

void wait()

{

intfd;

char *buf;

char *fname = "/tmp/shm_command";

 

char command;

time_tnow;

 

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("%c\n",command);*/

switch(command)

{

case'0':

printf("%ld\tslave 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);

}

}

masterslave出如下
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 |<--

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

总结

1 Linux采用了投机取巧的分配策略,用到,才分配物理内存。也就是说进brk()mmap(),只是占用了虚地址空,并没有真正占用物理内存。也正是free–mused并不意味着消耗的全都是物理内存。
2
mmap()指定(flag)MAP_ANONYMOUS来表明映射是匿名内存映射,此可以忽略fd,可将它-1。如果不支持MAP_ANONYMOUS志的unix,可以映射至特殊设备文件/dev/zero实现匿名内存映射。
3
mmap()就决定了映射大小,不能再增加。话说,映射不能改文件的大小。反来,由文件被映射部分,而不是由文件大小来决定程可访问内存空(映射,指定offset最好是内存面大小的整数倍)
4
、通常使用mmap()的三种情况.提高I/O效率、匿名内存映射、共享内存程通信

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值