Skip to content

Add a system call in Linux Kernel that get physical addresses from virtual addresses

License

Notifications You must be signed in to change notification settings

gary7102/Linux-get-physical-address

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Intro

2024 Fall NCU Linux OS Project 1

  • Add a system call that get physical addresses from virtual addresses
  • 介紹 copy_from_usercopy_to_user 使用方法
  • 使用Copy on Write 機制來驗證system call 正確呼叫
  • 介紹 Demand Paging 在 memory 中的使用時機

Demo問題可參考這篇github,好讀版hackmd

Environment

OS: Ubuntu 22.04
ARCH: X86_64
Kernel Version: 5.15.137

copy_from_usercopy_to_user

copy_from_user

根據bootlin

unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);

這個函數的功能是將user space的資料複製到kernel space。其中:
to: 目標位址,是kernel space中的一個指標,用來存放從user space 複製過來的資料。
from:來源位址,是user space中的一個指標,指向需要被複製的資料(ex: point to virtual address)。
n: 要傳送資料的長度
傳回值: 0 on success, or the number of bytes that could not be copied.

copy_to_user

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);

這個函數的功能是將kernel space的資料複製到user space variable。其中:
to: 目標地址(user space)
from: 複製地址(kernel space)
n: 要傳送資料的長度
傳回值: 0 on success, or the number of bytes that could not be copied.

Purpose

  • Prevents crashes due to invalid memory access.
  • Maintains security by ensuring memory access respects user-space permissions.
  • Enables error handling by providing feedback on failed memory operations.

:::success 這兩個 function 都是在 kernel space 中使用 :::

Example

新增一個system call 作為範例

#include <linux/kernel.h>       
#include <linux/syscalls.h>     
#include <linux/uaccess.h>      // For copy_from_user and copy_to_user

SYSCALL_DEFINE2(get_square, int __user *, input, int __user *, output) {
    int kernel_input;
    int result;

    // Copy the input value from user space to kernel space
    if (copy_from_user(&kernel_input, input, sizeof(int))) {
        return -EFAULT; // Return error if copy fails
    }

    // Calculate the square
    result = kernel_input * kernel_input;

    // Copy the result back to user space
    if (copy_to_user(output, &result, sizeof(int))) {
        return -EFAULT; // Return error if copy fails
    }

    return 0; // Return success
}

User code

#include <stdio.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <errno.h>

int main() {
    int input;
    int output;

    printf("Enter an integer: ");
    if (scanf("%d", &input) != 1) {
        fprintf(stderr, "Invalid input\n");
        return 1;
    }

    // Call the system call with pointers to input and output
    long result = syscall(451, &input, &output);

    if (result == -1) {
        perror("syscall failed");
    }else{
        printf("Input: %d, Output (Square): %d\n", input, output);
    }

    return 0;
}

line 17傳入&input&output
分別對應system call 的int __user *, inputint __user *, output,若正確複製則回傳值為0
而user space的output已經在copy_to_user()時寫入新資料。

執行結果 :
image

system call 正確呼叫且輸出計算結果

實作system call

Page Table in Linux

Page table 一般來說可以分為兩種結構,32 bit cpu使用4-level(10-10-12)或是 64 bit cpu使用5-level(9-9-9-9-12,加起來只有 48 因為最高的 16 位是sign extension)的架構,但也有3-level的結構,這可以透過 config 內的 CONFIG_PGTABLE_LEVELS 設定,基本上是基於處理器架構在設定的

  • Structure of page tables
    • PGD (Page Global Directory)
    • P4D (Page 4 Directory,5-level 才有)
    • PUD (Page Upper Directory)
    • PMD (Page Middle Directory)
    • PTE (page table entry)

使用4-level page table 為例:

linux_paging

可以看到Page table的base address 是存放在 CR3(又稱 PDBR,page directory base register)這個register,存放的是physical address。但我們需要的是他的virtual address,因此,使用 task_struct->mm->pgd 內儲存的則是 Process Global Directory(PGD) 的virtual address,

補充: 甚麼是task_structmm_struct可以參考下方 what is mm_struct?

每個process有各自的page table,每當context switch發生時,CR3會載入新的page table base addr.,且CR3寫入時,TLB會被自動刷新,避免用到上一個process之TLB。

因此要從logical address轉換為physical address,需要一層一層下去查表, 順序為: pgd_t -> p4d_t -> pud_t -> pmd_t -> pte_t

其中舉例,若要查p4d的base address則需要pgd_t + p4d_index

pgd_t *pgd;
p4d_t *p4d;

pgd = pgd_offset(current->mm, vaddr);
p4d = p4d_offset(pgd, vaddr);

同理,若要查pte的base address則需要pmd_t + ptd_index

ptd_t *pte;

pte = pte_offset(pmd, vaddr);

我們可以直接到 bootlin 中看到這些offset function 的實作細節

// include/linux/pgtable.h line 88

#ifndef pte_offset_kernel
static inline pte_t *pte_offset_kernel(pmd_t *pmd, unsigned long address)
{
        return (pte_t *)pmd_page_vaddr(*pmd) + pte_index(address);
}
#define pte_offset_kernel pte_offset_kernel
#endif

//...

// line 106
/* Find an entry in the second-level page table.. */
#ifndef pmd_offset
static inline pmd_t *pmd_offset(pud_t *pud, unsigned long address)
{
        return pud_pgtable(*pud) + pmd_index(address);
}
#define pmd_offset pmd_offset
#endif

#ifndef pud_offset
static inline pud_t *pud_offset(p4d_t *p4d, unsigned long address)
{
        return p4d_pgtable(*p4d) + pud_index(address);
}
#define pud_offset pud_offset
#endif

static inline pgd_t *pgd_offset_pgd(pgd_t *pgd, unsigned long address)
{
        return (pgd + pgd_index(address));
};

/*
 * a shortcut to get a pgd_t in a given mm
 */
#ifndef pgd_offset
#define pgd_offset(mm, address)		pgd_offset_pgd((mm)->pgd, (address))
#endif

對應到前一張圖,找到前一層的Directory offset再加上當前Directory的 index,一層一層去找

不過發現p4d_offset的實作細節沒有出現在這,但是pud_offset傳入的參數卻是p4d_t *p4d,後來在arch/x86/include/asm/pgtable.h line 926中找到

// arch/x86/include/asm/pgtable.h line 926

/* to find an entry in a page-table-directory. */
static inline p4d_t *p4d_offset(pgd_t *pgd, unsigned long address)
{
        if (!pgtable_l5_enabled())
                return (p4d_t *)pgd;
        return (p4d_t *)pgd_page_vaddr(*pgd) + p4d_index(address);
}

根據上述對linux中page table介紹,便可以寫出page table walk 的程式碼

Page Table walk 實作

新增一個檔案叫 project1.c,路徑為 kernel/project1.c :::spoiler 範例

#include <linux/syscalls.h>
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/sched.h>
#include <linux/pid.h>
#include <linux/mm_types.h>
#include <asm/pgtable.h>

SYSCALL_DEFINE2(my_get_physical_addresses,
                void *, user_vaddr, 
                unsigned long *, user_paddr) {
    
    unsigned long vaddr;
    unsigned long paddr = 0;
    pgd_t *pgd;
    p4d_t *p4d;
    pud_t *pud;
    pmd_t *pmd;
    pte_t *pte;
    unsigned long page_addr = 0;
    unsigned long page_offset = 0;

    // Copy the virtual address from user space to kernel space
    if (copy_from_user(&vaddr, user_vaddr, sizeof(unsigned long))) {
        printk("Error: Failed to copy virtual address from user space\n");
        return -EFAULT;
    }

    // Get the PGD (Page Global Directory) for the current process
    pgd = pgd_offset(current->mm, vaddr);
    if (pgd_none(*pgd) || pgd_bad(*pgd)) {
        printk("PGD entry not valid or not present\n");
        return -EFAULT;    // #define	EFAULT		14	 /*Bad address*/
    }

    // Get the P4D (Page 4 Directory)
    p4d = p4d_offset(pgd, vaddr);
    if (p4d_none(*p4d) || p4d_bad(*p4d)) {
        printk("P4D entry not valid or not present\n");
        return -EFAULT;
    }
    // Get the PUD (Page Upper Directory)
    pud = pud_offset(p4d, vaddr);
    if (pud_none(*pud) || pud_bad(*pud)) {
        printk("PUD entry not valid or not present\n");
        return -EFAULT;
    }

    // Get the PMD (Page Middle Directory)
    pmd = pmd_offset(pud, vaddr);
    if (pmd_none(*pmd) || pmd_bad(*pmd)) {
        printk("PMD entry not valid or not present\n");
        return -EFAULT;
    }

    // Get the PTE (Page Table Entry)
    pte = pte_offset_kernel(pmd, vaddr);
    if (!pte_present(*pte)) {
        printk("Page not present in memory\n");
        return -EFAULT;
    }

    // Compute physical address from PTE
    page_addr = pte_val(*pte) & PTE_PFN_MASK;
    page_offset = vaddr & ~PAGE_MASK;
    paddr = page_addr | page_offset;

    // Copy the result back to user space
    if (copy_to_user(user_paddr, &paddr, sizeof(unsigned long))) {
        printk("Error: Failed to copy physical address to user space\n");
        return -EFAULT;
    }

    return 0;
}

:::

地址轉換trace code:

第一層轉換PGD:

目標 : 回傳PGD entry的virtual address

程式碼:

pgd = pgd_offset(current->mm, vaddr);

trace code:

image

image

current->mm->pgd找出PGD的base address再加上pgd_index 計算出pgd entry的虛擬位置,回傳指標。

:::success

How to get pgd_index?

根據 bootlin

#ifndef pgd_index
/* Must be a compile-time constant, so implement it as a macro */
#define pgd_index(a)        (((a) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
#endif

其中

  • #define pgd_index(a):定義 pgd_index Macro,接受一個參數 a,代表一個virtual address
  • (((a) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1)):這是用來計算 a 在 PGD 中的index的表達式。

舉例:
在x86_64架構的 PGDIR_SHIFT 為 39 (48 - 9), 且PTRS_PER_PGD 為 512,那麼 pgd_index(a) 的操作流程如下:

  • 將虛擬地址 a 右移 39 位,提取出對應 PGD 的高位部分
  • 將結果與 511PTRS_PER_PGD - 1)做 bitwise &,確保index在有效範圍內

得到的結果即為 virtual address apgd_index, 並且可以依此類推到 p4d_indexpud_indexpmd_indexpte_index的計算方法 :::

第二層轉換P4D(p4d僅5 level轉換時啟用,此處會值接回傳傳入的pgd *)

程式碼:

p4d = p4d_offset(pgd, vaddr);

trace code: //arch/x86/include/asm/pgtable.h line 926)

image

其中pgtable_l5_enabled() check whether 5-level page table is enabled。因此如果系統使用的是4-level,則無需存取 p4d_t,且直接回傳以(p4d_t*) pgd
也就是說在4-level下 pgd = p4d
相同道理,3-level下 pgd = p4d = pud

第三層轉換PUD

目標 : 使用*pgd與pud index找到之PUD entry的virtual address

程式碼:

pud = pud_offset(p4d, vaddr);

trace code:

//arch/x86//include/linux/pgtable.h Line:115
#ifndef pud_offset
static inline pud_t *pud_offset(p4d_t *p4d, unsigned long address)
{
        return p4d_pgtable(*p4d) + pud_index(address);
}

image

image

這裡先用macro判斷CONFIG_PGTABLE_LEVELS是否大於4(p4d table是否有真正使用) 在我們情況下使用4 level轉換,故實際function為下方349行而非337行。

/ arch / x86 / include / asm / pgtable_types.h

image

image

此處的查詢使用的pgd entry為第一層轉換出來(p4d=pgd),透過virtual address來指向一個pgd entry的pointer

__va() trace code:

/ arch / x86 / include / asm / page.h

image

透過將physical address加上PAGE_OFFSET,也就是加上kernel space virtual address的啟始位置藉此得到透過偏移量轉換的virtual address.

image

第四層轉換PMD

目標 : 使用*pud與pmd index找到之PMD entry的virtual address

程式碼:

pmd = pmd_offset(pud, vaddr);

trace code:

//arch/x86//include/linux/pgtable.h line 106
/* Find an entry in the second-level page table.. */
#ifndef pmd_offset
static inline pmd_t *pmd_offset(pud_t *pud, unsigned long address)
{
        return pud_pgtable(*pud) + pmd_index(address);
}
#define pmd_offset pmd_offset

image

這裡傳入的pud是透過virtual address指向一個pud entry

image

可以看到這裡一樣會檢查判斷CONFIG_PGTABLE_LEVELS是否大於3(pud table是否有啟用)

image

這裡因為我們CONFIG_PGTABLE_LEVELS = 4,故執行的是363行而不是375行的native_pud_val()

第五層轉換PTE

目標 : 使用*pmd與pte index找到之PTE entry的virtual address

程式碼:

pte = pte_offset_kernel(pmd, vaddr);

trace code:

// include/linux/pgtable.h line 88

#ifndef pte_offset_kernel
static inline pte_t *pte_offset_kernel(pmd_t *pmd, unsigned long address)
{
        return (pte_t *)pmd_page_vaddr(*pmd) + pte_index(address);
}
#define pte_offset_kernel pte_offset_kernel
#endif

/ arch / x86 / include / asm / pgtable.h

image

由PTE table找到實體記憶體位置

目標 : 由*pte與pte index找到pte entry中存放的physical address

程式碼:

page_addr = pte_val(*pte) & PTE_PFN_MASK;
page_offset = vaddr & ~PAGE_MASK;
paddr = page_addr | page_offset;

trace code:

image

image

這裡透過的pte_val()得到pte table entry中的內容。

計算physical address

:::success 以實際例子介紹line 64~66

新增test.c

#include <stdio.h>
#include <sys/syscall.h>      /* Definition of SYS_* constants */
#include <unistd.h>

void * my_get_physical_addresses(void *vaddr_of_a){
        unsigned long paddr;

        long result = syscall(450, &vaddr_of_a, &paddr);

        return (void *)paddr;
};

int main()
{
    int a = 10;
    printf("Virtual addr. of arg a = %p\n", &a);
    printf("Physical addr. of arg a = %p\n", my_get_physical_addresses(&a));
}

結果:
image

使用dmesg來查看kernel內的訊息

image

image

可以看到virtual address = 0x7fffd5bd1544
pte_val(*pte) = PTE base address = 0x8000000093567867
另外,PTE_PFN_MASK = 0x0000FFFFFFFFF000因為page size 為 4KB,且保留了bit 12 到 51 的部分(總共 40 bit),可參考上圖,或是最下方physical memory範圍

page_addr = pte_val(*pte) & PTE_PFN_MASK;

page_addr = 0x8000000093567867 & 0x0000FFFFFFFFF000 = 0x93567000
page_addrbase address of the physical page frame

page_offset = vaddr & ~PAGE_MASK;

page_offset = 0x7fffd5bd1544 & 0x0000000000000FFF = 0x544
得到 physical page frame的offset

paddr = page_addr | page_offset;

最後 physical address = 0x93567000 | 0x544 = 0x93567544

簡單來說,其實就只是需要先算出page frame address再和offset 相加而已,只不過是使用 bitwise&| 來計算出結果 :::

:::warning 因為使用 copy_from_user()因此必須傳入pointer of of virtual address of a
所以即使 my_get_physical_addresses(void *vaddr_of_a)中的*vaddr_of_a已經是pointer,
但是在呼叫system calls時,long result = syscall(450, &vaddr_of_a, &paddr);
需要傳送的參數是&vaddr_of_a(i.e. pointer of of virtual address of a) :::

Add system call

1. Modified Makefile

修改 kernel/Makefile,增加 project1.o

obj-y     = fork.o exec_domain.o panic.o \
            cpu.o exit.o softirq.o resource.o \
            sysctl.o capability.o ptrace.o user.o \
            signal.o sys.o umh.o workqueue.o pid.o task_work.o \
            extable.o params.o \
            kthread.o sys_ni.o nsproxy.o \
            notifier.o ksysfs.o cred.o reboot.o \
            async.o range.o smpboot.o ucount.o regset.o \
            project1.o \

使得在編譯時也會編譯到project1這個檔案

2. Modified syscall Table

要新增自己的 system call,打開arch/x86/entry/syscalls/syscall_64.tbl 在第 374 行後面新增自己的 system call:

450     common  my_get_physical_addresses       sys_my_get_physical_addresses

這行有四個部分,每項之間由空白或 tab 隔開,它們代表的意義是:

  • 450 system call number,在使用系統呼叫時要使用這個數字
  • common 支援的 ABI, 只能是 64、x32 或 common,分別表示「只支援 amd64」、「只支援 x32」或「都支援」
  • my_get_physical_addresses system call 的名字
  • sys_my_get_physical_addresses system call 對應的實作,kernel 中通常會用 sys 開頭來代表 system call 的實作

syscall_64.tbl 這個檔案會在編譯階段被讀取後轉為 header file 檔案位於: arch/x86/include/generated/asm/syscalls_64.h
image

3. Modified syscalls.h

將 syscall 的原型添加進檔案 (#endif 之前) 路徑為: include/linux/syscalls.h

image

這定義了我們system call的prototype,asmlinkage代表我們的參數都可以在stack裡取用, 當 assembly code 呼叫 C function,並且是以 stack 方式傳參數時,在 C function 的 prototype 前面就要加上 asmlinkage

Compile Kernel

請參考 add a system call

Copy on Write

  • Copy on write: allows multiple processes to share the same physical memory until one intends to modify it.

螢幕擷取畫面 2024-11-08 154515

可以看到程式執行時,parent process、child process中 global_a 的physical memory都是共用的,直到global_a被改動之後,os會分配新的physical memory 給改動的process,也因此驗證了system call 確實有正確呼叫

Loader

進入這章節前,先快速介紹Linux 中的Demand paging機制,可以對應到老師之前介紹的lazy allocation,不過lazy allocation相對廣義一些,demand paging 單純在memory 中使用

  • Demand Paging: pages of a process's memory are loaded into physical memory only when they are actually needed(ex: when the process tries to access them)

簡單來說,並不是一開始所有的virtual address都有對應到physical address,而是等到需要使用(access)時才載入到physical memory

因此,以process是否access the item作為區分,可以分為下列幾種情況:

case 1: Array store in bss segment

// global variable
int a[2000000];   // store in bss segment,
                  // same as  int a[2000000] = {0}; 

執行結果:
image

可以看到,存放在 bss segment 的 array, Load到memory中的只有到 a[1007],之後就沒有load 進memory,因此沒有分配physical memory

case 2: Array store in data segment

// global variable
int a[2000000] = {1};  // initialized variable, store in Data segment

執行結果:
image

可以看到,因為第一個element有被預設初始值,因此array a會預先載入幾個page至memory中,but only few page store in memory, 剩下尚未存取的需要透過page fault來載入至memory,因此印至 a[15351]便停止

補充:
因為load至a[15351],所以我想試看看預先存取a[15352] 產生page fault並將其load入physical memory,看看有甚麼結果

a[15352] = 1;     // occur page fault, load to phy_mem

執行結果:
image

可以看到 load 到a[16375]結束,而a[16376]尚未存取, 因此可得:

16375 - 15351 = 1024    

因為page size = 4KB,且一個int 4 bytes,而我們使用64位元架構, 因此page table entries size = 8 bytes(存兩個int element = 8 bytes),因此:$$\dfrac{4KB}{8B} = \dfrac{2^{12}}{2^3} = 2^9 = 512$$ 證明也是64位元架構page table entries 為512個

由此證明老師上課講解的內容

case 3: loop through array

// in local 
for(int i=0; i<2000000; i++)
{
    a[i] = 0;    //pre-accessing the array
}

執行結果:
image

In this particular case,不管是定義在Data segment or BSS segment,透過迴圈存取每個element,會造成page fault 並強迫load into memory,因此陣列中每個element 都有分配到各自的physical address

Note

BSS segment vs Data segment

BSS segment 存放的資料為 uninitialized global variable (initialized with 0) 或是 uninitialized static variable,而存放在bss segment和data segment的差別可以從case 1case 2看到,data segement中的資料會在程式載入時會立即分配頁面,因此分配到的記憶體更多

// global variables

int a[100];               // bss segment
int a[100] = {0};         // bss segment
static int global_var2;   // bss segment
int a[100] = {1};         // Data segment

mm_struct

What is mm_struct?

task_struct 被稱為 process descriptor,因為其記錄了這個 process所有的context(ex: PID, scheduling info),其中有一個被稱為 memory descriptor的結構 mm_struct,記錄了Linux視角下管理process address的資訊(ex: page tables)。
30528e172c325228bf23dec7772f0c73
圖源: Linux源码解析-内存描述符(mm_struct)

因此 struct mm_struct *mm = current->mm; 指的是存取目前process的memory management 資訊

By assigning current->mm to this pointer, now can access to the memory-related information (ex: page tables) for the process that is running the system call.

What is task_struct?

根據 bootlin

在 Linux 中,Process Descriptor的data structure是 task_struct,每個正在運行或等待的process都對應一個 task_struct

其中比較常見的有:

struct task_struct {
    pid_t pid;                  // process ID
    pid_t tgid;                 // thread ID
    long state;                 // process state
    struct mm_struct *mm;       // memory descriptor
    struct files_struct *files; // 文件描述符
    struct fs_struct *fs;       // 文件系統信息
    int prio;                   // 優先級
    struct cred *cred;          // 權限信息
    struct signal_struct *signal; // 信號處理
    // ... 
};

SYSCALL_DEFINE

What is SYSCALL_DEFINE2? 根據 bootlin定義:

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

#define SYSCALL_DEFINE_MAXARGS	6

其中SYSCALL_DEFINE1(name, ...) 中的

  • 1表示system call 參數的個數,依此類推2、3、4、5、6 表示參數個數
  • name 表示系統呼叫system call的名字

而後面的 SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) 中的

  • _##name 是一個預處理器拼接操作,會將 _name 組合成一個標識符,
    例如,如果kernel中使用了
SYSCALL_DEFINE1(my_get_physical_addresses, void *ptr)

則這個 Macro 會展開為:

asmlinkage long sys_my_get_physical_addresses(void *ptr);
  • __VA_ARGS__ 代表傳入的參數

How does the kernel set register cr3

refrence: stackoverflow

Stackoverflow Reply:

the page tables are found in kernel address space and the kernel keeps a close track of the virtual->physical mapping there.


Linux differentiates between two types of virtual addresses in the kernel:

Kernel virtual addresses - which can map (conceptually) to any physical address; and

Kernel logical addresses - which are virtual addresses that have a linear mapping to physical addresses



The kernel places the page tables in logical addresses, so you only need to focus on those for this discussion.

Mapping a logical address to its corresponding physical one requires only the subtraction of a constant (see e.g. the __pa macro in the Linux source code).

For example, on x86, physical address 0 corresponds to logical address 0xC0000000, and physical address 0x8000 corresponds to logical address 0xC0008000.

So once the kernel places the page tables in a particular logical address, it can easily calculate which physical address it corresponds to.

kernel有兩種虛擬記憶體機制:Kernel virtual addresses與Kernel logical addresses,page table存放區域使用的是Kernel logical addresses機制(簡單的偏移量關係可以實現更快速的虛擬位置實體位置轉換)。

透過bootlin trace code: 轉換kernel logical address至physical address是透過__pa()

/ arch / x86 / include / asm / page.h

image

繼續進行trace code

image

page_32.h中

image

在32位元中__pa()透過將physical address減去PAGE_OFFSET

page_64.h中

image

image

當 x < y 時,這表示 x 是一個低於內核映射起始地址的虛擬地址。這種情況下,y 會是一個負值(在無符號長整型中,這會導致進位)。為了計算出正確的物理地址,我們需要將 (__START_KERNEL_map - PAGE_OFFSET) 加到 y 上。

__START_KERNEL_map 是內核映射的起始地址,而 PAGE_OFFSET 是內核虛擬地址空間的偏移量。通過將 (__START_KERNEL_map - PAGE_OFFSET) 加到 y 上,我們可以得到一個正確的物理地址,這樣可以確保計算出的物理地址是正確的。

撰寫x = y + (__START_KERNEL_map - PAGE_OFFSET);是為了讓系統可以兼容使用PAGE_OFFSET機制而不是__START_KERNEL_map機制來轉換virtual address的程式

CR2暫存器作用?

CR2 暫存器的作用:

在x86架構下,當發生頁錯(page fault)時,處理器會自動將導致頁錯的虛擬地址寫入 cr2 暫存器。這個虛擬地址就是系統試圖存取但未映射到物理內存的地址。 Page Fault 處理流程:

當頁表條目(page table entry,PTE)中的 present 位(flag)為 0 時,表示該頁未映射到物理內存,因此會觸發頁錯中斷(page fault)。 頁錯處理程式(page fault handler)會讀取 cr2 中的虛擬地址,從而知道是哪個地址引發了頁錯。 內核在頁錯處理過程中可能會在物理內存中找到一個可用的頁框,然後從磁碟(或其他二級存儲)將需要的頁面內容載入到這個頁框中。 最後,內核使用 cr2 中的虛擬地址來更新相應的頁表條目,使該虛擬地址映射到剛載入的物理頁框,並將 present 標誌設為 1,以便未來的訪問不會再觸發頁錯。

Problems

physical_memory範圍

假設我們給予8GB 記憶體空間,那麼8GB = 8,589,934,592 Bytes = 0x2 0000 0000
因此最高能分配到的記憶體位置為 0x1 FFFF FFFF
image

以上圖為例,physical address 明顯超出記憶體範圍,原因如下圖所述,並不是所有的bit都為實體記憶體位址,前面0x8000...都是NX bit或是其他功能,所以必須在計算physical address時使用PTE_PFN_MASK過濾掉第52 bits以上及後面12 bits,得到的才是實際physical frame number,加上offset 才會是physical address

image

Referenced

Releases

No releases published

Packages

No packages published

Languages