操作系统实验3:内存管理
实验目标
理解页面置换算法原理,并编写程序演示页面置换算法。
验证Linux虚拟地址到物理地址转换的机制。
理解并验证程序执行局部性的原理。
理解并验证页面错误处理过程。
实验内容
编写一个在Win/Linux中遍历2D数组的程序,以理解局部性原理。
在Windows/Linux中模拟并实现OPT、FIFO或LRU页面置换算法。
研究并修改Linux内核的页面错误处理函数do_no_page(新版本应为handle_mm_fault)或页面帧分配函数get_free_page,并使用printk打印调试信息。注意:需要内核编译。推荐:Ubuntu
Kylin或Kylin系统 。
在Linux中使用/proc/pid/pagemap技术计算变量或函数的虚拟地址对应的物理地址。推荐:Ubuntu
Kylin或Kylin系统 。
任务1:编写2D数组遍历程序以理解程序局部性原理
任务要求
尽可能增大数组,并尝试改变数组大小和内外循环的顺序。例如,将[2048]
X [2048]改为[10240] x [20480],观察它们的遍历效率。
在任务管理器中观察它们的页面错误计数或使用/proc文件系统。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <iostream> #include <sys/time.h> #include <sys/resource.h> #include <vector> #include <iomanip> using namespace std;void traverseRowMajor (vector<vector<int >> &arr) { int sum = 0 ; for (size_t i = 0 ; i < arr.size (); i++) { for (size_t j = 0 ; j < arr[i].size (); j++) { sum += arr[i][j]; } } } void traverseColumnMajor (vector<vector<int >> &arr) { int sum = 0 ; for (size_t j = 0 ; j < arr[0 ].size (); j++) { for (size_t i = 0 ; i < arr.size (); i++) { sum += arr[i][j]; } } } int main () { int rows, cols; cout << "请输入矩阵的行数和列数(格式:x y):" ; cin >> rows >> cols; vector<vector<int >> arr (rows, vector <int >(cols, 1 )); rusage usage; timeval start, end; char choice; cout << "选择遍历方法(r: 行优先,c: 列优先):" ; cin >> choice; getrusage (RUSAGE_SELF, &usage); start = usage.ru_utime; if (choice == 'r' ) { traverseRowMajor (arr); } else if (choice == 'c' ) { traverseColumnMajor (arr); } getrusage (RUSAGE_SELF, &usage); end = usage.ru_utime; double time_taken = (end.tv_sec - start.tv_sec) * 1000 + (end.tv_usec - start.tv_usec) / 1000.0 ; cout << fixed << setprecision (3 ) << "消耗时间:" << time_taken << " 毫秒\n" ; cout << "页面错误次数:" << usage.ru_minflt << " 次\n" ; return 0 ; }
32位程序编译
如果直接添加 -m32 进行编译,会报错:
32位编译错误
因为缺少32位库,需要安装32位库:
1 2 sudo dpkg --add-architecture i386 sudo apt-get install gcc-multilib g++-multilib
32位和64位程序执行结果
使用g++编译32位和64位可执行文件test_32和test_64,执行结果如下:
64位执行结果
32位执行结果
现象观察与分析
执行时间:
在较小的矩阵(2048x2048)中,64位程序的执行时间略低于32位程序。
在较大的矩阵(10240x20480)中,64位程序在行优先遍历时显著快于32位程序,但在列优先遍历时,两者的时间差异不大。
页面错误:
对于两个程序(32位和64位),在相同的矩阵大小和遍历方法下,页面错误的次数几乎相同。
执行时间差异:
64位程序通常能更高效地处理大数据集,因为它们可以利用更大的地址空间和更高效的数据处理能力(例如,更大的寄存器和更好的内存管理)。这解释了在处理较大矩阵时,64位程序在行优先遍历中比32位程序更快。
对于列优先遍历,由于内存访问模式导致频繁的缓存未命中,这可能在一定程度上抵消64位架构的优势,导致两者在大矩阵列优先遍历中的性能相似。
页面错误相似性:
页面错误的数量主要取决于程序的内存访问模式和操作系统的页面管理策略,而不是程序的位宽。因此,即使是32位和64位程序,如果它们执行相同的内存访问操作,页面错误的数量也可能非常相似。这似乎更依赖于内存访问模式和操作系统的内存管理。
但我实际上有一个问题:直观上,列优先遍历应该产生比行优先遍历更多的页面错误,因为在列优先遍历中,每个访问地址是连续的,而在行优先遍历中,每个访问地址是不连续的。但实际上,两者的页面错误数量几乎相同。
GPT的回答:
内存分页和访问模式:
在现代计算机系统中,物理内存是以页面管理的。如果一个矩阵的行或列跨越多个内存页面,那么行优先和列优先遍历都可能导致页面错误。
在行优先遍历中,如果矩阵的每一行跨越多个页面,那么即使访问是连续的,在访问新行时也可能发生页面错误。
在列优先遍历中,虽然每次跳到新列可能会触及新的内存页面,但如果列数据较小并且可以完全包含在一个或几个内存页面中,则页面错误的发生可能不会比行优先遍历多多少。
缓存和预取策略:
现代处理器具有先进的缓存和预取机制。这些机制可能在一定程度上减少列优先遍历中预期的页面错误数量。
处理器可能能够预测即将到来的内存访问模式,并将数据从主内存预加载到缓存中,因此即使是列优先访问也可能不会立即导致页面错误。
操作系统内存管理:
操作系统可能在内存管理中采用一些策略,例如延迟分配,这可能导致实际的页面错误数量与预期不同。
操作系统可能维护一定数量的空闲页面池,因此在早期内存访问中,页面错误的发生率可能较低。
矩阵的物理内存布局:
矩阵在物理内存中的实际布局也可能影响页面错误的发生。例如,如果矩阵的某些部分由于其他原因已经加载到内存中,这可能会减少特定遍历方法中的页面错误。
整个系统的内存负载:
如果系统上运行的其他进程消耗了大量内存,这可能会影响程序的页面错误行为。系统的整体内存负载和可用性可能导致相同操作在不同时间点的页面错误行为不同。
任务2:模拟并实现OPT、FIFO或LRU页面置换算法
任务要求
使用数组遍历操作模拟程序指令执行;
使用一个大数组A(例如2400个元素)模拟一个进程,数组中填充随机数。当每个元素被访问时,使用printf打印出来,模拟指令执行。数组A的大小必须是设定页面大小的整数倍(例如10个元素或16个元素等)。
使用3-8个小数组(例如数组B、数组C、数组D等)模拟分配的页面帧。小数组的大小等于页面大小(例如10个指令大小,即10个元素)。小数组包含来自大数组的相应页面内容的副本(单独构建一个页面表以描述大数组的页面与小数组索引之间的关系)。
以不同的顺序访问数组A,可以是:顺序、跳跃、分支、循环或随机。自行构建访问顺序。不同的顺序在某种程度上也反映了程序的局部性。
大数组的访问顺序可以使用rand()函数定义,以模拟与大数组A的访问顺序相对应的指令访问序列。然后将指令序列转换为相应的页面地址流,并计算不同页面置换算法的“页面错误”。当相应的“页面”未加载到小数组(例如数组B、数组C、数组D等)中时,发生页面错误。
在实验中,页面大小、页面帧数量、访问顺序和置换算法都应可调。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 #include <stdio.h> #include <stdlib.h> #include <time.h> #include <limits.h> #define ARRAY_SIZE 2400 #define PAGE_SIZE 10 #define NUM_PAGES (ARRAY_SIZE / PAGE_SIZE) #define NUM_FRAMES 5 #define JUMP_NUM_FRAMES 7 #define LOOP_TIMES 3 int main_array[ARRAY_SIZE]; int frames[NUM_FRAMES][PAGE_SIZE]; int page_table[NUM_PAGES]; int frame_table[NUM_FRAMES]; int page_faults = 0 ; int algo_choice = 0 ; int frame_pointer = 0 ; int frame_queue[NUM_FRAMES]; int frame_timestamp[NUM_FRAMES]; int future_pages[ARRAY_SIZE]; char *algo_name[4 ] = {"随机置换" , "OPT" , "FIFO" , "LRU" }; void initialize () { srand(time(NULL )); for (int i = 0 ; i < ARRAY_SIZE; i++) { main_array[i] = rand() % 100000 ; future_pages[i] = -1 ; } for (int i = 0 ; i < NUM_PAGES; i++) { page_table[i] = -1 ; } for (int i = 0 ; i < NUM_FRAMES; i++) { frame_queue[i] = -1 ; frame_timestamp[i] = -1 ; frame_table[i] = -1 ; } } int find_frame_opt (int current_time) { int frame = -1 ; int max_future = -1 ; for (int i = 0 ; i < NUM_FRAMES; i++) { if (frame_table[i] == -1 ) { frame = i; break ; } int future = -1 ; for (int j = current_time; j < ARRAY_SIZE; j++) { if (future_pages[j] == frame_table[i]) { future = j; break ; } } if (future > max_future) { max_future = future ; frame = i; } } if (max_future == -1 ) frame = rand() % NUM_FRAMES; return frame; } int find_frame_fifo () { int frame = frame_pointer; frame_pointer = (frame_pointer + 1 ) % NUM_FRAMES; return frame; } int find_frame_lru () { int lru = INT_MAX; int frame = -1 ; for (int i = 0 ; i < NUM_FRAMES; i++) { if (frame_timestamp[i] < lru) { lru = frame_timestamp[i]; frame = i; } } return frame; } void access_page (int page_number, int offset, int current_time) { if (page_table[page_number] == -1 ) { int frame_number; switch (algo_choice) { case 1 : frame_number = find_frame_opt(current_time); break ; case 2 : frame_number = find_frame_fifo(); break ; case 3 : frame_number = find_frame_lru(); break ; default : frame_number = rand() % NUM_FRAMES; } if (frame_table[frame_number] != -1 ) page_table[frame_table[frame_number]] = -1 ; printf ("\033[1;31m页面错误\033[0m,页面 %3d 加载到帧 %2d,偏移 %2d 的数据:" , page_number, frame_number, offset); page_table[page_number] = frame_number; frame_table[frame_number] = page_number; for (int i = 0 ; i < PAGE_SIZE; i++) { frames[frame_number][i] = main_array[page_number * PAGE_SIZE + i]; } page_faults++; frame_queue[frame_number] = page_number; frame_timestamp[frame_number] = current_time; } else { printf ("\033[1;32m页面命中\033[0m,页面 %3d 在帧 %2d,偏移 %2d 的数据:" , page_number, page_table[page_number], offset); frame_timestamp[page_table[page_number]] = current_time; } printf ("%5d " , main_array[page_number * PAGE_SIZE + offset]); printf ("\n" ); } int main () { initialize(); printf ("请选择页面置换算法(1=OPT,2=FIFO,3=LRU),默认随机置换:" ); scanf ("%d" , &algo_choice); if (algo_choice < 1 || algo_choice > 3 ) { printf ("无效的页面置换算法选项,使用默认置换方法:随机置换。\n" ); algo_choice = 0 ; } printf ("当前使用的页面置换算法:%s\n" , algo_name[algo_choice]); int access_order = 1 ; if (algo_choice == 1 ) { do { if (access_order < 1 || access_order > 4 ) printf ("无效的访问顺序选项,请重新选择。\n" ); printf ("OPT算法需要知道未来页面访问顺序,请选择页面访问顺序(1=顺序,2=跳跃,3=分支,4=循环):" ); scanf ("%d" , &access_order); } while (access_order < 1 || access_order > 4 ); } else { do { if (access_order < 1 || access_order > 5 ) printf ("无效的访问顺序选项,请重新选择。\n" ); printf ("请选择页面访问顺序(1=顺序,2=跳跃,3=分支,4=循环,5=随机):" ); scanf ("%d" , &access_order); } while (access_order < 1 || access_order > 5 ); } switch (access_order) { case 1 : for (int i = 0 ; i < ARRAY_SIZE; i++) future_pages[i] = i / PAGE_SIZE; for (int i = 0 ; i < ARRAY_SIZE; i++) access_page(i / PAGE_SIZE, i % PAGE_SIZE, i); break ; case 2 : for (int i = 0 ; i < ARRAY_SIZE; i++) future_pages[i] = i * JUMP_NUM_FRAMES % ARRAY_SIZE / PAGE_SIZE; for (int i = 0 ; i < ARRAY_SIZE; i++) access_page(i * JUMP_NUM_FRAMES % ARRAY_SIZE / PAGE_SIZE, i * JUMP_NUM_FRAMES % ARRAY_SIZE % PAGE_SIZE, i); break ; case 3 : for (int i = 0 ; i < ARRAY_SIZE; i++) { if (i % 2 == 0 ) future_pages[i] = i / PAGE_SIZE; else future_pages[i] = i * JUMP_NUM_FRAMES % ARRAY_SIZE / PAGE_SIZE; } for (int i = 0 ; i < ARRAY_SIZE; i++) { if (i % 2 == 0 ) access_page(i / PAGE_SIZE, i % PAGE_SIZE, i); else access_page(i * JUMP_NUM_FRAMES % ARRAY_SIZE / PAGE_SIZE, i * JUMP_NUM_FRAMES % ARRAY_SIZE % PAGE_SIZE, i); } break ; case 4 : for (int i = 0 ; i < ARRAY_SIZE; i++) future_pages[i] = (i % (ARRAY_SIZE / LOOP_TIMES)) / PAGE_SIZE; for (int i = 0 ; i < ARRAY_SIZE; i++) access_page((i % (ARRAY_SIZE / LOOP_TIMES)) / PAGE_SIZE, (i % (ARRAY_SIZE / LOOP_TIMES)) % PAGE_SIZE, i); break ; case 5 : for (int i = 0 ; i < ARRAY_SIZE; i++) { int ind = rand() % ARRAY_SIZE; access_page(ind / PAGE_SIZE, ind % PAGE_SIZE, i); } break ; default : for (int i = 0 ; i < ARRAY_SIZE; i++) { int ind = rand() % ARRAY_SIZE; access_page(ind / PAGE_SIZE, ind % PAGE_SIZE, i); } } printf ("页面访问错误计数:%d,命中率:%.2f%%\n" , page_faults, (1 - (float )page_faults / ARRAY_SIZE) * 100 ); return 0 ; }
程序执行结果
这是原始输出示例,但为了更好地演示,我对程序做了一些修改并注释掉了一些输出。
原始输出
OPT
OPT执行结果
FIFO
FIFO执行结果
LRU
LRU执行结果
由于OPT算法需要知道未来页面访问顺序,选择OPT算法后没有选项5=随机访问。
任务3:修改Linux内核页面错误处理函数以打印调试信息
任务要求
编写两个类似于hello
world或简单for循环的应用程序作为测试目标。
在Linux内核的页面错误处理函数do_no_page()或类似函数中使用printk添加调试信息(函数名称在不同版本中可能不同),打印特定进程的页面错误信息(使用程序名称作为过滤条件),并计数页面错误。
还可以在Linux内核的物理页面帧分配函数get_free_page()或类似函数中使用printk添加调试信息(函数名称在不同版本中可能不同),打印特定进程执行期间新页面帧分配的信息(使用程序名称作为过滤条件),并计数相关信息。
修改内核源代码
添加代码 ./mm/memory.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static char target_program[256 ] = {0 }; static long page_fault_count = 0 ; SYSCALL_DEFINE1(set_target_program_name, const char __user *, program_name) { if (copy_from_user(target_program,program_name, 255 ) != 0 ) return -EFAULT; target_program[255 ] = '\0' ; return 0 ; } SYSCALL_DEFINE0(get_page_fault_count) { printk(KERN_INFO "%s 页面错误计数:%ld\n" , target_program, page_fault_count); page_fault_count = 0 ; return page_fault_count; }
1 2 3 if (strcmp (current->comm, target_program) == 0 ) page_fault_count++;
系统调用函数声明
./include/linux/syscalls.h
1 2 3 asmlinkage long sys_set_target_program_name (const char __user *name) ; asmlinkage long sys_get_page_fault_count (void ) ;
系统调用ID
./arch/x86/entry/syscalls/syscall_64.tbl
1 2 3 448 64 set_target_program_name sys_set_target_program_name449 64 get_page_fault_count sys_get_page_fault_count
系统调用ID声明
./include/uapi/asm-generic/unistd.h
1 2 3 4 5 6 #define __NR_set_target_program_name 448 __SYSCALL(__NR_set_target_program_name, sys_set_target_program_name) #define __NR_get_page_fault_count 449 __SYSCALL(__NR_get_page_fault_count, sys_get_page_fault_count)
内核编译后的测试代码
编译时会报错如下:
错误
起初我不知道这个错误是什么意思,经过长时间的修改无效后,我后来添加了#include <linux/syscalls.h>,它就能工作了。这意味着如果你定义系统调用,必须包含这个头文件。但错误信息不是很清晰,导致我浪费了很多时间。
下面是test1.c,test2.c只是去掉了新添加的系统调用。这是为了测试调试信息是否可控。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <stdio.h> #include <unistd.h> #include <sys/syscall.h> #include <string.h> #define SYS_SET_TARGET_PROGRAM_NAME 448 #define SYS_GET_PAGE_FAULT_COUNT 449 const int size = 1000 ;int array [1000 ][1000 ];int main () { const char *target_program = "test" ; syscall(SYS_SET_TARGET_PROGRAM_NAME, target_program); int i, j; for (i = 0 ; i < size; i++) { for (j = 0 ; j < size; j++) { array [i][j] = i + j; } } syscall(SYS_GET_PAGE_FAULT_COUNT); return 0 ; }
程序执行结果
程序执行结果
如图所示,仅打印了test1程序的页面错误计数,而未打印test2程序的页面错误计数。
任务4:使用Linux中的/proc/pid/pagemap计算VA到PA的映射
任务要求
Linux的/proc/pid/pagemap文件允许用户查看当前进程虚拟页面的物理地址和其他相关信息。每个虚拟页面包含一个64位值,注意分析64位信息。
获取当前进程的pagemap文件的完整名称
1 2 3 4 5 6 7 8 9 pid_t pid = getpid();sprintf (buf, "%d" , pid);strcpy (filename, "/proc/" );strcat (filename, buf);strcat (filename, "/pagemap" );sprintf (filename, "/proc/%d/pagemap" , pid);
可以输出进程中一个或多个全局变量或自定义函数的虚拟地址、页面编号、物理页面帧编号、物理地址等信息。
思考:
如何扩展实验(编写通用函数)以显示指定进程的指定虚拟地址对应的物理地址。
如何扩展实验以验证不同进程的共享库(例如一些已知的、常用的*.so库)具有相同的物理地址。
代码实现
program.c实现输出一个或多个全局变量或自定义函数的虚拟地址、页面编号、物理页面帧编号、物理地址等信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #include <stdint.h> #include <sys/wait.h> void printPhysicalAddress (const char *description, unsigned long vaddr) { int pageSize = getpagesize(); unsigned long v_pageIndex = vaddr / pageSize; unsigned long v_offset = v_pageIndex * sizeof (uint64_t ); unsigned long page_offset = vaddr % pageSize; uint64_t item = 0 ; char buf[200 ]; sprintf (buf, "/proc/%d/pagemap" , getpid()); int fd = open(buf, O_RDONLY); if (fd == -1 ) { perror("打开pagemap时出错" ); return ; } lseek(fd, v_offset, SEEK_SET); if (read(fd, &item, sizeof (uint64_t )) != sizeof (uint64_t )) { perror("读取pagemap时出错" ); close(fd); return ; } uint64_t phy_pageIndex = (((uint64_t )1 << 55 ) - 1 ) & item; unsigned long paddr = (phy_pageIndex * pageSize) + page_offset; printf ("【%s】pid = %d, 虚拟地址 = 0x%lx, 页面编号 = %lu, 物理地址 = 0x%lx, 物理页面帧编号 = %lu\n" , description, getpid(), vaddr, v_pageIndex, paddr, phy_pageIndex); close(fd); } const int a = 100 ; int main () { int b = 100 ; static int c = 100 ; const int d = 100 ; char *p = (char *)malloc (100 ); printPhysicalAddress("全局常量" , (unsigned long )&a); printPhysicalAddress("局部变量" , (unsigned long )&b); printPhysicalAddress("局部静态变量" , (unsigned long )&c); printPhysicalAddress("局部常量" , (unsigned long )&d); printPhysicalAddress("动态内存" , (unsigned long )p); free (p); exit (0 ); return 0 ; }
va2pa.c实现输出指定进程的指定虚拟地址对应的物理地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <stdint.h> #include <stdlib.h> void printPhysicalAddressForProcess (pid_t pid, unsigned long vaddr) { int pageSize = getpagesize(); unsigned long v_pageIndex = vaddr / pageSize; unsigned long v_offset = v_pageIndex * sizeof (uint64_t ); unsigned long page_offset = vaddr % pageSize; uint64_t item = 0 ; char pagemap_file[256 ]; sprintf (pagemap_file, "/proc/%d/pagemap" , pid); int fd = open(pagemap_file, O_RDONLY); if (fd == -1 ) { perror("打开pagemap时出错" ); return ; } lseek(fd, v_offset, SEEK_SET); if (read(fd, &item, sizeof (uint64_t )) != sizeof (uint64_t )) { perror("读取pagemap时出错" ); close(fd); return ; } uint64_t phy_pageIndex = (((uint64_t )1 << 55 ) - 1 ) & item; unsigned long paddr = (phy_pageIndex * pageSize) + page_offset; printf ("pid = %d, 虚拟地址 = 0x%lx, 页面编号 = %lu, 物理地址 = 0x%lx, 物理页面帧编号 = %lu\n" , pid, vaddr, v_pageIndex, paddr, phy_pageIndex); close(fd); } int main (int argc, char *argv[]) { if (argc != 3 ) { fprintf (stderr , "用法:%s <pid> <虚拟地址>\n" , argv[0 ]); return 1 ; } pid_t pid = atoi(argv[1 ]); unsigned long vaddr = strtoul(argv[2 ], NULL , 16 ); printPhysicalAddressForProcess(pid, vaddr); return 0 ; }
test.c是一个测试程序,用于验证不同进程的共享库是否具有相同的物理地址。例如,这个程序中的printf函数在libc.so 共享库中。
1 2 3 4 5 6 7 8 9 #include <stdio.h> #include <unistd.h> int main () { printf ("PID: %d, printf的虚拟地址: %p\n" , getpid(), (void *)printf ); sleep(1000 ); return 0 ; }
程序执行结果
必须以sudo运行,否则该文件无权限打开
program.c
program.c执行结果
va2pa.c && test.c
启动两个测试进程,分别输出printf函数的虚拟地址。va2pa.c计算两个进程的printf函数虚拟地址对应的物理地址。
va2pa.c程序执行结果
两个红框右侧打印了当前运行测试程序的pid和printf函数的虚拟地址,然后使用进程号和虚拟地址作为参数调用va2pa.c计算物理地址。结果显示在左侧两个框中。两个进程的printf函数虚拟地址对应的物理地址(用蓝色高亮标记)是相同的,证明不同进程的共享库具有相同的物理地址。