虚存映射
我们知道,程序是存储在磁盘上到静态文件;进程是对程序到一次运行过程。在进程开始运行时,进程的代码和数据等内容必须装入到进程用户空间到适当区域。这些区域也就是所谓的代码段和数据段等,而被装入的数据和代码等内容被称为进程的可执行映像。从上面都描述中可以发现,进程在运行时并不是将程序一下子就装入到物理内存,而只是将程序装入到进程的用户空间,这个装入的过程称为虚存映射。
一个源程序在成为可执行文件的过程中会经历预处理、编译、汇编和链接四个阶段。因此,进程要成功运行不仅要在其用户空间装入进程映像,也要装入该进程所用到到函数库以及链接程序等。所以,一个进程到用户空间就被分为若干个内存区域。linux使用mm_struct结构来描述一个进程到用户地址空间,使用vm_area_struct结构来描述进程地址空间中的一个内存区域。因此,一个vm_area_struct结构可能代表进程到数据段,也可能代表链接程序到代码段等。
进程的虚存映射所做的只是将磁盘上到文件映射到该进程的用户地址空间,并没有建立虚拟内存到物理内存的映射。当某个可执行映像映射到进程用户空间并开始执行时,只有很少一部分虚拟页被装入了物理内存。在进程后续到执行过程中,如果需要访问到数据并不在物理内存中,则产生一个缺页中断(其实是异常),将所需页从交换区或磁盘中调入物理内存,这个过程即虚拟内存中到请页机制。
进程到虚存区
那么对于一个任意的进程,我们可以通过下面到方法查看其地址空间中到内存区域。
我们先看一个简单的测试程序:
#include < stdio.h > #include < stdlib.h > int main() { int i=1; char *str=NULL; printf("hello,world!\n"); str=(char *)malloc(sizeof(char)*1119); sleep(1000); return 0; }
这个程序中使用到了malloc函数,因此str变量存储于堆中。我们通过打印/proc/3530/maps文件,即可看到该进程的内存空间划分。其中3530是该进程的id。
edsionte@edsionte-desktop:~$ cat /proc/3530/maps 0014a000-00165000 r-xp 00000000 08:07 398276 /lib/ld-2.11.1.so 00165000-00166000 r--p 0001a000 08:07 398276 /lib/ld-2.11.1.so 00166000-00167000 rw-p 0001b000 08:07 398276 /lib/ld-2.11.1.so 001d8000-0032b000 r-xp 00000000 08:07 421931 /lib/tls/i686/cmov/libc-2.11.1.so 0032b000-0032c000 ---p 00153000 08:07 421931 /lib/tls/i686/cmov/libc-2.11.1.so 0032c000-0032e000 r--p 00153000 08:07 421931 /lib/tls/i686/cmov/libc-2.11.1.so 0032e000-0032f000 rw-p 00155000 08:07 421931 /lib/tls/i686/cmov/libc-2.11.1.so 0032f000-00332000 rw-p 00000000 00:00 0 00441000-00442000 r-xp 00000000 00:00 0 [vdso] 08048000-08049000 r-xp 00000000 08:09 326401 /home/edsionte/test 08049000-0804a000 r--p 00000000 08:09 326401 /home/edsionte/test 0804a000-0804b000 rw-p 00001000 08:09 326401 /home/edsionte/test 08958000-08979000 rw-p 00000000 00:00 0 [heap] b78ce000-b78cf000 rw-p 00000000 00:00 0 b78dd000-b78e0000 rw-p 00000000 00:00 0 bfa6a000-bfa7f000 rw-p 00000000 00:00 0 [stack]
每一行信息依次显示的内容为内存区域其实地址-终止地址,访问权限,偏移量,主设备号:次设备号,inode,文件。
上面的信息不但包含了test可执行对象的各内存区域,而且还分别显示了 /lib/ld-2.11.1.so(动态连接程序)文件和/lib/tls/i686/cmov/libc-2.11.1.so(C库)文件的内存区域信息。
我们从某个内存区域的访问权限上可以大致判断该区域的类型。各个属性符号的意义为:r-read,w-write,x-execute,s-shared,p-private。因此,r-x一般代表程序的代码段,即可读,可执行。rw-可能代表数据段,BSS段和堆栈段等,即可读,可写。堆栈段从行信息的文件名就可以区分;如果某行信息的文件名为空,那么可能是BSS段。另外,上述test进程共享了内核动态库,所以在00441000-00442000行处文件名显示为vdso(Virtual Dynamic Shared Object)。
mmap系统调用
通过mmap系统调用可以在进程到用户空间中创建一个新到虚存区。该系统调用到原型如下:
#include void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
该函数可以将以打开的文件映射到进程用户空间到一片内存区上,执行成功后,该函数返回这段映射区到首地址。用户得到这片虚存的首地址后,就可以像访问内存那样访问文件。
该系统调用的参数说明如下:
addr:映射到用户地址空间到起始地址;
length:映射区以字节为单位到长度;
prot:对映射区到访问模式。包括PROT_EXEC(可执行),PROT_READ (可读),PROT_WRITE(可写),PROT_NONE(文件不可访问)。这个访问模式不能超过所映射文件到打开模式。比如被映射的文件打开模式为只读,那么此处到访问模式不能是可读写的。
flags:这个字段比较灵活,不同到标志有不同的功能,具体如下:
MAP_SHARED:创建一个可被子进程共享的映射区;
MAP_PRIVATE:创建一个“写实复制”的映射区;
MAP_ANONYMOUS:创建一个匿名到映射区,该虚存区与进程无关;
fd:所要映射到进程用户空间的文件描述符,该文件必须为以打开的文件;
offset:文件的起始映射偏移量;
mmap()举例
在该程序中,首先以只读方式打开文件test.c,再通过该文件返回到文件描述符和mmap函数将test.c文件映射到当前进程到用户地址空间中。成功执行mmap函数后,buf被赋值为所映射的虚存区的首地址。注意,mmap函数返回的是void型指针,而buf是char型指针。将mmap返回值赋值给buf变量时,自动将void*转化为char*型。
最后,就像平常我们使用一个char型指针变量那样,依次打印出buf中到数据。
#include < stdio.h > #include < sys/mman.h > #include < fcntl.h > int main() { int i,fd; char *buf = NULL; fd = open("./test.c", O_RDONLY); if(fd < 0) { printf("open error\n"); return -1; } buf = mmap(NULL, 12, PROT_READ, MAP_PRIVATE ,fd, 0); for(i = 0;i < 12;i++) { printf("%c",buf[i]); } printf("\n"); return 0; }
try一下!