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

94
1 Solaris Solaris 操操操 操操 操操操 操操 操操操操 操操操操

Upload: chaeli

Post on 10-Feb-2016

138 views

Category:

Documents


0 download

DESCRIPTION

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

TRANSCRIPT

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

1

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

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

2

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

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

3

虚存管理中涉及到的实体虚拟内存

程序员看到的地址空间,还没有被 MMU 转换通常大于实际的物理内存

物理内存由物理地址或实地址应用系统中实际安装的内存

文件磁盘上的数据

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

4

虚拟内存与物理内存的关系虚拟地址通过页表映射到物理地址进程地址空间

一个进程可以访问的所有虚拟地址集合 每个地址空间有一个页表

Page Table

Virtual Memory

MMU

Physical Memory

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

5

虚拟内存与文件的关系虚地址空间包含到不同的映射

每个映射是一个地址范围,用一个段( seg )表示每个段把它的虚地址映射到文件或设备Virtual

Memory

File Swap Device

Seg Seg

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

6

物理内存与文件的关系每个物理页面与文件或设备中的一块相对应

可能页面的内容来自文件可能页面在换出时需要写入文件

Physical Memory

File Swap Device

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

7

全部关系Virtual

Memory

File Swap Device

Seg Seg

Physical Memory

Page TableMMU

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

8

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

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

9

虚拟内存虚拟内存

观察进程和内核的地址空间文件映射页故障

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

10

简介目的

了解进程和内核的地址空间布局主要步骤

编写一个程序,声明不同类型的变量,观察这些变量位于什么段中 观察堆段的创建 观察内核的地址空间

知识点 进程地址空间的布局 内核地址空间的布局 数据段的映射 堆段的创建

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

11

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

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

12

进程中最常用的段 : seg_vn 段

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

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

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

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

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

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;}

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

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

动态分配的内存一部分在数据段中,一部分在堆中,为什么?

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

17

数据段与堆段的映射

PageBoundary

p_brkbaseData

Segment

HeapSegment

PageBoundary

p_brkbase

DataSegment

HeapSegment

PageBoundary

PageBoundary

.data.data

.bss.bss

a b

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

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 文件中只需要存放已初始化变量的初始值,所以这个长度等于已初始化变量的长度

章节虚地址章节长度

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

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 位进程中,堆位于下方,栈位于顶端。

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

20

观察 test 中堆段的创建test 刚启动时没有堆段

整个 .bss 段位于数据段中, brk 的起始地址也位于数据段中堆段在调用 malloc 时创建步骤

使用 segvn_create.d 跟踪 test 中的段创建单步运行 test ,看 segvn_create.d 何时捕获到堆栈的创建。

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

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 的程序的名称跟踪哪个地址范围内的段创建

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

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 ]……

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

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 完成栈段的创建和扩展

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

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 函数后进程的地址空间中就多了一个堆段

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

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

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

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

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

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

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

28

虚拟内存虚拟内存

观察进程和内核的地址空间文件映射页故障

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

29

简介目的

了解 Solaris 中的文件映射 I/O主要步骤

编写一段代码,调用 mmap 函数映射一个文件 在执行 mmap 函数前后分别观察进程的地址空间,看有什么不同 观察文件映射段中页面的内容 观察多个进程映射同一个文件时物理页面的共享

知识点 mmap 系统调用 文件映射的实现方式 在文件映射中的页面共享

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

30

内存文件映射

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

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 完成映射

映射之前要打开文件

程序退出之前解除映射

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

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 在地址空间中新增加了一个段

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

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 转换成物理地址

查看物理页面的内容,这个内容与文件内容一致

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

34

共享映射文件

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

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

两个进程使用的物理页面是相同的

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

36

虚拟内存虚拟内存

观察进程和内核的地址空间文件映射页故障

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

37

简介目的

了解 Solaris 中的页故障类型以及页故障处理方式主要步骤

构造三个程序,来引发三种不同类型的页故障 用 DTrace 脚本跟踪页故障 观察不同类型的段上的页故障,以及未映射页故障

知识点 三种页故障类型>严重页故障( Major Page Fault )>轻度页故障( Minor Page Fault )>保护性页故障( Protection Page Fault )

不同段上的页故障处理

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

38

三种类型的页故障

Invalid

Read OnlyInvalid

PageTables

Write

MajorFault

MinorFault

ProtectionFault

I/O

Page Fault Handler

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

39

页故障处理流程

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

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 /{}

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

41

严重页故障基本思路

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

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

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

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

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] 所在的虚拟页面分配了物理页面

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

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

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

45

轻度页故障基本思路

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

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

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

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

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

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

48

保护性页故障基本思路

让程序对代码段执行写操作protect.c 源码。 protect.c 中执行了往代码段写的操作。

int main(){ ((char*)main)[0] = '\0'; return 0;}

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

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)表示越权操作

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

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等

●如果一个程序访问了一个不属于任何段的虚地址,就会发生一次未映射错误

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

51

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

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

52

匿名内存指向不直接与 vnode 关联的页面

进程堆空间 进程的栈 copy-on-write 产生的页面

实现匿名内存的两个子系统 Anon layer >通过这个层提供的接口创建和管理匿名内存页

swapfs file system>作为匿名内存页面的后备存储>当内存不足时,把匿名内存页面拷贝到交换文件系统

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

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

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

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

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

55

匿名内存匿名内存

堆和栈Copy-On-Write (COW)

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

56

简介目的

了解 Solaris 中堆和栈的增长知识点

brk 系统调用负责堆的增长保护性页故障和 grow 函数结合起来完成栈的增长匿名内存相关的数据结构

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

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 ]

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

58

栈的增长 - stack.cstack.c 源码。void func();

int main(){ func();}

void func(){ char buffer[2 * 4096]; buffer[0] = '\0';}

stack.c 中通过声明大块的局部变量来引发栈的增长。栈段的增长借用了未映射错误。发生在栈段下方的未映射错误会引发对 grow 函数的调用,最终完成栈的增长。grow 函数在完成对栈段的增长后,会主动调用 as_fault ,为新增的部分分配物理页面,防止再次发生页故障。

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

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 /{}

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

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

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

61

虚拟内存匿名内存

堆和栈Copy-On-Write (COW)

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

62

简介目的

了解 Solaris 中 Copy-On-Write 的实现机制知识点

数据段的映射方式和装载方式>数据段是可写的,但它的页面是只读的

COW 的基本机制

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

63

Copy on Write

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

64

Copy-On-Write 的机制

MAP_PRIVATE MAP_SHARE

只读 段错误 段错误读写 COW 直接修改页面

权限映射方式

段的权限设成读写页表中把该段对应的页设成只读往页面中写时 MMU 会引发保护性页故障,在页故障处理函数中完成 COW

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

65

数据段上的 COW数据段加载

数据段是可读写的在映射数据段时,共享方式为 MAP_PRIVATEexec 函数在执行应用程序的时候会进行预装载,这个过程中分配给数据段的物理页全是只读的

第一次访问数据段上的页面会引发 COWCOW 为数据段分配一个匿名页,并把被写的页复制到匿名页。

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

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

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

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

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

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(步骤见实验手册)表明当前虚地址对应的页不是匿名页

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

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 结构

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

70

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

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

71

物理内存物理内存

虚地址到物理地址的转换由物理地址找到虚拟地址页面的分配与回收

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

72

简介目的

熟悉 x86 的地址转换过程知识点

普通 32 位 x86 处理器的地址转换32 位 PAE模式下 x86 处理器的地址转换64 位 x86 处理器的地址转换

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

73

32 位 x86 平台的地址转换01112212231

PageDirectory

PageTable

PhysicalPage

121010

Page-Directory Base CR3

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

74

32 位 PAE模式 x86 处理器的地址转换01112202131

PDE

PTE

PageDirectory

PageTable

PhysicalPage

1299

Page-Directory-Pointer Base CR3

PDPE

2930

PageDirectoryPointer

2

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

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

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

76

32 位 x86 平台中定位页表MMU

proc

p_as

as

a_hat

hat

hat_as

htable

ht_pfn

hat_htable

PageTables

PhysicalPages

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

77

32 位程序运行在 64 位平台的情况MMU

proc

p_as

as

a_hat

hat

hat_ashat_vlp_ptes

PageTables

PhysicalPages

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

78

物理内存物理内存

虚地址到物理地址的转换由物理地址找到虚拟地址页面的分配与回收

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

79

简介目的

了解 Solaris 中从物理地址到虚拟地址的反向映射机制知识点

与反向映射相关的数据结构

Page 80: 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

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

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

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

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

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

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…………

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

84

物理内存物理内存

虚地址到物理地址的转换由物理地址找到虚拟地址页面的分配与回收

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

85

简介目的

了解 Solaris 中物理页面的声明周期知识点

文件缓存 free list 与 cache list页面的分配、释放与重用

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

86

物理页面的生命周期

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

87

文件缓存seg

s_data

segmap_datasmd_sm

smd_mpagessmd_free

smap

sm_vpsm_off

sm_nextsm_prev

……

file

……

segkmap

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

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;

}

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

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………………

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

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

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

91

页面生命周期创建或重用页面

page_lookup_createpage_create_va

使用状态放入 cache listpage_free

使用状态到 free listpage_destroy

cache list 到 free listpage_destroy_free

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

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

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

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

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

94

王华[email protected]

Q&A