Computer-Science

Memory

1. goal

2. basic solution (paging)

3. process image

1) with printf

ex3.c :

int x;
int y;
void main(){
    y=x+3;
    printf("x:%p y:%p main:%p\n", &x, &y, main);
    for(;;);
}

Compile:

$ gcc -o ex3 ex3.c

run:

$ ./ex3&
x:0x804a01c y:0x804a020 main:0x80483b4
[1] 4693

2) /proc/xxx/maps shows the layout of a process whose pid=xxx (For mor detailed layout, type readelf -S ex3: .text is code section, .data is initialized data section, .bss is uninitialized data section)

example)

$ ./ex3 &
$ ps
  PID TTY          TIME CMD
4601 tty1    00:00:00 bash
4693 tty1    00:02:24 ex3
4694 tty1    00:00:00 ps
$ cat /proc/4693/maps
Start      end       perm offset    dev   inode       file
08048000-08049000 r-xp 00000000 08:03 614956     /root/ex3
08049000-0804a000 r--p 00000000 08:03 614956     /root/ex3
0804a000-0804b000 rw-p 00001000 08:03 614956     /root/ex3
b7de7000-b7de8000 rw-p b7de7000 00:00 0
b7de8000-b7f12000 r-xp 00000000  08:03 113693    /lib/libc-2.6.1.so
b7f12000-b7f14000 r--p 0012a000  08:03 113693    /lib/libc-2.6.1.so
b7f14000-b7f15000 rw-p 0012c000  08:03 113693    /lib/libc-2.6.1.so
..........................

(hw 1-1) Draw the memory map (process image) of the following program (ex1.cpp). What are the starting addresses of the code, data, heap, stack segment of this program and how many pages each segment occupies? What is the address of main function, the addresses of the global variables and local variables?

ex1.cpp :

#include <stdio.h>
int x;
int y[10000];
int main(){
   int k;
   int *pk;
   pk=new int;
   printf("ex1. &main:%p &x:%p &y:%p &y[9999]:%p &k:%p &pk:%p pk:%p\n", main,&x,&y,&y[9999],&k,&pk,pk);
   for(;;);  // to see memory map of this process
   return 0;
}

printf์˜ ์ถœ๋ ฅ ๊ฒฐ๊ณผ๋Š” Logical ์ฃผ์†Œ๋ฅผ ์˜๋ฏธํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋˜๋Š” ์œ„์น˜๋ฅผ ์•Œ๊ธฐ ์œ„ํ•ด์„œ๋Š” Process Table์„ ์—ด์–ด Physical ์ฃผ์†Œ๋ฅผ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.

$ g++  -o ex1 ex1.cpp
$ ./ex1 &
$ cat /proc/(pid of ex1)/maps > x1
$ vi x1


Segment Address Identifier
Code 08048000-08049000 &main
Read-Only Data 08049000-0804a000 ย 
Data 0804a000-0804b000 &x, &y
Heap 0804b000-08075000, b7e22000-b7e23000 &y[9999], pk
C Library Code b7e23000-b7f4d000 ย 
C Library Data b7f4d000-b7f50000 ย 
Library Loader b7f58000-b7f74000 ย 
Stack bfc5f000-bfc74000 &k, &pk

(hw 1-2) Write another simple program, ex2.cpp (see below), and run ex2, ex1 at the same time. Confirm they have the same address for main function. How can they run at the same location at the same time?

ex2.cpp :

#include <stdio.h>
int x1;
int main(){
   int *pk1;
   pk1 = new int;
   printf("ex2. &main:%p &x1:%p\n", main,&x1);
   for(;;);  // to see memory map of this process
   return 0;
}
$ g++ -o ex2 ex2.cpp
$ ./ex2 &
...........
$ ./ex1 &
...........
$ ps
.............. ex2
.............. ex1

ex1๊ณผ ex2์˜ main ์ฃผ์†Œ๊ฐ€ 0x80484f4๋กœ ๋˜‘๊ฐ™์ด ์ถœ๋ ฅ๋œ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ƒ์„ฑํ•œ logical address์ด๋‹ค.
ps ๋ช…๋ น์–ด๋กœ PID๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด, ์‹ค์ œ memory์— load๋œ physical address๋Š” ์„œ๋กœ ๋‹ค๋ฅด๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
๋”ฐ๋ผ์„œ ex1๊ณผ ex2๋Š” ๋™์‹œ์— ์‹คํ–‰์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

4. similarity with file system

4.1) FS and MM

The goal is similar, and the basic solution is also similar. For a file, all information about a particular file is stored at the corresponding inode{}; for a process, all information is stored at the corresponding task_struct{}. For a file, the block location of this file is stored in inode->i_private->i_block[] table; for a process, the page location of this process is stored in task_struct->mm->pgd table.

example)

(hw 2) Show the memory map of the following program. Which pages does the program access during the run time? Show the page numbers that the program accesses in the order they are accessed. Indicate which pages are for code and which are for global data and which are for local data. To predict page numbers, you need to print the address of main, variable i and j, and the address of A[0], A[1], .., A[4].

ex4.c:

#include <stdio.h>
int A[5][1024];
int main()
{
    int i, j;
    printf("ex4. &main:%p &i:%p &j:%p\n", main, &i, &j);
    for (i = 0; i < 5; i++)
    {
        printf("   A[%d][0]:%p\n", i, &A[i][0]);
        printf("A[%d][1023]:%p\n", i, &A[i][1023]);
        for (j = 0; j < 1024; j++)
            A[i][j] = 3;
    }
    for (;;);
}
$ ./ex4 &

Segment Page Identifier
Code 8048-8049 &main
Data 804a-804b &A
Heap 804b-8050 &A[4][1024]
Stack bfc5d-bfc72 &i, &j

๊ธฐ๋ณธ์ ์œผ๋กœ Page์˜ ํฌ๊ธฐ๋Š” 4KB์ด๊ธฐ ๋•Œ๋ฌธ์— โ€œ/proc/pid/mapsโ€์—์„œ 0x1000๋‹น ํ•œ Page๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์‹œ์ž‘๊ณผ ๋ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ์—์„œ ๋’ค 3์ž๋ฆฌ๋ฅผ ์ œํ•˜๋ฉด Page ๋ฒˆํ˜ธ๋ฅผ ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค. ํ”„๋กœ๊ทธ๋žจ์€ Code(main) -> Data(์ „์—ญ๋ณ€์ˆ˜) -> Heap(์ „์—ญ๋ณ€์ˆ˜) -> Stack(์ง€์—ญ๋ณ€์ˆ˜) ์ˆœ์œผ๋กœ ์ ‘๊ทผํ•œ๋‹ค.

4.2) Address mapping (assume 1block=1page=4K)

FS:

      x=open("/aa/bb", ...);     // assume /aa/bb is at disk block 100, 101
      lseek(x, 4098, SEEK_SET);
      read(x, buf, 10);

MM:

    y = x + 3;       // assume x is at 0x804a040 (logical address) and stored at
                     // 0x7000040 (physical address)

5. Three problems with paging

1) Process is much bigger than a file while the memory is much smaller than a disk.

2) Page table itself is also very large

3) Address mapping is becoming slower

6. Solution

1) process size too big

example of VMA list:

xx.c :

#include <stdio.h>
#include <stdlib.h>
int i=1; int j=10; int k[10000];
int main(){
   char * x;
   j=i+5;
   x = (char *)malloc(1024*1024);
   printf("x: %p &k[0]:%p &k[9999]:%p &i:%p &j:%p\n", x, &k[0],&k[9999], &i, &j);
   for(;;);
   return 0;
}

Compile above to get an executable file xx.

$ gcc -o xx xx.c
$ ./xx &
.................
[1] 23277

Get the process image of xx.

$ cat /proc/23277/maps
08048000-08049000 r-xp 00000000 03:06 614954     /home/kchang/xx
08049000-0804a000 r--p 00000000 03:06 614954     /home/kchang/xx
0804a000-0804b000 rw-p 00000000 03:06 614954     /home/kchang/xx  -- i, j, k[]
0804b000-08054000 rw-p 00001000 00:00 0            -- k[]
b7cc7000-b7dc9000 rw-p 0804b000 00:00 0             -- x[]
.........................

Now the VMA list is

hw 3) How many page faults will the program in hw 2) generate? Explain your reasoning. Remember in the beginning the system has no page of the current process in the memory.

Page Faults๋Š” ์•„์ง frame์— ํ• ๋‹น๋˜์ง€ ์•Š์€ ์ƒˆ๋กœ์šด page์— ์ ‘๊ทผํ•  ๋•Œ๋งˆ๋‹ค ๋ฐœ์ƒํ•œ๋‹ค. ๋”ฐ๋ผ์„œ A[][]๋ฅผ ์„ ์–ธ ์‹œ 1๋ฒˆ, main์— 1๋ฒˆ, i&j์— 1๋ฒˆ, loop ์•ˆ์—์„œ A[][]์— ์ ‘๊ทผํ•  ๋•Œ 5๋ฒˆ, ์ด 8๋ฒˆ์˜ Page Faults๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

hw 4) Confirm your answer in hw 3) by defining a new system call, my_sys_show_pfcnt(), in mm/mmap.c, which displays the number of page faults generated so far. The pfcnt should be increased by one whenever there is a page fault. Remember a page fault will raise INT 14 which will be processed by page_fault() in arch/x86/kernel/entry_32.S, which in turn calls do_page_fault() in arch/x86/mm/fault.c. Define โ€œint pfcnt=0โ€ in this file and increase it inside do_page_fault(). Now you call this system call before and after the double loop in hw 2) and see the difference.

arch/x86/kernel/syscall_table_32.S :

58๋ฒˆ์— my_sys_show_pfnt๋ฅผ ํ• ๋‹นํ•˜์˜€๋‹ค.

mm/mmap.c :

......
extern int pfcnt;
void sys_show_pfcnt() {
  printk("page fault count so far: %d\n", pfcnt);
}
......

arch/x86/mm/fault.c :

ํŒŒ์ผ๋“ค์„ ์ˆ˜์ • ํ›„์— ์ปดํŒŒ์ผ ๋ฐ ์žฌ๋ถ€ํŒ…ํ•œ๋‹ค.

$ make bzImage
$ cp arch/x86/boot/bzImage /boot/bzImage
$ reboot

ex5.c :

Page fault count๋Š” loop๊ฐ€ ๋๋‚œ ํ›„, 5 ์ฆ๊ฐ€ํ•œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ „์—ญ๋ณ€์ˆ˜๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์œ„ํ•ด A[0]๊ฐ€ ์œ„์น˜ํ•œ 804a Page๋ฅผ ์ œ์™ธํ•˜๊ณ  A[4][1024]๊นŒ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์œ„ํ•ด์„œ๋Š” 804f๊นŒ์ง€์˜ 5๋ฒˆ์˜ Page Fault๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

hw 4-1) You can display the exact address where page fault has happend. Make ex3.c and insert following code in arch/mm/fault.c:do_page_fault(). When you run ex3, the kernel will display the page fault addresses generated by ex3. Explain the result. Also confirm your answer in hw 3) again by examining page fault addresses.

ex3.c :

int x;
void main(){
    x=3;
}

arch/x86/mm/fault.c :

void do_page_fault(...........){
        ...........
        /* get the address */
        address = read_cr2();
        if (strcmp(tsk->comm, "ex3")==0){
            printk("pg fault for ex3 at:%p\n", address);
        }
        ..............

ํŒŒ์ผ๋“ค์„ ์ˆ˜์ • ํ›„์— ์ปดํŒŒ์ผ ๋ฐ ์žฌ๋ถ€ํŒ…ํ•œ๋‹ค.

$ make bzImage
$ cp arch/x86/boot/bzImage /boot/bzImage
$ reboot




do_page_fault ํ•จ์ˆ˜์— ํ˜„์žฌ ์‹คํ–‰ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ ์ด๋ฆ„์ด โ€œex3โ€์ด๋ฉด Page Fault ์ฃผ์†Œ๋ฅผ ์ถœ๋ ฅํ•˜๋„๋ก ์ปค๋„ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค. ํ”„๋กœ๊ทธ๋žจ์ด ์‹œ์ž‘๋˜๋ฉด Code(804a) Page๋ฅผ ๋จผ์ € ์ฝ๊ณ , libc ๋“ฑ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ €์žฅ๋œ ํŽ˜์ด์ง€๋ฅผ ์ฝ๋Š”๋‹ค. ๊ทธ๋ฆฌ๊ณ  Heap ์˜์—ญ์„ ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ฆฐ๋‹ค. ํ”„๋กœ๊ทธ๋žจ์˜ ์ฝ”๋“œ์— ๋น„ํ•ด ๋งŽ์€ Page Fault๊ฐ€ ์ƒ์„ฑ๋œ ์ด์œ ๋Š” mainํ•จ์ˆ˜๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์–ด ํ˜ธ์ถœ๋˜๊ธฐ๊นŒ์ง€์˜ ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ์ด ํฌํ•จ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

hw 4-2) Repeat hw 4) with modified hw 2) code as below. Why is pfcnt increased?

ex6.c :

#include <stdio.h>
int A[5][1024];
int main()
{
    int i, j;
    syscall(58);
    for (i = 0; i < 5; i++)
    {
        printf("A[%d][0]:%p\n", i, &A[i][0]);
        for (j = 0; j < 1024; j++)
            A[i][j] = 3;
    }
    syscall(58);
    for (;;);
}

5์—์„œ 722483-722459=24๋กœ ๊ฐ’์ด ์ฆ๊ฐ€ํ•œ ์ด์œ ๋Š” printf ํ˜ธ์ถœ์—์„œ ์ถ”๊ฐ€์ ์ธ Page Fault๊ฐ€ ์ผ์–ด๋‚ฌ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

hw 5) Make a system call that prints vma information of the current process, and write a user program that displays the VMA list with it. Confirm that this result matches to those in โ€œ/proc/ex7โ€™s_pid/mapsโ€.

ํŒŒ์ผ๋“ค์„ ์ˆ˜์ • ํ›„์— ์ปดํŒŒ์ผ ๋ฐ ์žฌ๋ถ€ํŒ…ํ•œ๋‹ค.

$ make bzImage
$ cp arch/x86/boot/bzImage /boot/bzImage
$ reboot


VMA List๋Š” Linked List ํ˜•ํƒœ๋กœ ๊ตฌํ˜„๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— temp = temp->vm_next๋กœ ๋‹ค์Œ ๊ฐ’์„ ์ง€์ •ํ•˜๋ฉฐ NULL์ด ๋  ๋•Œ๊นŒ์ง€ ์ด๋ฅผ ๋ฐ˜๋ณตํ•œ๋‹ค. vm_file ๊ฐ’์ด NULL์ผ ๊ฒฝ์šฐ์—๋Š” name์„ ์ถœ๋ ฅํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ถœ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด vDSO๋ถ€๋ถ„์„ ์ œ์™ธํ•˜๊ณ  โ€œ/proc/ex7โ€™s_pid/mapsโ€์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

hw 6) Count the number of page faults when you run following ex1 and ex2 by using sys_show_pfcnt(). Explain the results. Also compare the running time of each code (use gettimeofday() function) and explain why they differ. Run several times and compute the average.

ex8.c :

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
int A[8192][8192];

double getUnixTime()
{
    struct timeval tv;
    gettimeofday(&tv, (void *)NULL);         // get current time
    return (tv.tv_sec + tv.tv_usec / 1.0e6); // return it in seconds
}

int main()
{
    int i, j;
    double stime, etime, diff;

    stime = getUnixTime(); // starting time
    syscall(58);           // display pfcnt

    for (i = 0; i < 8192; i++)
        for (j = 0; j < 8192; j++)
            A[i][j] = 3;

    syscall(58);           // display pfcnt again
    etime = getUnixTime(); // ending time
    diff = etime - stime;  // the difference
    printf("The elapsed time: %f\n", diff);

    return 0;
}

ex9.c :

// same as `ex8.c` except
// change A[i][j]=3; to A[j][i]=3;
// (If your vm dies, reduce the array size)

Program Page Fault Elapsed Time
ex8 65,541 0.669330
ex9 74,125 1.765722

ex8๊ณผ ex9์˜ ์ฐจ์ด์ ์€ A[i][j]์™€ A[j][i]์ด๋‹ค. ex9์—์„œ ๋” ๋งŽ์€ Page Fault๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”๋ฐ, ex8์€ A์— ๋ฉ”๋ชจ๋ฆฌ ์ ์žฌ๋œ ์ˆœ์„œ๋Œ€๋กœ ์ ‘๊ทผํ•œ ๋ฐ˜๋ฉด, ex9๋Š” ์ˆœ์„œ๋Œ€๋กœ ์ ‘๊ทผํ•˜์ง€ ์•Š์•˜๊ธฐ(์ฐธ์กฐ์˜ ์ง€์—ญ์„ฑ์„ ๋งŒ์กฑํ•˜์ง€ ์•Š์•˜๊ธฐ) ๋•Œ๋ฌธ์— LRU ์•Œ๊ณ ๋ฆฌ์ฆ˜์— ์˜ํ•ด ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ Page๋ฅผ ์‚ญ์ œํ•˜๊ณ  ๋ฉ”๋ชจ๋ฆฌ์—์„œ ๋‚ด๋ ค๊ฐ„ Page๋ฅผ ์ƒˆ๋กœ ์ฝ์–ด์˜ค๋Š” ์ž‘์—…์ด ๋ฐ˜๋ณต๋˜์–ด ๋” ๋งŽ์€ Page Fault๊ฐ€ ์ผ์–ด๋‚ฌ๋‹ค.

Page Fault ๊ณผ์ •์€ ๋ฐœ์ƒํ•œ ํšŒ์ˆ˜๋งŒํผ ํ”„๋กœ๊ทธ๋žจ์˜ ์„ฑ๋Šฅ์„ ์ €ํ•˜์‹œํ‚ค๋ฏ€๋กœ ex9์ด ex8๋ณด๋‹ค ๋Š๋ฆฌ๊ฒŒ ์‹คํ–‰์„ ๋๋งˆ์ณค๋‹ค.

2) page table size too big

solution: two-level paging

example)

Suppose we have a process which has only two pages(page 1 and 2099) stored in the memory as below:

The page table should look like as the left picture in below:

Since storing this entire page table into memory is inefficient, we divide the page table into a number of pages(which are called directories), store only the active directories, and store the directory table which shows which directory is stored in which frame. In above, we can see only dir0 and dir2 are stored in frame 14 and frame 74 respectively. The final memory that contains our process and page table looks as below:

1-level paging requires 2+1024=1026 frames while 2-level paging requires only 5 frames.

3) logical address to physical address mapping becomes slow

solution: use TLB(Translation Lookaside Buffer) cache

7. Implementation of paging in Linux

7.1. process image, page table, page frame

1) process image: current->mm->mmap (VMA list)

struct mm_struct{
    .............
    struct vm_area_struct *mmap;
    ..........
};
struct vm_area_struct{
        .........
        unsigned long  vm_start;
        unsigned long  vm_end;
        struct file *     vm_file;
        struct vm_area_struct *vm_next;
        .........
}

7.2) page table: current->mm->pgd

pgd is a pointer to Page Global Directoy.

struct mm_struct{
      .............
      pgd_t * pgd;
      ..........
};

pgd[x] has the physical address of the frame where page table x resides.

hw 7) Make a system call, sys_get_phyloc(), which will display the physical address of main().

arch/x86/kernel/syscall_table_32.S :

1) Write a simple program that prints the address of main().

hw7-1.c :

2) Call sys_get_phyloc(main) in this program which passes the address of main.

hw7-2.c :

3) sys_get_phyloc(addr) is a system call that performs following steps in order:

mm/mmap.c :


ํŒŒ์ผ์„ ์ˆ˜์ • ํ›„์— ์ปดํŒŒ์ผ ๋ฐ ์žฌ๋ถ€ํŒ…ํ•œ๋‹ค.

$ make bzImage
$ cp arch/x86/boot/bzImage /boot/bzImage
$ reboot

๋ฐ”๋กœ ์ง์ „ 7-2 ๋ฌธ์ œ์—์„œ ๋งŒ๋“  hw7-2 ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰์‹œ์ผฐ๋‹ค.

$ echo 8 > /proc/sys/kernel/printk
$ ./hw7-2

$ objdump -d ./hw7-2

main์˜ ์ฒซ ์ฃผ์†Œ์™€ objdump๋กœ ์ถœ๋ ฅํ•œ ์ฃผ์†Œ ๊ฐ’์ด 0x80483f4๋กœ ๊ฐ™์€ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

7.3) page frame table: mem_map

struct page * mem_map; // array of struct page

page{} is a page descriptor: it has the information of the corresponding page frame.

Each descriptor is 32 bytes. For 4GB ram, we have 232/212=220 frames. This means we need 220 memory descriptors, which will require 2*_20 _ 32 bytes=32 MB.

struct page {
   unsigned long flags; // property of this frame
   atomic_t  _count;  // page frame's reference number. -1 if free
   .....
}

struct mm_struct{
   unsigned long nr_ptes; // number of page tables(directories) this process is using
   .........
}

7.4) process address space =

user-mode address space(3GB)+ kernel-mode address space(1GB)

kernel-mode address space starts at 0xc0000000, and each page there maps each frame in the physical memory. So we can read the physical memory via kernel-mode address space.

example)

ex1.c :

void main(){
    write(...);
    ....
}
$ ./ex1

=> write(โ€ฆ..) => โ€ฆ. => sys_write(โ€ฆ.) => call 0xc015d04b (assuming the address of sys_write is 0xc015d04b)

Now cpu has to jump to 0xc015d04b which is a virtual address. The cpu looks at ex1โ€™s page table since โ€œcurrentโ€ is still ex1. How ex1 knows the physical location of sys_write? Because of this problem, Linux maps all the physical address of the memory page frames to the virtual address starting at 0xc0000000. For example, the above virtual address 0xc015d04b maps to physical address 0xc015d04b - 0xc0000000 = 0x015d04b.