solaris 操作系统实验 - 内存管理
Post on 10-Feb-2016
139 Views
Preview:
DESCRIPTION
TRANSCRIPT
1
Solaris Solaris 操作系统实验操作系统实验- 内存管理- 内存管理
2
Outline 内存管理内存管理概述 虚拟内存 匿名内存 物理内存
3
虚存管理中涉及到的实体虚拟内存
程序员看到的地址空间,还没有被 MMU 转换通常大于实际的物理内存
物理内存由物理地址或实地址应用系统中实际安装的内存
文件磁盘上的数据
4
虚拟内存与物理内存的关系虚拟地址通过页表映射到物理地址进程地址空间
一个进程可以访问的所有虚拟地址集合 每个地址空间有一个页表
Page Table
Virtual Memory
MMU
Physical Memory
5
虚拟内存与文件的关系虚地址空间包含到不同的映射
每个映射是一个地址范围,用一个段( seg )表示每个段把它的虚地址映射到文件或设备Virtual
Memory
File Swap Device
Seg Seg
6
物理内存与文件的关系每个物理页面与文件或设备中的一块相对应
可能页面的内容来自文件可能页面在换出时需要写入文件
Physical Memory
File Swap Device
7
全部关系Virtual
Memory
File Swap Device
Seg Seg
Physical Memory
Page TableMMU
8
Outline 内存管理内存管理概述 虚拟内存 匿名内存 物理内存
9
虚拟内存虚拟内存
观察进程和内核的地址空间文件映射页故障
10
简介目的
了解进程和内核的地址空间布局主要步骤
编写一个程序,声明不同类型的变量,观察这些变量位于什么段中 观察堆段的创建 观察内核的地址空间
知识点 进程地址空间的布局 内核地址空间的布局 数据段的映射 堆段的创建
11
地址空间每个地址空间中的虚存被分成连续的段,用seg 数据结构表示
12
进程中最常用的段 : seg_vn 段
13
段相关的数据结构struct proc
p_as
struct asa_tree AVL Tree
struct segs_datastruct
segvn_datavp
offsetampindexcred
vpage
struct vnodeFILE
struct segs_data
struct segs_data
14
x86 平台进程的地址空间256-MB Kernel
Libraries
Heapmalloc(), sbrk()
Executable Data
Executable Text
0xFFFFFFFF
0xE0000000
0x0
Stack
Libraries
Heapmalloc(), sbrk()
Executable Data
Executable Text
0xFFFFFD7F-FFDFC000
0xFFFFFD7F-FF3FC000
0x00000000-00400000
32-bit x86 64-bit x86
Stack
0x80480000
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$ ./testAddress: var1: 8060ba4 // 已初始化全局变量 var2: 8060bb0 // 未初始化全局变量 var3: 8047cc4 // 局部变量 str: 8060ba8 // 已初始化全局变量 buffer: 8060f88 // 动态分配的内存 观察 test 的地址空间,并比较不同的变量落入什么段中-bash-3.00$ pmap `pgrep test`17785: ./test08046000 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
数据段与堆段的映射
PageBoundary
p_brkbaseData
Segment
HeapSegment
PageBoundary
p_brkbase
DataSegment
HeapSegment
PageBoundary
PageBoundary
.data.data
.bss.bss
a b
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$ ./test64Address: var1: 410fa8 var2: 410fc0 var3: fffffd7fffdffbf8 str: 410fb0 buffer: 4119f0
查看 64 位程序的地址空间-bash-3.00$ pmap `pgrep test64`27493: ./test640000000000400000 4K r-x-- /home/user1/source/test640000000000410000 4K rw--- /home/user1/source/test640000000000411000 20K rw--- [ heap ]FFFFFD7FFF1B0000 4K rwx-- [ anon ]……FFFFFD7FFF3FC000 8K rwx-- /lib/amd64/ld.so.1FFFFFD7FFFDFE000 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> ::runmdb: stop at mainmdb: 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/testfeea0000 feea6000 6000 [ anon ]……
23
捕获 seg_vn 段的创建 单步运行 test> :emdb: target stopped at:main+0x14: call -0x185 <PLT:malloc>> :emdb: 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+0x7aKernel space stack: genunix`as_map_locked+0x15c genunix`as_map+0x53 genunix`brk_internal+0x2aa genunix`brk+0x62 unix`_sys_sysenter_post_swapgs+0x14bexecname: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 NAMEd1802000 d2800000 ffe000 kmapsegd2800000 f93fe000 26bfe000 kvsegfd400000 fe800000 1400000 kvallocfe800000 fecd1000 4d1000 ktextsegff800000 ffc00000 400000 kdebugseg
26
32 位x86 平台内核地址空间
kmapseg
kvseg
kvalloc
ktextseg
kdebugseg
segkmem, 4.8M
segkmem, valloc_sz (0x1400000, 20M)
segkmem, 620M
segmap, segmapsize, 16M
0xd27f fff f
0xd1802000 (segkmap_start = kernel _base + KERNEL_REDZONE_SI ZE)
0xd2800000 (fi nal _kernel heap = segkmap_start + segmapsi ze)
0xf93fdff f
0xfd400000 (val l oc_base = KERNEL_TEXT – val l oc_sz)
0xfe7ff ff f0xfe800000 (s_text, KERNEL_TEXT)
0xfecd0ff f (e_moddata)
0xff 800000
segkmem, 4M
0xff bff ff f
4M
11M
Unused
Unused
0xff fff ff f
ptable0xfd3ff ff f
0xfd3fe000 (ptabl e_va = val l oc_base – ptabl e_sz)ptable_sz (0x2000, 8K)
ktextseg
text
modtext
data
moddata
0xfe800000 s_text
0xfe9a3e70 e_text0xfe9a4000 modtext
0xfeba4000 e_modtext
0xfec85737 e_data0xfec86000 moddata
0xfecd1000 e_moddata
0xfec00000 s_data
0xd1800000 (kernel base = KERNELBASE – ROUND_UP_4MEG(2 * val l oc_sz))red zone KERNEL_REDZONE_SIZE, 8K
27
kmapseg
kvseg
kvalloc
ktextseg
kdebugseg
segkmem, 5M
segkmem, valloc_sz, 16M
segkmem, 1533.5G
segkp, segkpsize * pagesize, 496M
0xff ff fe801eff ff ff
0xff ff fe0000000000 (kpm_vbase = kernel base + KERNEL_REDZONE_SI ZE)
0xff ff fe805f000000 (segkmap_start = ROUND_UP_LPAGE(toxi c_addr + toxi c_si ze))
0xff ff ff ff bff ff ff f0xff ff ff ff c0000000 (core_base, COREHEAP_BASE)
0xff ff ff ff fa800000 (val l oc_base = KERNEL_TEXT – val l oc_sz)
0xff ff ff ff fb7ff ff f0xff ff ff ff fb800000 (s_text, KERNEL_TEXT)
0xff ff ff ff fbd0dff f
segkmem, 4M0xff ff ff ff ff 800000 (SEGDEBUGBASE)
4M
59M
toxic_size, 1G
Unused
Unused
0xff ff ff ff ff bff ff f
kpseg
kpmseg
kvseg_core
0xff ff fe001f685ff f
segkpm, kpm_size = physmax * pagesize, 502.5M
Unused 511.5G
0xff ff fe8000000000 (segkp_base = (ROUND_UP_TOPLEVEL(kpm_vbase + kpm_si ze))
0xff ff fe8062ff ff ff
segmap, 64M
0xff ff fe8063000000 (fi nal _kernel heap = segkmap_start + segmapsi ze)
0xff ff ff ff fa7fdfff
segkmem, core_size = ptable_va – core_base, 936M
0xff ff ff ff fbd0e000 (e_moddata)
red zone0xff ff fd8000000000 (kernel base, KERNELBASE)
KERNEL_REDZONE_SIZE (1<<39, 512G)
ptable0xff ff ff ff fa7fe000 (ptabl e_va = val l oc_base – ptabl e_sz)
0xff ff ff ff fa7ff ff fptable_sz (0x2000, 8K)
ktextseg
text
modtext
data
moddata
0xff ff ff ff fb800000 s_text
0xff ff ff ff fba7f1a0 e_text0xff ff ff ff fba80000 modtext
0xff ff ff ff fbc00000 e_modtext, s_data
0xff ff ff ff fbcc2373 e_data0xff ff ff ff fbcc3000 moddata
0xff ff ff ff fbd0e000 e_moddata
0xff ff fe801f000000 (toxi c_addr = segkp_base + segkpsi ze * pagesi ze)
device mappings0xff ff fe805eff ff ff
64-Bit x86 Kernel Address Space Layout
Mapping physical memory
Pageable kernel
memory
kernel heap
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.txt08046000 8K rwx-- [ stack ]08050000 4K r-x-- /home/user1/source/mmaptest08060000 4K rwx-- /home/user1/source/mmaptest08061000 4K rwx-- [ heap ]FEEA0000 24K rwx-- [ anon ]……
查看执行 mmap 之后的地址空间-bash-3.00$ pmap `pgrep mmaptest`18587: ./mmaptest test.txt08046000 8K rwx-- [ stack ]08050000 4K r-x-- /home/user1/source/mmaptest08060000 4K rwx-- /home/user1/source/mmaptest08061000 4K rwx-- [ heap ]10000000 4K r--s- dev:102,7 ino:12273FEEA0000 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
三种类型的页故障
Invalid
Read OnlyInvalid
PageTables
Write
MajorFault
MinorFault
ProtectionFault
I/O
Page Fault Handler
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 0x8062000dtrace: 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等
●如果一个程序访问了一个不属于任何段的虚地址,就会发生一次未映射错误
51
Outline 内存管理内存管理概述 虚拟内存 匿名内存 物理内存
52
匿名内存指向不直接与 vnode 关联的页面
进程堆空间 进程的栈 copy-on-write 产生的页面
实现匿名内存的两个子系统 Anon layer >通过这个层提供的接口创建和管理匿名内存页
swapfs file system>作为匿名内存页面的后备存储>当内存不足时,把匿名内存页面拷贝到交换文件系统
53
匿名内存数据结构 第一次页故障时
如果需要的话,分配anon map 结构
segvn_data 中的ahp 指向 anon header
每个 anon 单元指向一个 anon 结构 指示这个页面在后备存储中的位置 初始化为交换文件系统中的 vnode 和 offset
struct proc
p_as
struct asa_tree AVL Tree
struct segs_data
structsegvn_data
vpoffsetamp
indexcred
vpage
struct cred
struct vpage[]
struct vpagenvp_prot
nvp_advise
structanon_map
size(bytes)ahp
structanon_hdr
size(slots)array_chunk
struct vnode
void* []
struct anon* []
struct vnode
struct anon
an_offan_vp
an_pvpan_poff
struct vnode
swap space
double indirection
single indirection
SWAPFS
ANONLAYER
MAPPEDFILE
PER-PAGEPROTECTION
& ADVICE
54
计算 anon 数组中索引的过程 struct segvn_data
...
...anon_index
struct seg
...
...
s_data amp
struct anon_map
...
...ahp
struct anon_hdr
...
...
array_chunkamp
struct* anon[]
s_base
虚地址 - +>>12
struct anonan_vp
...an_pvp
55
匿名内存匿名内存
堆和栈Copy-On-Write (COW)
56
简介目的
了解 Solaris 中堆和栈的增长知识点
brk 系统调用负责堆的增长保护性页故障和 grow 函数结合起来完成栈的增长匿名内存相关的数据结构
57
堆的增长heap.c 源码。char *ptr1, ptr2[4096];int main(){ ptr1 = malloc(2 * 4096); ptr1[4095] = '\0'; free(ptr1);}
malloc 函数调用 brk 对段进行扩展。 brk 实际上是在当前堆之后创建一个新的匿名段,然后把新匿名段与当前堆合并,从而实现堆的增长。查看 heap 在执行 malloc 之前和之后的地址空间,比较堆的大小。执行 malloc 之前> ::mappings BASE LIMIT SIZE NAME…… 8061000 8062000 1000 [ heap ]
执行 malloc 之后> ::mappings BASE LIMIT SIZE NAME…… 8061000 8066000 5000 [ heap ]
58
栈的增长 - stack.cstack.c 源码。void func();
int main(){ func();}
void func(){ char buffer[2 * 4096]; buffer[0] = '\0';}
stack.c 中通过声明大块的局部变量来引发栈的增长。栈段的增长借用了未映射错误。发生在栈段下方的未映射错误会引发对 grow 函数的调用,最终完成栈的增长。grow 函数在完成对栈段的增长后,会主动调用 as_fault ,为新增的部分分配物理页面,防止再次发生页故障。
59
栈的增长 - grow.d#!/usr/sbin/dtrace -s
#pragma D option flowindent
fbt::grow:entry/ execname == $$1 && (long)args[0] >= $2 && (long)args[0] < $3 /{ stack(); printf("\nexecname:%s pid:%d addr:%x", execname, pid, (long)args[0]); self->start = 1;}
fbt::grow:return/ self->start == 1 /{ self->start = 0; exit(0);}
fbt:::/ self->start == 1 /{}
60
栈的增长 - grow.d 的输出CPU FUNCTION 0 -> grow unix`trap+0xbc8 unix`_cmntrap+0x140
execname:stack pid:25873 addr:8045ca4 0 | grow:entry 0 -> as_rangelock 0 <- as_rangelock 0 -> grow_internal 0 -> as_map …… 0 <- as_map 0 <- grow_internal 0 -> as_rangeunlock 0 -> cv_signal 0 <- cv_signal 0 <- as_rangeunlock 0 -> as_fault …… 0 <- as_fault 0 <- grow
61
虚拟内存匿名内存
堆和栈Copy-On-Write (COW)
62
简介目的
了解 Solaris 中 Copy-On-Write 的实现机制知识点
数据段的映射方式和装载方式>数据段是可写的,但它的页面是只读的
COW 的基本机制
63
Copy on Write
64
Copy-On-Write 的机制
MAP_PRIVATE MAP_SHARE
只读 段错误 段错误读写 COW 直接修改页面
权限映射方式
段的权限设成读写页表中把该段对应的页设成只读往页面中写时 MMU 会引发保护性页故障,在页故障处理函数中完成 COW
65
数据段上的 COW数据段加载
数据段是可读写的在映射数据段时,共享方式为 MAP_PRIVATEexec 函数在执行应用程序的时候会进行预装载,这个过程中分配给数据段的物理页全是只读的
第一次访问数据段上的页面会引发 COWCOW 为数据段分配一个匿名页,并把被写的页复制到匿名页。
66
cow.ccow.c 源码char buffer[4096] = "hello";
int main(){ buffer[4095] = '\0'; return 0;}
buffer 是一个已初始化全局变量,对 buffer 进行写操作会引发 COW 。虚地址为buffer 加上 4095 ,也就是 0xfff 。
-bash-3.00$ dumpstabs -t cow | grep buffer 54: buffer 0806090c 00001000 GLOBAL OBJECT 17
虚地址为: 0x806090c + 0xfff = 0x806190b
67
验证 pte 中的权限为只读找到虚地址对应的物理地址> ::pgrep cow S PID PPID PGID SID UID FLAGS ADDR NAME R 26171 26170 26170 18231 100 0x4a004000 ffffffff82065df0 cow > ffffffff82065df0::print struct proc p_as p_as = 0xffffffff82dfd620 > 806190b::vtop -a 0xffffffff82dfd620 virtual 806190b mapped to physical 598d90b
找到这个物理地址所在物理页面对应的 page 结构> (598d90b >> 0t12) =K | ::page_num2pp 598d has page at fffffffffaadeca0
根据 page 结构找到这个物理页面对应的 pte…………(步骤见实验手册) 598d005验证 pte 为只读> 598d005::pte
PTE=598d005: page=0x598d user
68
观察段的映射类型和 anon 结构找到虚地址对应的段> ::pgrep cow S PID PPID PGID SID UID FLAGS ADDR NAME R 26171 26170 26170 18231 100 0x4a004000 ffffffff82065df0 cow > ffffffff82065df0::print struct proc p_as | ::walk seg | ::seg SEG BASE SIZE DATA OPS fffffe808f72eb90 8046000 2000 fffffe808f661480 segvn_ops ffffffffa5c81998 8050000 1000 ffffffffa5db3d30 segvn_ops ffffffff90310288 8060000 2000 fffffe808f7327d8 segvn_ops ……
虚地址 0x806190b 落在第三个段,观察该段的映射方式。> ffffffff90310288::print struct seg s_data | ::print struct segvn_data typetype = 0x2
2 表示这个段的映射类型为 MAP_PRIVATE
观察虚地址对应的 anon 结构指针anon 结构指针为 0(步骤见实验手册)表明当前虚地址对应的页不是匿名页
69
观察 COW过程使用 cow.d监视 COW 操作# ./cow.d cow 0x8061000 0x8062000
在 mdb 中单步执行 cow> :emdb: target stopped at:main+0x14: movb %al,0x806190b <cow`buffer+0xfff>> :emdb: target stopped at:main+0x19: movl $0x0,-0x4(%ebp)
cow.d 的输出pid:26171 execname:cow vaddr:806190b Type: F_PROT RW: S_WRITE SegProt: RWX
再次观察 pte和 anon 结构 (由于虚地址不变,所以 pte和 anon 的位置都不变,可以直接查看)pte 映射到另一个页面,且可写anon 指针指向一个有效的 anon 结构
70
Outline 内存管理内存管理概述 虚拟内存 匿名内存 物理内存
71
物理内存物理内存
虚地址到物理地址的转换由物理地址找到虚拟地址页面的分配与回收
72
简介目的
熟悉 x86 的地址转换过程知识点
普通 32 位 x86 处理器的地址转换32 位 PAE模式下 x86 处理器的地址转换64 位 x86 处理器的地址转换
73
32 位 x86 平台的地址转换01112212231
PageDirectory
PageTable
PhysicalPage
121010
Page-Directory Base CR3
74
32 位 PAE模式 x86 处理器的地址转换01112202131
PDE
PTE
PageDirectory
PageTable
PhysicalPage
1299
Page-Directory-Pointer Base CR3
PDPE
2930
PageDirectoryPointer
2
75
64 位 x86 处理器地址转换01112202138
PDE
PTE
PageDirectory
PageTable
PhysicalPage
1299
Page-Map Level-4 Base Address CR3
2930
9
4763 3948
PDPE
PageDirectoryPointer
PML4E
Page-MapLevel-4
9
76
32 位 x86 平台中定位页表MMU
proc
p_as
as
a_hat
hat
hat_as
htable
ht_pfn
hat_htable
PageTables
PhysicalPages
77
32 位程序运行在 64 位平台的情况MMU
proc
p_as
as
a_hat
hat
hat_ashat_vlp_ptes
PageTables
PhysicalPages
78
物理内存物理内存
虚地址到物理地址的转换由物理地址找到虚拟地址页面的分配与回收
79
简介目的
了解 Solaris 中从物理地址到虚拟地址的反向映射机制知识点
与反向映射相关的数据结构
80
根据页面找到它映射到哪些地址空间page
p_embedp_mappingp_mlentry
htable
ht_pfn
p_pagenum
hment
hm_entryhm_htable
hm_next
hment
hm_entryhm_htable
hm_next
htable
ht_pfn
PageTable
PageTable
81
根据页面找到它映射到的进程地址空间
page
p_ embedp_ mappingp_ mlentry
htable
ht_pfn
p_ pagenum
htable
ht_hat
hat
hat_as
as
hment
hm_entryhm_ htable
hm_next
hment
hm_entryhm_ htable
hm_next
proc
as
82
观察 printf运行 hello-bash-3.00$ ./helloHello world!
由于 printf 是动态加载的符号,所以只能在 hello 启动后才能得到它的虚地址在 mdb 获取 printf 的虚地址-bash-3.00$ mdb hello> !pgrep hello1788> ::attach 1788Loading modules: [ ld.so.1 libc.so.1 ]> printf::nmValue Size Type Bind Other Shndx Name0xfef26790|0x00000105|FUNC |GLOB |0x0 |12 |libc.so.1`printf
83
观察 printf (Cont’)获取 printf 的物理地址和 page 结构> ::pgrep helloS PID PPID PGID SID UID FLAGS ADDR NAMER 1788 1640 1788 1640 100 0x4a004000 ffffffff82aa2048 hello> ffffffff82aa2048::print struct proc p_asp_as = 0xffffffff831a27e8> 0xfef26790::vtop -a 0xffffffff831a27e8virtual fef26790 mapped to physical 1e796790> 1e796790 >> 0t12 =K | ::page_num2pp1e796 has page at fffffffffb64c0d8
可以用一条长管道命令显示出所有映射了某个物理页表的进程的可执行程序名。> fffffffffb64c0d8::print struct page p_mapping | ::list struct hment hm_next
| ::print struct hment hm_htable | ::print struct htable ht_hat | ::print struct hat hat_as | ::as2proc | ::print struct proc p_exec | ::vnode2path
/usr/bin/i86/mdb/home/user1/source/hello/usr/bin/bash…………
84
物理内存物理内存
虚地址到物理地址的转换由物理地址找到虚拟地址页面的分配与回收
85
简介目的
了解 Solaris 中物理页面的声明周期知识点
文件缓存 free list 与 cache list页面的分配、释放与重用
86
物理页面的生命周期
87
文件缓存seg
s_data
segmap_datasmd_sm
smd_mpagessmd_free
smap
sm_vpsm_off
sm_nextsm_prev
……
file
……
segkmap
88
page_create_va.d跟踪与某个特定文件相关的页面的创建#!/usr/sbin/dtrace -s
#pragma D option quiet
self struct vnode * vp;
fbt::page_create_va:entry/ (vp = args[0]) && vp->v_path == $$1 /{
self->trace = 1;}
fbt::page_create_va:return/ self->trace == 1 /{
printf(“%-15s create page %X\n”, execname, arg1);self->trace == 0;
}
89
跟踪 hello 的页面创建用 page_create_va.d 跟踪与 hello 相关的页面的创建# ./page_create_va.d /home/user1/source/hello
删除 hello(如果存在的话),并重新编译 hellobash-3.00$ rm hellobash-3.00$ cc -o hello hello.c
page_create_va.d 的输出为 genunix`segmap_pagecreate+0x1aa genunix`fbzero+0xd9 ufs`bmap_write+0xf21 ufs`ufs_itrunc+0xa5f ufs`ufs_trans_itrunc+0x128 ufs`ufs_freesp+0x153 ufs`ufs_space+0xd5 genunix`fop_space+0x47 genunix`fcntl+0xa87 unix`_sys_sysenter_post_swapgs+0x14bld create page FFFFFFFFFB5C12F8………………
90
观察 smap 结构找到物理页面在内核地址空间中的虚地址> fffffffffb5c12f8::print struct page p_mappingp_mapping = 0xffffffff83219a50> 0xffffffff83219a50::list struct hment hm_next | ::print struct hment hm_htablehm_htable = 0xffffffff80855f80> 0xffffffff80855f80::print struct htable ht_hat | ::print struct hat hat_ashat_as = kas> 0xffffffff80855f80::print struct htable ht_vaddrht_vaddr = 0xfffffe8061e00000> 0xffffffff83219a50::print struct hment hm_entryhm_entry = 0x1f6> 0xfffffe8061e00000 + (0x1f6 * 0t4096) = K fffffe8061ff6000
用 addr2smap 命令找到该虚地址对应的 smap 地址。> 0xfffffe8061ff6000::addr2smapfffffe8061ff6000 is smap ffffffff80a47e70
查看该 smap 映射的是否是 hello> ffffffff80a47e70::print struct smap sm_vp | ::vnode2path/export/home/user1/source/hello
91
页面生命周期创建或重用页面
page_lookup_createpage_create_va
使用状态放入 cache listpage_free
使用状态到 free listpage_destroy
cache list 到 free listpage_destroy_free
92
pagelist.d#!/usr/sbin/dtrace -s#pragma D option quiet
fbt::page_destroy:entry,fbt::page_destroy_free:entry,fbt::page_free:entry,fbt::page_reclaim:entry/ args[0] && args[0]->p_vnode && args[0]->p_vnode->v_path == $$1 /{ stack(); printf("%-15s %-20s page:%X\ n", execname, probefunc, (long)args[0]);}
fbt::page_create_va:return,fbt::page_lookup_create:return/ (struct page*)arg1 && ((struct page*)arg1)->p_vnode && ((struct page*)arg1)->p_vnode->v_path == $$1 /{ stack(); printf("%-15s %-20s page:%X\n", execname, probefunc, arg1);}
93
观察页面生命周期用 pagelist.d 跟踪 test.txt 的页面分配与回收
# ./pagelist.d /home/usr1/source/test.txt创建 test.txt
-bash-3.00$ ls > test.txt把 test.txt 的页面放入 cache list
-bash-3.00$ find /usr -type f -exec cat {} > /dev/null 2>&1 \;
执行这个命令,知道 pagelist.d 输出新内容重用 test.txt 的页面
-bash-3.00$ cat test.txt删除 test.txt ,把 test.txt 的页面放入 free list 中
-bash-3.00$ rm test.txt
94
王华wanghua@os.pku.edu.cn
Q&A
top related