CPU                  CPU                  Bus
           Virtual              Physical             Address
           Address              Address               Space
            Space                Space

          +-------+             +------+             +------+
          |       |             |MMIO  |   Offset    |      |
          |       |  Virtual    |Space |   applied   |      |
        C +-------+ --------> B +------+ ----------> +------+ A
          |       |  mapping    |      |   by host   |      |
+-----+   |       |             |      |   bridge    |      |   +--------+
|     |   |       |             +------+             |      |   |        |
| CPU |   |       |             | RAM  |             |      |   | Device |
|     |   |       |             |      |             |      |   |        |
+-----+   +-------+             +------+             +------+   +--------+
          |       |  Virtual    |Buffer|   Mapping   |      |
        X +-------+ --------> Y +------+ <---------- +------+ Z
          |       |  mapping    | RAM  |   by IOMMU
          |       |             |      |
          |       |             |      |
          +-------+             +------+
          
  0x7f...
  0xffff...
  같은 주소 공간은 virtual addrsss(가상주소)이다.
  메모리 가상화는 프로세스의 물리 주소 공간이 연속적이지 않더라도
  이를 연속적인 메모리 공간인 것처럼 접근 가능하게 하여 단편화 문제를 해결한다.
  
  이를 위해 paging과 페이지 교체 알고리즘을 도입한다.
  
  물리 메모리는 4KB 크기의 page로 구분된다. 각 page는 page frame number를 통해 구분되고,
  페이지 내부의 위치는 page offset을 사용해 구분한다. 이 둘을 합쳐서 CPU 물리 주소가 만들어진다.
  CPU는 페이지 테이블을 통해 가상 주소를 물리 주소로 변환한다.
  

www.kernel.org

pagemap, from the userspace perspective
---------------------------------------

pagemap is a new (as of 2.6.25) set of interfaces in the kernel that allow
userspace programs to examine the page tables and related information by
reading files in /proc.

There are four components to pagemap:

 * /proc/pid/pagemap.  This file lets a userspace process find out which
   physical frame each virtual page is mapped to.  It contains one 64-bit
   value for each virtual page, containing the following data (from
   fs/proc/task_mmu.c, above pagemap_read):

    * Bits 0-54  page frame number (PFN) if present
    * Bits 0-4   swap type if swapped
    * Bits 5-54  swap offset if swapped
    * Bit  55    pte is soft-dirty (see Documentation/vm/soft-dirty.txt)
    * Bit  56    page exclusively mapped (since 4.2)
    * Bits 57-60 zero
    * Bit  61    page is file-page or shared-anon (since 3.5)
    * Bit  62    page swapped
    * Bit  63    page present

   Since Linux 4.0 only users with the CAP_SYS_ADMIN capability can get PFNs.
   In 4.0 and 4.1 opens by unprivileged fail with -EPERM.  Starting from
   4.2 the PFN field is zeroed if the user does not have CAP_SYS_ADMIN.
   Reason: information about PFNs helps in exploiting Rowhammer vulnerability.

   If the page is not present but in swap, then the PFN contains an
   encoding of the swap file number and the page's offset into the
   swap. Unmapped pages return a null PFN. This allows determining
   precisely which pages are mapped (or in swap) and comparing mapped
   pages between processes.

   Efficient users of this interface will use /proc/pid/maps to
   determine which areas of memory are actually mapped and llseek to
   skip over unmapped regions.
//mmu.c
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <inttypes.h>
 

#define PAGE_SHIFT  ???
#define PAGE_SIZE   (1 << PAGE_SHIFT)
#define PFN_PRESENT  ???
#define PFN_PFN      ???
 
int fd;

/*
	Get page offset of given address
	addr: under 32bit of guest virtual address
	return : page offset == under ??bit of given address
*/ 
uint32_t page_offset(uint32_t addr)
{
    return addr & ((1 << PAGE_SHIFT) - 1);
}

/*
	Get page frame number of given address
	addr: guest virtual address
	return : page frame number of given address
*/ 
uint64_t gva_to_gfn(void *addr)
{
    uint64_t pme, gfn;
    size_t offset;
    
    //get PFN offset in the /proc/self/pagemap
    /*
	    0010 0000 0000 >> 9 == 1
	    
	    x & ~7 == x & 1000 == 8bit align
	    
	    -> page frame offset 
	    63           11   9        0
	    +--------------------------+
	    |            |000 |////////|
	    +--------------------------+
	    
    */
    offset = ((uintptr_t)addr >> 9) & ~7;
    lseek(fd, offset, SEEK_SET);
    read(fd, &pme, 8);
    if (!(pme & PFN_PRESENT))
        return -1;
    gfn = pme & PFN_PFN;
    return gfn;
}

/*
	Change guest virtual address to guest physical address.
	addr : guest virtual address
	return : guest physical address
*/ 
uint64_t gva_to_gpa(void *addr)
{
    uint64_t gfn = gva_to_gfn(addr);
    assert(gfn != -1);
    return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
}
 
int main()
{
		/*
		TC for getting physical address of given string in heap.
		
		1) open /proc/self/pagemap
		2) allocate string in heap
		3) get physical address
		*/
    uint8_t *ptr;
    uint64_t ptr_mem;
 
		// 1) open /proc/self/pagemap
    fd = open("/proc/self/pagemap", O_RDONLY);
    if (fd < 0) {
        perror("open");
        exit(1);
    }
		 
		// 2) allocate string in heap 
    ptr = malloc(256);
    strcpy(ptr, "Where am I?");
    printf("%s\\n", ptr);
    
    // 3) get physical address
    ptr_mem = gva_to_gpa(ptr);
    printf("Your physical address is at 0x%"PRIx64"\\n", ptr_mem);
 
    return 0;
}