操作系统实验三:内存管理
实验目的
理解页面淘汰算法原理,编写程序演示页面淘汰算法。
验证Linux虚拟地址转化为物理地址的机制
理解和验证程序运行局部性的原理。
理解和验证缺页处理的流程。
实验内容
Win/Linux编写二维数组遍历程序,理解局部性的原理。
Windows/Linux模拟实现OPT或FIFO或LRU淘汰算法。
研读并修改Linux内核的缺页处理函数do_no_page(新版本应该是handle_mm_fault) 或页框分配函数get_free_page,并用printk打印调试信息。注意:需要编译内核。建议优麒麟或麒麟系统 。
Linux下利用/proc/pid/pagemap技术计算某个变量或函数虚拟地址对应的物理地址等信息。建议优麒麟或麒麟系统 。
任务一:编写二维数组遍历程序,理解程序局部性原理
任务要求
数组尽可能开大一些,并尝试改变数组大小,改变内外重循环次序。例如从[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位的库:
1 2 sudo dpkg --add-architecture i386 sudo apt-get install gcc-multilib g++-multilib
32位和64位程序的运行结果
利用g++分别编译出32位和64位的可执行文件test_32和test_64运行结果如下:
现象观察和分析
执行时间:
在较小的矩阵(2048x2048)中,64位程序的执行时间略少于32位程序。
在较大的矩阵(10240x20480)中,64位程序在行优先遍历时明显快于32位程序,但在列优先遍历时,两者的时间差异不大。
页面错误:
对于两种程序(32位和64位),在相同的矩阵大小和遍历方式下,页面错误的数量几乎相同。
执行时间差异:
64位程序通常可以更有效地处理大型数据集,因为它们可以利用更大的地址空间和通常更高效的数据处理能力(例如,更大的寄存器和更优的内存管理)。这解释了为什么在处理较大的矩阵时,64位程序在行优先遍历中比32位程序更快。
对于列优先遍历,由于内存访问模式导致频繁的缓存未命中,这可能在一定程度上抵消了64位架构的优势,导致两者在大矩阵列优先遍历时的性能差异不大。
页面错误相似性:
页面错误的数量主要取决于程序的内存访问模式和操作系统的页面管理策略,而不是程序的位数。因此,即使是32位和64位程序,如果它们执行相同的内存访问操作,页面错误的数量也可能非常接近。感觉更多取决于内存访问模式和操作系统的内存管理。
但我其实有一个疑问在于,从直观的感觉来看,列有限遍历产生的页面错误应该比行优先遍历更多,因为列优先遍历时,每次访问的地址都是连续的,而行优先遍历时,每次访问的地址都是不连续的。但实际上,两者的页面错误数量几乎相同。
GPT的回答:
内存分页和访问模式:
现代计算机系统中,物理内存是分页管理的。如果一个矩阵的行或列跨越了多个内存页,那么无论是行优先还是列优先遍历,都可能导致页面错误。
在行优先遍历中,如果矩阵的每行都跨越多个页面,那么即使访问是连续的,也可能在访问新行时遇到页面错误。
在列优先遍历中,尽管每次跳跃到新列可能会触及新的内存页,但如果列数据较小并且能够在单个或少数几个内存页中被完全包含,页面错误的发生可能不会比行优先多很多。
缓存和预取策略:
现代处理器具有高级的缓存和预取机制。这些机制可能在某种程度上减少了预期中列优先遍历的页面错误数量。
处理器可能能够预测接下来的内存访问模式,并提前将数据从主内存加载到缓存中,这样即使是列优先访问,也可能不会立即导致页面错误。
操作系统的内存管理:
操作系统在管理内存时可能采取一些策略,如延迟分配,这可能导致实际的页面错误数量与预期不同。
操作系统可能会保持一定数量的空闲页面池,这样在初期的内存访问中,页面错误的发生率可能较低。
矩阵的物理内存布局:
矩阵在物理内存中的实际布局也可能影响页面错误的发生。例如,如果矩阵的某些部分已经由于其他原因被加载到内存中,这可能会减少特定遍历方式下的页面错误。
系统的整体内存负载:
如果系统上运行的其他进程占用了大量内存,这可能会影响到您的程序的页面错误行为。系统的整体内存负载和可用性可能会导致不同时间点上相同操作的不同页面错误表现。
任务二:模拟实现OPT或FIFO或LRU淘汰算法
任务要求
程序指令执行过程采用遍历数组的操作来模拟;
用1个较大数组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
fifo
lru
由于opt算法需要知道未来的页面访问顺序,而故选中opt算法之后,不存在5=随机访问这个选项。
任务三:修改Linux内核缺页处理函数,打印调试信息
任务要求
编写2个类似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 page fault count: %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程序的页面错误计数没有被打印出来。
任务四: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);
可以输出进程中某个或多个全局变量或自定义函数的虚拟地址,所在页号,所在物理页框号,物理地址等信息。
思考:
(1) 如何扩充实验(写个通用函数)展示指定进程的指定虚拟地址对应的物理地址。
(2) 如何扩充实验验证不同进程的共享库(例如某个已知,被普遍调用的*.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("Error opening pagemap" ); return ; } lseek(fd, v_offset, SEEK_SET); if (read(fd, &item, sizeof (uint64_t )) != sizeof (uint64_t )) { perror("Error reading 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("Error opening pagemap" ); return ; } lseek(fd, v_offset, SEEK_SET); if (read(fd, &item, sizeof (uint64_t )) != sizeof (uint64_t )) { perror("Error reading 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 , "Usage: %s <pid> <virtual address>\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, Virtual address of printf: %p\n" , getpid(), (void *)printf ); sleep(1000 ); return 0 ; }
程序运行结果
必须用sudo运行,否则这个文件应该是没有权限打开的
program.c
va2pa.c && test.c
启动两个test进程,分别输出printf函数的虚拟地址。va2pa.c分别计算两个进程的printf函数的虚拟地址对应的物理地址。
右边两个红色的框线,打印当前运行的test程序的pid和printf函数的虚拟地址,然后把进程号和虚拟地址作为va2pa.c的参数,计算出物理地址。结果如左边两个框线所示,两个进程的printf函数的虚拟地址对应的物理地址(蓝色荧光笔标记)相同,证明了不同进程的共享库具有同一物理地址。