1 / 94

Solaris 操作系统实验 - 内存管理

Solaris 操作系统实验 - 内存管理. Outline. 内存管理 概述 虚拟内存 匿名内存 物理内存. 虚存管理中涉及到的实体. 虚拟内存 程序员看到的地址空间,还没有被 MMU 转换 通常大于实际的物理内存 物理内存 由物理地址或实地址应用 系统中实际安装的内存 文件 磁盘上的数据. 虚拟内存与物理内存的关系. 虚拟地址通过页表映射到物理地址 进程地址空间 一个进程可以访问的所有虚拟地址集合 每个地址空间有一个页表. 虚拟内存与文件的关系. 虚地址空间包含到不同的映射 每个映射是一个地址范围,用一个段( seg )表示

chaeli
Download Presentation

Solaris 操作系统实验 - 内存管理

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Solaris 操作系统实验- 内存管理

  2. Outline • 内存管理概述 • 虚拟内存 • 匿名内存 • 物理内存

  3. 虚存管理中涉及到的实体 • 虚拟内存 • 程序员看到的地址空间,还没有被MMU转换 • 通常大于实际的物理内存 • 物理内存 • 由物理地址或实地址应用 • 系统中实际安装的内存 • 文件 • 磁盘上的数据

  4. 虚拟内存与物理内存的关系 • 虚拟地址通过页表映射到物理地址 • 进程地址空间 • 一个进程可以访问的所有虚拟地址集合 • 每个地址空间有一个页表

  5. 虚拟内存与文件的关系 • 虚地址空间包含到不同的映射 • 每个映射是一个地址范围,用一个段(seg)表示 • 每个段把它的虚地址映射到文件或设备

  6. 物理内存与文件的关系 • 每个物理页面与文件或设备中的一块相对应 • 可能页面的内容来自文件 • 可能页面在换出时需要写入文件

  7. 全部关系

  8. Outline • 内存管理概述 • 虚拟内存 • 匿名内存 • 物理内存

  9. 虚拟内存 • 虚拟内存 • 观察进程和内核的地址空间 • 文件映射 • 页故障

  10. 简介 • 目的 • 了解进程和内核的地址空间布局 • 主要步骤 • 编写一个程序,声明不同类型的变量,观察这些变量位于什么段中 • 观察堆段的创建 • 观察内核的地址空间 • 知识点 • 进程地址空间的布局 • 内核地址空间的布局 • 数据段的映射 • 堆段的创建

  11. 地址空间 • 每个地址空间中的虚存被分成连续的段,用seg数据结构表示

  12. 进程中最常用的段: seg_vn 段

  13. 段相关的数据结构

  14. x86平台进程的地址空间

  15. 源程序 - test.c #include <stdio.h> #include <stdlib.h> // 已初始化全局变量 int var1 = 1; char str[] = "Global!"; // 未初始化全局变量 int var2; char *buffer; int main() { // 局部变量 int var3; buffer = (char*)malloc(4096); // 动态分配的内存 printf("Address:\n"); printf("\tvar1:\t%p\n", &var1); printf("\tvar2:\t%p\n", &var2); printf("\tvar3:\t%p\n", &var3); printf("\tstr:\t%p\n", str); printf("\tbuffer:\t%p\n", buffer); getchar(); free((void*)buffer); return 0; }

  16. 查看不同变量所在的段 - 32位x86平台 • 运行test -bash-3.00$ ./test Address: var1: 8060ba4 // 已初始化全局变量 var2: 8060bb0 // 未初始化全局变量 var3: 8047cc4 // 局部变量 str: 8060ba8 // 已初始化全局变量 buffer: 8060f88 // 动态分配的内存 • 观察test的地址空间,并比较不同的变量落入什么段中 -bash-3.00$ pmap `pgrep test` 17785: ./test 08046000 8K rwx-- [ stack ] // 栈段 08050000 4K r-x-- /home/user1/source/test // 代码段 08060000 4K rwx-- /home/user1/source/test // 数据段 08061000 8K rwx-- [ heap ] // 堆段 FEEA0000 24K rwx-- [ anon ] FEEB0000 896K r-x-- /lib/libc.so.1 …… total 1284K 动态分配的内存一部分在数据段中,一部分在堆中,为什么?

  17. 数据段与堆段的映射

  18. 观察ELF文件信息 • 观察程序头: 程序头定义了执行程序时映射进内存的部分,包括映射的虚地址、长度、对齐边界等 -bash-3.00$ elfdump -p test ………… 程序头[3]: p_vaddr: 0x8050000 p_flags: [ PF_X PF_R ] p_paddr: 0 p_type: [ PT_LOAD ] p_filesz: 0xa55 p_memsz: 0xa55 p_offset: 0 p_align: 0x10000 程序头[4]: p_vaddr: 0x8060a58 p_flags: [ PF_X PF_W PF_R ] p_paddr: 0 p_type: [ PT_LOAD ] p_filesz: 0x158 p_memsz: 0x524 p_offset: 0xa58 p_align: 0x10000 ………… • 观察章节头: -bash-3.00$ elfdump -c test ………… 章节头[19]: sh_name: .data sh_addr: 0x8060b58 sh_flags: [ SHF_WRITE SHF_ALLOC ] sh_size: 0x58 sh_type: [ SHT_PROGBITS ] sh_offset: 0xb58 sh_entsize: 0 sh_link: 0 sh_info: 0 sh_addralign: 0x4 ………… 章节头[21]: sh_name: .bss sh_addr: 0x8060bb0 sh_flags: [ SHF_WRITE SHF_ALLOC ] sh_size: 0x3cc sh_type: [ SHT_NOBITS ] sh_offset: 0xbb0 sh_entsize: 0 sh_link: 0 sh_info: 0 sh_addralign: 0x8 ………… 映射的虚地址 访问权限 对齐边界 内存长度:内存中实际占的长度,包括已初始化全局变量和未初始化全局变量 文件长度:ELF文件中只需要存放已初始化变量的初始值,所以这个长度等于已初始化变量的长度 章节虚地址 章节长度

  19. 查看不同变量所在的段 - 64位x86平台 • 编译并运行64位程序 -bash-3.00$ cc -o test64 -xarch=amd64 test.c -bash-3.00$ ./test64 Address: var1: 410fa8 var2: 410fc0 var3: fffffd7fffdffbf8 str: 410fb0 buffer: 4119f0 • 查看64位程序的地址空间 -bash-3.00$ pmap `pgrep test64` 27493: ./test64 0000000000400000 4K r-x-- /home/user1/source/test64 0000000000410000 4K rw--- /home/user1/source/test64 0000000000411000 20K rw--- [ heap ] FFFFFD7FFF1B0000 4K rwx-- [ anon ] …… FFFFFD7FFF3FC000 8K rwx-- /lib/amd64/ld.so.1 FFFFFD7FFFDFE000 8K rw--- [ stack ] total 2088K 64位进程中,堆位于下方,栈位于顶端。

  20. 观察test中堆段的创建 • test刚启动时没有堆段 • 整个.bss段位于数据段中,brk的起始地址也位于数据段中 • 堆段在调用malloc时创建 • 步骤 • 使用segvn_create.d跟踪test中的段创建 • 单步运行test,看segvn_create.d何时捕获到堆栈的创建。

  21. segvn_create.d脚本 #!/usr/sbin/dtrace -s #pragma D option quiet /* segvn_create的函数原型为: int segvn_create(struct seg *seg, void *argsp) */ self struct segvn_crargs *ap; /* 跟踪segvn_create函数的调用,可以指定跟踪什么程序,以及跟踪在什么地址范围内的段创建 */ fbt::segvn_create:entry / execname == $$1 && (long)args[0]->s_base >= $2 && (long)args[0]->s_base < $3 / { self->ap = (struct segvn_crargs*)args[1]; printf("\nUser space stack:"); ustack(); printf("\nKernel space stack:"); stack(); printf("\nexecname:%s\tpid:%d\tbase:%x\n", execname, pid, (long)args[0]->s_base); printf("\tvp:\t%X\n\toffset:\t%X\n\ttype:\t%d\n\tflags:\t%X\n\n", (long)self->ap->vp, self->ap->offset, self->ap->type, self->ap->flags); } 调用segvn_create的程序的名称 跟踪哪个地址范围内的段创建

  22. 观察test刚启动时的地址空间 • 启动dtrace脚本 # ./segvn_create.d test 0x8061000 0x10000000 • 用mdb运行程序 -bash-3.00$ mdb test > main::bp > ::run mdb: stop at main mdb: target stopped at: main: pushl %ebp • 观察test的地址空间,此时还没有堆段存在 > ::mappings BASE LIMIT SIZE NAME 8046000 8048000 2000 [ stack ] 8050000 8051000 1000 /home/user1/source/test 8060000 8061000 1000 /home/user1/source/test feea0000 feea6000 6000 [ anon ] ……

  23. 捕获seg_vn段的创建 • 单步运行test > :e mdb: target stopped at: main+0x14: call -0x185 <PLT:malloc> > :e mdb: target stopped at: main+0x19: addl $0x4,%esp • 此时segvn_create.d脚本跟踪到堆段的创建,脚本的输出为: User space stack: libc.so.1`_brk_unlocked+0x15 libc.so.1`sbrk+0x2c libc.so.1`_morecore+0xf9 libc.so.1`_malloc_unlocked+0x164 libc.so.1`malloc+0x37 test`main+0x19 test`_start+0x7a Kernel space stack: genunix`as_map_locked+0x15c genunix`as_map+0x53 genunix`brk_internal+0x2aa genunix`brk+0x62 unix`_sys_sysenter_post_swapgs+0x14b execname:test pid:18185 base:8061000 vp: 0 offset: 0 type: 2 flags: 0 执行完malloc之后脚本就会有输出 程序调用malloc分配内存,malloc调用brk完成栈段的创建和扩展

  24. 观察堆段创建完后的地址空间 > ::mappings BASE LIMIT SIZE NAME 8046000 8048000 2000 [ stack ] 8050000 8051000 1000 /home/user1/source/test 8060000 8061000 1000 /home/user1/source/test 8061000 8063000 2000 [ heap ] feea0000 feea6000 6000 [ anon ] …… 执行完malloc函数后进程的地址空间中就多了一个堆段

  25. 查看内核的地址空间 • 在“mdb -k”中用mappings命令观察 64位平台 > ::mappings BASE LIMIT SIZE NAME fffffe0000000000 fffffe001f687000 1f687000 kpmseg fffffe8000000000 fffffe801f000000 1f000000 kpseg fffffe805f000000 fffffe8063000000 4000000 kmapseg fffffe8063000000 ffffffffc0000000 17f5d000000 kvseg ffffffffc0000000 fffffffffa7fe000 3a7fe000 kvseg_core fffffffffa800000 fffffffffb800000 1000000 kvalloc fffffffffb800000 fffffffffbd15000 515000 ktextseg ffffffffff800000 ffffffffffc00000 400000 kdebugseg 32位平台 > ::mappings BASE LIMIT SIZE NAME d1802000 d2800000 ffe000 kmapseg d2800000 f93fe000 26bfe000 kvseg fd400000 fe800000 1400000 kvalloc fe800000 fecd1000 4d1000 ktextseg ff800000 ffc00000 400000 kdebugseg

  26. 32位x86平台内核地址空间

  27. 64-Bit x86 Kernel Address Space Layout kernel heap Pageable kernel memory Mapping physical memory

  28. 虚拟内存 • 虚拟内存 • 观察进程和内核的地址空间 • 文件映射 • 页故障

  29. 简介 • 目的 • 了解Solaris中的文件映射I/O • 主要步骤 • 编写一段代码,调用mmap函数映射一个文件 • 在执行mmap函数前后分别观察进程的地址空间,看有什么不同 • 观察文件映射段中页面的内容 • 观察多个进程映射同一个文件时物理页面的共享 • 知识点 • mmap系统调用 • 文件映射的实现方式 • 在文件映射中的页面共享

  30. 内存文件映射

  31. 参考源代码 - mmaptest.c #include <stdio.h> …… void *addr = (void*)0x10000000, *map_addr; int main(int argc, char** argv) { int fildes, len; struct stat statinfo; char c, buffer[256]; fildes = open(argv[1], O_RDWR); fstat(fildes, &statinfo)); len = statinfo.st_size; gets(buffer); if ((map_addr = mmap(addr, len, PROT_READ, MAP_SHARED | MAP_FIXED, fildes, 0)) == MAP_FAILED) printf("Map failed!\n"); else { // 访问映射段中每一个虚页,使得内核为这些虚页分配物理页面,并从文件中读入内存 for (int i = 0; i < len; i += 4096) c = ((char*)map_addr)[i]; ………… munmap(map_addr, statinfo.st_size); } …… } 映射之前要打开文件 调用mmap完成映射 程序退出之前解除映射

  32. 查看mmaptest的地址空间 单步运行mmaptest,在mmap调用之前分别用pmap命令观察mmaptest的进程地址空间 • 查看执行mmap之前的地址空间 -bash-3.00$ pmap `pgrep mmaptest` 18587: ./mmaptest test.txt 08046000 8K rwx-- [ stack ] 08050000 4K r-x-- /home/user1/source/mmaptest 08060000 4K rwx-- /home/user1/source/mmaptest 08061000 4K rwx-- [ heap ] FEEA0000 24K rwx-- [ anon ] …… • 查看执行mmap之后的地址空间 -bash-3.00$ pmap `pgrep mmaptest` 18587: ./mmaptest test.txt 08046000 8K rwx-- [ stack ] 08050000 4K r-x-- /home/user1/source/mmaptest 08060000 4K rwx-- /home/user1/source/mmaptest 08061000 4K rwx-- [ heap ] 10000000 4K r--s- dev:102,7 ino:12273 FEEA0000 24K rwx-- [ anon ] …… mmap在地址空间中新增加了一个段

  33. 查看新增段对应的文件及其页面内容 • 查看新增加的段对应的文件 > ::pgrep mmaptest S PID PPID PGID SID UID FLAGS ADDR NAME R 18587 18231 18587 18231 100 0x4a004000 ffffffff82067690 mmaptest > ffffffff82067690::print struct proc p_as p_as = 0xffffffff82dfd2a0 > 0xffffffff82dfd2a0::walk seg | ::seg ! grep 10000000 ffffffff8378ea68 10000000 1000 ffffffff95245b18 segvn_ops > ffffffff8378ea68::print struct seg s_data s_data = 0xffffffff95245b18 > 0xffffffff95245b18::print struct segvn_data vp | ::vnode2path /home/user1/source/test.txt • 查看新增加的段中页面的内容 > 10000000::vtop -a 0xffffffff82dfd2a0 virtual 10000000 mapped to physical 1222b000 > 1222b000\s 0x1222b000: abcde 获取进程的proc结构地址 获取as结构的地址 遍历各个段,找出起始地址为10000000的段 对于seg_vn类型的段,s_data字段这个值指向一个segvn_data结构 映射的文件名为test.txt,与打开的文件相同 把虚地址10000000转换成物理地址 查看物理页面的内容,这个内容与文件内容一致

  34. 共享映射文件

  35. 共享映射文件 • 启动两个mmaptest进程,并且让它们映射相同的文件 • 然后查看这两个进程中文件映射段对应的物理页面 > ::pgrep mmaptest S PID PPID PGID SID UID FLAGS ADDR NAME R 18598 18231 18598 18231 100 0x4a004000 ffffffff820682e0 mmaptest R 18597 18231 18597 18231 100 0x4a004000 ffffffff82067690 mmaptest 查看0x10000000在第一个进程中对应的物理内存 > ffffffff820682e0::print struct proc p_as p_as = 0xffffffff82dfd2a0 > 10000000::vtop -a 0xffffffff82dfd2a0 virtual 10000000 mapped to physical 1222b000 查看0x10000000在第二个进程中对应的物理内存 > ffffffff82067690::print struct proc p_as p_as = 0xffffffff834342a8 > 10000000::vtop -a 0xffffffff834342a8 virtual 10000000 mapped to physical 1222b000 两个进程使用的物理页面是相同的

  36. 虚拟内存 • 虚拟内存 • 观察进程和内核的地址空间 • 文件映射 • 页故障

  37. 简介 • 目的 • 了解Solaris中的页故障类型以及页故障处理方式 • 主要步骤 • 构造三个程序,来引发三种不同类型的页故障 • 用DTrace脚本跟踪页故障 • 观察不同类型的段上的页故障,以及未映射页故障 • 知识点 • 三种页故障类型 • 严重页故障(Major Page Fault) • 轻度页故障(Minor Page Fault) • 保护性页故障(Protection Page Fault) • 不同段上的页故障处理

  38. 三种类型的页故障

  39. 页故障处理流程

  40. DTrace脚本 - pagefault.d #!/usr/sbin/dtrace -s #pragma D option flowindent // 跟踪pagefault函数,每次发生页故障时,系统就会调用这个函数 fbt::pagefault:entry / execname == $$1 && (unsigned long)args[0] >= $2 && (unsigned long)args[0] < $3 / { stack(); printf("\nexecname:%s pid:%d addr:%x", execname, pid, (long)args[0]); self->start = 1; } fbt::pagefault:return / self->start == 1 / { // 显示pagefault函数的返回值,4(FC_PROTFC)表示越权操作,FC_NOMAP表示未映射 printf("\nReturn Addr:%x, Return Value:%d", arg0, arg1); exit(0); } fbt::: / self->start == 1 / { }

  41. 严重页故障 • 基本思路 • 程序在调用malloc操作的时候只是分配虚拟内存 • 当第一个访问分配的虚拟内存时,会引发页故障,这时系统才真正分配物理页面 • 分配的物理页面是新创建的物理页面,且会被清零 • 这次页故障就是一次严重页故障

  42. 严重页故障 - major.c • major.c源码 #include <stdlib.h> char* ptr; int main() { ptr = (char*)malloc(2 * 4096); // 这条指令将会引发一次页故障,导致系统为堆上的虚地址分配新的物理页 ptr[4095] = '\0'; free((void*)ptr); } • 运行pagefault.d,监视major的页故障,监视地址范围为0x8061000到0x8062000,包含了ptr[4095]这个位置 # ./pagefault.d major 0x8061000 0x8062000 dtrace: script './pagefault.d' matched 49260 probes

  43. 严重页故障 -地址映射的变化 • 在mdb中单步运行major -bash-3.00$ mdb major > main::bp > :r • ptr[4095]被访问之前虚地址的映射 > ::pgrep major S PID PPID PGID SID UID FLAGS ADDR NAME R 19063 19062 19062 18231 100 0x4a004000 ffffffffa50df8f8 major > ffffffffa50df8f8::print struct proc p_as p_as = 0xffffffff83434b68 > 8061997::vtop -a 0xffffffff83434b68 mdb: failed to get physical mapping: no mapping for address • ptr[4095]被访问之后虚地址的映射 > 8061997::vtop -a 0xffffffff83434b68 virtual 8061997 mapped to physical fc97997 系统为ptr[4095]所在的虚拟页面分配了物理页面

  44. 严重页故障 -处理过程 pagefault.d的输出 CPU FUNCTION 0 -> pagefault unix`trap+0xb6c unix`_cmntrap+0x140 execname:major pid:19063 addr:8061997 0 | pagefault:entry 0 -> segkp_map_red 0 <- segkp_map_red 0 -> as_fault 0 -> as_segat 0 <- as_segat 0 -> segvn_fault …… 0 <- segvn_fault 0 <- as_fault 0 <- pagefault Return Addr:218, Return Value:0

  45. 轻度页故障 • 基本思路 • 创建一个文件,系统会为这个文件分配页面 • 创建过程退出后,新页面会被放入文件缓存中 • 用DTrace脚本捕获新创建的页面的地址 • 使用前面的mmaptest程序再次访问这个文件,由于页面仍在文件缓存中,这时就会引发一次轻度页故障 • 验证:比较mmaptest中的页面的地址是否与前面捕获的页面一样

  46. 轻度页故障 -监视页面的创建 • page_create.d源码 #!/usr/sbin/dtrace -s #pragma D option quiet fbt::page_create_va:return, fbt::page_lookup_create:return / arg1 != 0 && ((struct page*)arg1)->p_vnode && ((struct page*)arg1)->p_vnode->v_path == $$1 / { printf("%-15s %-20s page:%x\n", execname, probefunc, arg1); } • 监视与某个文件相关的页面的创建 # ./page_create.d /home/user1/source/test.txt • 创建test.txt -bash-3.00$ echo "abcde" > test.txt • page_create.d的输出为 bash page_create_va page:fffffffffac18530

  47. 轻度页故障 - 监视页故障 • 用pagefault.d监视前面编译好的mmaptest • 运行mmaptest,访问新创建的test.txt • 观察pagefault.d的输出 • 查看页故障后新分配的物理页面的page结构地址,看它是否与刚才被缓存的页面一致 > 0x10000000::vtop -a 0xffffffff95238e00 virtual 10000000 mapped to physical 8b5b000 > 8b5b::page_num2pp 8b5b has page at fffffffffac18530

  48. 保护性页故障 • 基本思路 • 让程序对代码段执行写操作 • protect.c源码。protect.c中执行了往代码段写的操作。 int main() { ((char*)main)[0] = '\0'; return 0; }

  49. 保护性页故障 运行时发生段错误 -bash-3.00$ ./protect 段错误 (core dumped) pagefault.d的输出 CPU FUNCTION …… execname:protect pid:18914 addr:8050740 1 | pagefault:entry 1 -> segkp_map_red 1 <- segkp_map_red 1 -> as_fault …… 1 <- as_fault 1 <- pagefault Return Addr:218, Return Value:4 返回值4(FC_PROT)表示越权操作

  50. 不同段类型上的页故障 seg_fault.d源码,这个脚本用来统计不同段类型上的页故障次数。 #!/usr/sbin/dtrace -s #pragma D option quiet fbt::segvn_fault:entry, fbt::segmap_fault:entry, fbt::segdev_fault:entry, fbt::segkmem_fault:entry, fbt::segkpm_fault:entry, fbt::segkp_fault:entry { @[probefunc] = count(); } nomaptest.c源码。这个程序访问未映射区域,导致程序发生段错误。 int main() { char* p = (char*)0x10000000; p[0] = '\0'; return 0; } • 用户进程的段基本上都是seg_vn类型的段 • 其他具有页故障处理函数的段类型还有seg_map、seg_kpm、seg_kp和seg_dev等 • 如果一个程序访问了一个不属于任何段的虚地址,就会发生一次未映射错误

More Related