Operating System Lab3: Memory Management

Experiment Objectives

  1. Understand page replacement algorithm principles and write programs to demonstrate page replacement algorithms.

  2. Verify the mechanism of Linux virtual address to physical address conversion

  3. Understand and verify the principle of program execution locality.

  4. Understand and verify the page fault handling process.

Experiment Content

  1. Write a 2D array traversal program in Win/Linux to understand the principle of locality.

  2. Simulate and implement OPT, FIFO, or LRU page replacement algorithms in Windows/Linux.

  3. Study and modify Linux kernel’s page fault handling function do_no_page (new version should be handle_mm_fault) or page frame allocation function get_free_page, and use printk to print debug information. Note: Kernel compilation is required. Recommended: Ubuntu Kylin or Kylin system.

  4. Use /proc/pid/pagemap technology in Linux to calculate physical addresses corresponding to virtual addresses of variables or functions. Recommended: Ubuntu Kylin or Kylin system.

Task 1: Write a 2D Array Traversal Program to Understand the Principle of Program Locality

Task Requirements

  1. Make the array as large as possible, and try changing the array size and the order of inner and outer loops. For example, change from [2048] X [2048] to [10240] x [20480], and observe their traversal efficiency.

  2. Observe their page fault counts in Task Manager or using the /proc file system.

Code Implementation

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;

// Row-major traversal
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];
}
}
}

// Column-major traversal
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 << "Enter the number of rows and columns of the matrix (format: x y): ";
cin >> rows >> cols;

vector<vector<int>> arr(rows, vector<int>(cols, 1));

rusage usage;
timeval start, end;

char choice;
cout << "Choose traversal method (r: row-major, c: column-major): ";

cin >> choice;

// Get process resource usage before starting
getrusage(RUSAGE_SELF, &usage);
start = usage.ru_utime;

if (choice == 'r')
{
traverseRowMajor(arr);
}
else if (choice == 'c')
{
traverseColumnMajor(arr);
}

// Get process resource usage after completion
getrusage(RUSAGE_SELF, &usage);
end = usage.ru_utime;

// Output time in milliseconds
double time_taken = (end.tv_sec - start.tv_sec) * 1000 + (end.tv_usec - start.tv_usec) / 1000.0;

cout << fixed << setprecision(3) << "Time consumed: " << time_taken << " milliseconds\n";
cout << "Page faults: " << usage.ru_minflt << " times\n";

return 0;
}

32-bit Program Compilation

If you directly add -m32 for compilation, it will report an error:

32bit compilation error

Because 32-bit libraries are missing, you need to install 32-bit libraries:

1
2
sudo dpkg --add-architecture i386              # Add 32-bit architecture
sudo apt-get install gcc-multilib g++-multilib # Install 32-bit libraries

32-bit and 64-bit Program Execution Results

Using g++ to compile 32-bit and 64-bit executable files test_32 and test_64 respectively, the execution results are as follows:

64-bit execution result
32-bit execution result

Observation and Analysis of Phenomena

  1. Execution time:
    1. In smaller matrices (2048x2048), 64-bit programs have slightly less execution time than 32-bit programs.
    2. In larger matrices (10240x20480), 64-bit programs are significantly faster than 32-bit programs in row-major traversal, but in column-major traversal, the time difference between the two is not significant.
  2. Page faults:
    1. For both programs (32-bit and 64-bit), under the same matrix size and traversal method, the number of page faults is almost identical.

Execution time differences:

64-bit programs can typically handle large datasets more efficiently because they can utilize larger address spaces and generally more efficient data processing capabilities (e.g., larger registers and better memory management). This explains why 64-bit programs are faster than 32-bit programs in row-major traversal when processing larger matrices. For column-major traversal, due to memory access patterns causing frequent cache misses, this may somewhat offset the advantages of 64-bit architecture, resulting in similar performance between the two in large matrix column-major traversal.

Page fault similarity:

The number of page faults mainly depends on the program’s memory access patterns and the operating system’s page management strategy, rather than the program’s bit width. Therefore, even for 32-bit and 64-bit programs, if they perform the same memory access operations, the number of page faults may be very similar. It seems more dependent on memory access patterns and the operating system’s memory management.

But I actually have a question: intuitively, column-major traversal should produce more page faults than row-major traversal, because in column-major traversal, each access address is continuous, while in row-major traversal, each access address is discontinuous. But in reality, the number of page faults for both is almost the same.

GPT’s answer:

  • Memory paging and access patterns:
    • In modern computer systems, physical memory is managed in pages. If a matrix’s rows or columns span multiple memory pages, then both row-major and column-major traversal may cause page faults.
    • In row-major traversal, if each row of the matrix spans multiple pages, then even if access is continuous, page faults may occur when accessing new rows.
    • In column-major traversal, although each jump to a new column may touch new memory pages, if the column data is small and can be completely contained within a single or few memory pages, the occurrence of page faults may not be much more than in row-major traversal.
  • Cache and prefetch strategies:
    • Modern processors have advanced cache and prefetch mechanisms. These mechanisms may somewhat reduce the expected number of page faults in column-major traversal.
    • Processors may be able to predict upcoming memory access patterns and preload data from main memory to cache, so even column-major access may not immediately cause page faults.
  • Operating system memory management:
    • The operating system may adopt some strategies in memory management, such as lazy allocation, which may cause the actual number of page faults to differ from expectations.
    • The operating system may maintain a certain number of free page pools, so in early memory access, the occurrence rate of page faults may be lower.
  • Physical memory layout of matrices:
    • The actual layout of matrices in physical memory may also affect the occurrence of page faults. For example, if some parts of the matrix have already been loaded into memory for other reasons, this may reduce page faults in specific traversal methods.
  • Overall system memory load:
    • If other processes running on the system consume a lot of memory, this may affect the page fault behavior of your program. The overall memory load and availability of the system may cause different page fault behaviors for the same operation at different time points.

Task 2: Simulate and Implement OPT, FIFO, or LRU Page Replacement Algorithms

Task Requirements

  1. Use array traversal operations to simulate program instruction execution;

  2. Use one large array A (e.g., 2400 elements) to simulate a process, with random numbers in the array. When each element is accessed, use printf to print it out, simulating instruction execution. The size of array A must be an integer multiple of the set page size (e.g., 10 elements or 16 elements, etc.).

  3. Use 3-8 small arrays (e.g., array B, array C, array D, etc.) to simulate allocated page frames. The size of small arrays equals the page size (e.g., 10 instruction sizes, i.e., 10 elements). The small arrays contain copies of the corresponding page content from the large array (build a page table separately to describe the relationship between pages of the large array and small array indices).

  4. Access array A in different orders, which can be: sequential, jump, branch, loop, or random. Build access orders yourself. Different orders also reflect program locality to some extent.

  5. The access order of the large array can be defined using the rand() function to simulate instruction access sequences corresponding to the access order of large array A. Then transform the instruction sequence into corresponding page address streams and count “page faults” for different page replacement algorithms. Page faults occur when the corresponding “page” is not loaded into small arrays (e.g., array B, array C, array D, etc.).

  6. In the experiment, page size, number of page frames, access order, and replacement algorithm should all be adjustable.

Code Implementation

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 // Process virtual space size
#define PAGE_SIZE 10 // Page frame size
#define NUM_PAGES (ARRAY_SIZE / PAGE_SIZE) // Number of pages in virtual memory
#define NUM_FRAMES 5 // Number of pages in physical memory
#define JUMP_NUM_FRAMES 7 // Number of pages to jump
#define LOOP_TIMES 3 // Number of loop iterations

int main_array[ARRAY_SIZE]; // Process virtual space
int frames[NUM_FRAMES][PAGE_SIZE]; // Physical memory divided into page frames
int page_table[NUM_PAGES]; // Page table entries (mapping from virtual page frame number to physical page frame number)
int frame_table[NUM_FRAMES]; // Frame table (mapping from physical page frame number to virtual page frame number)
int page_faults = 0; // Number of page faults
int algo_choice = 0; // Page replacement algorithm

int frame_pointer = 0; // For FIFO
int frame_queue[NUM_FRAMES]; // For FIFO
int frame_timestamp[NUM_FRAMES]; // For LRU
int future_pages[ARRAY_SIZE]; // For OPT

char *algo_name[4] = {"Random Replacement", "OPT", "FIFO", "LRU"}; // Page replacement algorithm names

// Initialize
void initialize()
{
srand(time(NULL));
// Fill random numbers and initialize
for (int i = 0; i < ARRAY_SIZE; i++)
{
main_array[i] = rand() % 100000;
future_pages[i] = -1;
}
// Initialize page table
for (int i = 0; i < NUM_PAGES; i++)
{
page_table[i] = -1;
}
// Initialize page frame queue and timestamps
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)
{
// Implement OPT algorithm
int frame = -1;
int max_future = -1;
// Find the page frame that won't be used for the longest time in the future
for (int i = 0; i < NUM_FRAMES; i++)
{
// If an empty page frame is found, return directly
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()
{
// Implement FIFO algorithm, relying on queue, first in first out
int frame = frame_pointer;
frame_pointer = (frame_pointer + 1) % NUM_FRAMES;
return frame;
}

int find_frame_lru()
{
// Implement LRU algorithm
int lru = INT_MAX;
int frame = -1;
// Find the least recently used page frame
for (int i = 0; i < NUM_FRAMES; i++)
{
if (frame_timestamp[i] < lru)
{
lru = frame_timestamp[i];
frame = i;
}
}
return frame;
}

// Access page
void access_page(int page_number, int offset, int current_time)
{
// Page fault, use corresponding page replacement algorithm
if (page_table[page_number] == -1)
{
// Find an empty or page frame that should be replaced
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;
}
// Change the previous virtual to physical page table mapping to -1
if (frame_table[frame_number] != -1)
page_table[frame_table[frame_number]] = -1;

printf("\033[1;31mPage Fault\033[0m, Page %3d loaded into frame %2d, data at offset %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;32mPage Hit\033[0m, Page %3d in frame %2d, data at offset %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();

// Prompt user to select page replacement algorithm
printf("Please select page replacement algorithm (1=OPT, 2=FIFO, 3=LRU), default random replacement: ");
scanf("%d", &algo_choice);
if (algo_choice < 1 || algo_choice > 3)
{
printf("Invalid page replacement algorithm option, using default replacement method: random replacement.\n");
algo_choice = 0;
}
printf("Currently using page replacement algorithm: %s\n", algo_name[algo_choice]);

// Prompt user to select page access order
int access_order = 1;
if (algo_choice == 1)
{
// Prompt user to select, reselect if wrong choice
do
{
if (access_order < 1 || access_order > 4)
printf("Invalid access order option, please reselect.\n");
printf("OPT algorithm needs to know future page access order, please select page access order (1=sequential, 2=jump, 3=branch, 4=loop): ");
scanf("%d", &access_order);
} while (access_order < 1 || access_order > 4);
}
else
{
// Prompt user to select, reselect if wrong choice
do
{
if (access_order < 1 || access_order > 5)
printf("Invalid access order option, please reselect.\n");
printf("Please select page access order (1=sequential, 2=jump, 3=branch, 4=loop, 5=random): ");
scanf("%d", &access_order);
} while (access_order < 1 || access_order > 5);
}

// Access pages according to selected access order
switch (access_order)
{
case 1: // Sequential access
// Construct future_pages array
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: // Jump access
// Construct future_pages array
for (int i = 0; i < ARRAY_SIZE; i++)
future_pages[i] = i * JUMP_NUM_FRAMES % ARRAY_SIZE / PAGE_SIZE;
// Access with JUMP_NUM_FRAMES step intervals
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: // Branch access
// Construct future_pages array
for (int i = 0; i < ARRAY_SIZE; i++)
{
// Even page frames sequential access, odd page frames jump access
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: // Loop access
// Construct future_pages array
for (int i = 0; i < ARRAY_SIZE; i++)
future_pages[i] = (i % (ARRAY_SIZE / LOOP_TIMES)) / PAGE_SIZE;
// Loop access loop_times times
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: // Random access
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);
}
}

/* Print future_pages */
// for (int i = 0; i < NUM_PAGES; i++)
// {
// printf("%d ", future_pages[i]);
// }

// Output page fault count
printf("Page access error count: %d, Hit rate: %.2f%%\n", page_faults, (1 - (float)page_faults / ARRAY_SIZE) * 100);

return 0;
}

Program Execution Results

This is the original output example, but for better demonstration, I made some modifications to the program and commented out some output.

Original output

OPT

OPT execution result

FIFO

FIFO execution result

LRU

LRU execution result

Since the OPT algorithm needs to know the future page access order, after selecting the OPT algorithm, there is no option 5=random access.

Task 3: Modify Linux Kernel Page Fault Handling Function to Print Debug Information

Task Requirements

  1. Write 2 applications similar to hello world or simple for loops as test targets.

  2. Add debug information using printk in Linux kernel’s page fault handling function do_no_page() or similar functions (function names vary across versions), print page fault information for specific processes (using program name as filter condition), and count page faults.

  3. You can also add debug information using printk in Linux kernel’s physical page frame allocation function get_free_page() or similar functions (function names vary across versions), print information about new page frame allocation during specific process execution (using program name as filter condition), and count related information.

Modify Kernel Source Code

Add Code ./mm/memory.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Add two system calls for program control, one to set target program name, one to get target program's page fault count
static char target_program[256] = {0}; // target program name
static long page_fault_count = 0; // page fault counts

SYSCALL_DEFINE1(set_target_program_name, const char __user *, program_name) {
// copy name to kernel
if (copy_from_user(target_program,program_name, 255) != 0)
return -EFAULT; // if fail, 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
// Add debug information in do_no_page function
if (strcmp(current->comm, target_program) == 0)
page_fault_count++;

System Call Function Declaration ./include/linux/syscalls.h

1
2
3
// Add declarations for two system calls
asmlinkage long sys_set_target_program_name(const char __user *name);
asmlinkage long sys_get_page_fault_count(void);

System Call ID ./arch/x86/entry/syscalls/syscall_64.tbl

1
2
3
// Add system call numbers for two system calls
448 64 set_target_program_name sys_set_target_program_name
449 64 get_page_fault_count sys_get_page_fault_count

System Call ID Declaration ./include/uapi/asm-generic/unistd.h

1
2
3
4
5
6
// Add declarations for two system calls
#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)

Write Test Code After Kernel Compilation

The compilation will report an error as follows:

Error

Initially I didn’t know what this error meant, and after modifying for a long time without effect, I later added #include <linux/syscalls.h> and it worked. This means that if you define system calls, you must include this header file. But the error message is not very clear, which caused me to waste a lot of time.

Below is test1.c, test2.c just removes the newly added system calls. This is to test that the debug information is controllable.

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>

// Assume these are the numbers for your new system calls
#define SYS_SET_TARGET_PROGRAM_NAME 448
#define SYS_GET_PAGE_FAULT_COUNT 449

const int size = 1000;
int array[1000][1000];

int main() {
// Set target program name
const char *target_program = "test";
syscall(SYS_SET_TARGET_PROGRAM_NAME, target_program);

// Execute operations that may trigger page faults
int i, j;
for (i = 0; i < size; i++) {
for (j = 0; j < size; j++) {
array[i][j] = i + j;
}
}

// Get and output page fault count
syscall(SYS_GET_PAGE_FAULT_COUNT);

return 0;
}

Program Execution Results

Program execution result

As shown in the figure, only the page fault count for the test1 program was printed, while the page fault count for the test2 program was not printed.

Task 4: Calculate VA to PA Mapping using /proc/pid/pagemap in Linux

Task Requirements

  1. Linux’s /proc/pid/pagemap file allows users to view physical addresses and other related information of current process virtual pages. Each virtual page contains a 64-bit value, pay attention to analyzing the 64-bit information.

  2. Get the full name of the current process’s pagemap file

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Example code from ppt
    pid_t pid = getpid();
    sprintf(buf, "%d", pid);
    strcpy(filename, "/proc/");
    strcat(filename, buf);
    strcat(filename, "/pagemap");

    // Can be done in one step
    sprintf(filename, "/proc/%d/pagemap", pid);
  3. Can output virtual addresses, page numbers, physical page frame numbers, physical addresses and other information of one or more global variables or custom functions in the process.

Thinking:

  1. How to extend the experiment (write a general function) to show the physical address corresponding to a specified virtual address of a specified process.

  2. How to extend the experiment to verify that different processes’ shared libraries (e.g., some known, commonly called *.so libraries) have the same physical address.

Code Implementation

program.c implements output of virtual addresses, page numbers, physical page frame numbers, physical addresses and other information of one or more global variables or custom functions.

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(); // Get system-set page size
unsigned long v_pageIndex = vaddr / pageSize; // Calculate number of pages passed relative to 0x0 for this virtual address
unsigned long v_offset = v_pageIndex * sizeof(uint64_t); // Calculate offset in /proc/pid/page_map file
unsigned long page_offset = vaddr % pageSize; // Calculate offset of virtual address within page
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); // Move file pointer to corresponding position in file to get item
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; // Physical page number, i.e., take bits 0-54 of item
unsigned long paddr = (phy_pageIndex * pageSize) + page_offset; // Physical address = physical page number * page size + page offset
printf("【%s】pid = %d, Virtual address = 0x%lx, Page number = %lu, Physical address = 0x%lx, Physical page frame number = %lu\n",
description, getpid(), vaddr, v_pageIndex, paddr, phy_pageIndex);

close(fd);
}

const int a = 100; // Global constant

int main()
{
int b = 100; // Local variable
static int c = 100; // Local static variable
const int d = 100; // Local constant
char *p = (char *)malloc(100); // Dynamic memory

printPhysicalAddress("Global constant", (unsigned long)&a);
printPhysicalAddress("Local variable", (unsigned long)&b);
printPhysicalAddress("Local static variable", (unsigned long)&c);
printPhysicalAddress("Local constant", (unsigned long)&d);
printPhysicalAddress("Dynamic memory", (unsigned long)p);
free(p);
exit(0);

return 0;
}

va2pa.c implements output of physical address corresponding to a specified virtual address of a specified process.

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>

// Logic is the same as above, just pid and vaddr are parameters
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, Virtual address = 0x%lx, Page number = %lu, Physical address = 0x%lx, Physical page frame number = %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 is a test program used to verify that different processes’ shared libraries have the same physical address. For example, the printf function in this program is in the libc.so shared library.

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

Program Execution Results

Must run with sudo, otherwise this file should not have permission to open

program.c

program.c execution result

va2pa.c && test.c

Start two test processes, output the virtual addresses of the printf function respectively. va2pa.c calculates the physical addresses corresponding to the virtual addresses of the printf function for both processes.

va2pa.c program execution result

The two red boxes on the right print the pid of the currently running test program and the virtual address of the printf function, then use the process number and virtual address as parameters for va2pa.c to calculate the physical address. The results are shown in the two boxes on the left. The physical addresses corresponding to the virtual addresses of the printf function for both processes (marked with blue highlighter) are the same, proving that different processes’ shared libraries have the same physical address.