通過(guò)strace統(tǒng)計(jì)系統(tǒng)調(diào)用的時(shí)候,經(jīng)??梢钥吹絤map()與mmap2()。系統(tǒng)調(diào)用mmap()可以將某文件映射至內(nèi)存(進(jìn)程空間),如此可以把對(duì)文件的操作轉(zhuǎn)為對(duì)內(nèi)存的操作,以此避免更多的lseek()與read()、write()操作,這點(diǎn)對(duì)于大文件或者頻繁訪問(wèn)的文件而言尤其受益。但有一點(diǎn)必須清楚:mmap的addr與offset必須對(duì)齊一個(gè)內(nèi)存頁(yè)面大小的邊界,即內(nèi)存映射往往是頁(yè)面大小的整數(shù)倍,否則maaped_file_size%page_size內(nèi)存空間將被閑置浪費(fèi)。
演示一下,將文件/tmp/file_mmap中的字符轉(zhuǎn)成大寫(xiě),分別使用mmap與read/write二種方法實(shí)現(xiàn)。
/* * @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這里的內(nèi)存映射
雖然沒(méi)有看到read/write寫(xiě)文件操作,但此時(shí)文件/tmp/file_mmap中的內(nèi)容已由www.perfgeeks.com改變成了WWW.PERFGEEKS.COM .這里mmap的addr是0(NULL),offset是18,并不是一個(gè)內(nèi)存頁(yè)的整數(shù)倍,即有4078bytes(4kb-18)內(nèi)存空間被閑置浪費(fèi)了。
#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, 返回當(dāng)前中斷點(diǎn) brk(0x9866000) = 0x9866000 //malloc分配內(nèi)存,堆當(dāng)前最后地址 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
這里通過(guò)read()讀取文件內(nèi)容,toupper()后,調(diào)用write()寫(xiě)回文件。因?yàn)槲募。w現(xiàn)不出read()/write()的缺點(diǎn):頻繁訪問(wèn)大文件,需要多個(gè)lseek()來(lái)確定位置。每次編輯read()/write(),在物理內(nèi)存中的雙份數(shù)據(jù)。當(dāng)然,不可以忽略創(chuàng)建與維護(hù)mmap()數(shù)據(jù)結(jié)構(gòu)的成本。需要注意:并沒(méi)有具體測(cè)試mmap vs read/write,即不能一語(yǔ)斷言誰(shuí)孰誰(shuí)劣,具體應(yīng)用場(chǎng)景具體評(píng)測(cè)分析。你只是要記?。簃map內(nèi)存映射文件之后,操作內(nèi)存即是操作文件,可以省去不少系統(tǒng)內(nèi)核調(diào)用(lseek, read, write)。
mmap() vs malloc()
使用strace調(diào)試的時(shí)候,通??梢钥吹酵ㄟ^(guò)mmap()創(chuàng)建匿名內(nèi)存映射的身影。比如啟用dl(‘a(chǎn)pc.so’)的時(shí)候,就可以看到如下語(yǔ)句。
mmap2(NULL, 31457280, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0xb5ce7000 //30M
通常使用mmap()進(jìn)行匿名內(nèi)存映射,以此來(lái)獲取內(nèi)存,滿足一些特別需求。所謂匿名內(nèi)存映射,是指mmap()的時(shí)候,設(shè)置了一個(gè)特殊的標(biāo)志MAP_ANONYMOUS,且fd可以忽略(-1)。某些操作系統(tǒng)(像FreeBSD),不支持標(biāo)志MAP_ANONYMOUS,可以映射至設(shè)備文件/dev/zero來(lái)實(shí)現(xiàn)匿名內(nèi)存映射。使用mmap()分配內(nèi)存的好處是頁(yè)面已經(jīng)填滿了0,而malloc()分配內(nèi)存后,并沒(méi)有初始化,需要通過(guò)memset()初始化這塊內(nèi)存。另外,malloc()分配內(nèi)存的時(shí)候,可能調(diào)用brk(),也可能調(diào)用mmap2()。即分配一塊小型內(nèi)存(小于或等于128kb),malloc()會(huì)調(diào)用brk()調(diào)高斷點(diǎn),分配的內(nèi)存在堆區(qū)域,當(dāng)分配一塊大型內(nèi)存(大于128kb),malloc()會(huì)調(diào)用mmap2()分配一塊內(nèi)存,與堆無(wú)關(guān),在堆之外。同樣的,free()內(nèi)存映射方式分配的內(nèi)存之后,內(nèi)存馬上會(huì)被系統(tǒng)收回,free()堆中的一塊內(nèi)存,并不會(huì)馬上被系統(tǒng)回收,glibc會(huì)保留它以供下一次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
通過(guò)malloc()分別分配100bytes和5kb的內(nèi)存,可以看出其實(shí)分別調(diào)用了brk()和mmap2(),相應(yīng)的free()也是不回收內(nèi)存和通過(guò)munmap()系統(tǒng)回收內(nèi)存。
mmap()共享內(nèi)存,進(jìn)程通信
內(nèi)存映射mmap()的另一個(gè)外常見(jiàn)的用法是,進(jìn)程通信。相較于管道、消息隊(duì)列方式而言,這種通過(guò)內(nèi)存映射的方式效率明顯更高,它不需要任務(wù)數(shù)據(jù)拷貝。這里,我們通過(guò)一個(gè)例子來(lái)說(shuō)明mmap()在進(jìn)程通信方面的應(yīng)用。我們編寫(xiě)二個(gè)程序,分別是master和slave,slave根據(jù)master不同指令進(jìn)行不同的操作。Master與slave就是通過(guò)映射同一個(gè)普通文件進(jìn)行通信的。
/* *@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); } }
執(zhí)行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發(fā)出job指令2,4,6。slave收到指令后,執(zhí)行相關(guān)邏輯操作,完成后告訴master,master知道slave完成工作后,打印good job并且發(fā)送一下job指令。master與slave通信,是通過(guò)mmap()共享內(nèi)存實(shí)現(xiàn)的。
總結(jié)
1、 Linux采用了投機(jī)取巧的分配策略,用到時(shí),才分配物理內(nèi)存。也就是說(shuō)進(jìn)程調(diào)用brk()或mmap()時(shí),只是占用了虛擬地址空間,并沒(méi)有真正占用物理內(nèi)存。這也正是free –m中used并不意味著消耗的全都是物理內(nèi)存。
2、 mmap()通過(guò)指定標(biāo)志(flag) MAP_ANONYMOUS來(lái)表明該映射是匿名內(nèi)存映射,此時(shí)可以忽略fd,可將它設(shè)置為-1。如果不支持MAP_ANONYMOUS標(biāo)志的類unix系統(tǒng),可以映射至特殊設(shè)備文件/dev/zero實(shí)現(xiàn)匿名內(nèi)存映射。
3、 調(diào)用mmap()時(shí)就決定了映射大小,不能再增加。換句話說(shuō),映射不能改變文件的大小。反過(guò)來(lái),由文件被映射部分,而不是由文件大小來(lái)決定進(jìn)程可訪問(wèn)內(nèi)存空間范圍(映射時(shí),指定offset最好是內(nèi)存頁(yè)面大小的整數(shù)倍)。
4、通常使用mmap()的三種情況.提高I/O效率、匿名內(nèi)存映射、共享內(nèi)存進(jìn)程通信。
相關(guān)鏈接
1.高性能網(wǎng)絡(luò)編程
2.內(nèi)存管理內(nèi)幕
3.C語(yǔ)言指針與內(nèi)存泄漏
4.read系統(tǒng)調(diào)用剖析
5.linux環(huán)境進(jìn)程間通信:共享內(nèi)存
6. <<linux系統(tǒng)編程>> <<unix網(wǎng)絡(luò)編程2>></unix網(wǎng)絡(luò)編程2></linux系統(tǒng)編程>