---
# System prepended metadata

title: Linux Project1：實作回傳 Physical Address 的 System Call
tags: [KB]

---

Linux Project1：實作回傳 Physical Address 的 System Call
==================================================


## 一、專案簡介（Project Overview）

- **專案目標**  
  - 在 Linux kernel 5.15.135 中新增一個系統呼叫：`void * my_get_physical_addresses(void *vaddr)`  
  - 讓 user-space 程式可以查詢「某個虛擬位址目前是否對應到實體位址」，並觀察：
    - **Copy-on-Write (CoW)**：父子行程在 `fork()` 之後，對同一個 global 變數的實體位址變化。  
    - **Lazy allocation / Demand paging**：大型 global array 在尚未實際使用時，是否已經配置實體記憶體。

- **系統呼叫回傳值**  
  - `0`：目前沒有實體位址被指派（page 尚未被配置或尚未建立 PTE）。  
  - 非 0：對應的實體位址（實際上是實體位址的 offset）。  

- **小組成員**  
  - 114552005  黃致霖
  - 114552006  丁偉哲
  - 114552036  許宗元

---

## 二、實驗環境與前置準備

### 2.1 環境與版本

- **作業系統**：Ubuntu Desktop 22.04（https://releases.ubuntu.com/jammy/）
- **VM Machine 系統資源** : CPU 12 cores / Memory 8GB / Disk 60GB **(VM Workstation)**
- **CPU 架構**：x86_64（對應 `syscall_64.tbl` 與五層 page table）  
- **Kernel 原始碼版本**：`linux-5.15.135`  

### 2.2 安裝編譯工具

在 Ubuntu 上先安裝編譯 kernel 所需套件：

```bash
sudo apt update
sudo apt install build-essential libncurses-dev bison flex libssl-dev 
libelf-dev dwarves -y
```


### 2.3 下載並解壓 Linux 5.15.135 Kernel Source Code

```bash
# 當前路徑為使用者 home 目錄
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.135.tar.xz
tar -xvf linux-5.15.135.tar.xz
```

![下載解壓縮](https://hackmd.io/_uploads/HkxeKzvZbx.png)
![下載解壓縮2](https://hackmd.io/_uploads/SyeltfD-Wg.png)

---

## 三、新增系統呼叫 `my_get_physical_addresses`

> 我們把所有和 system call 相關的程式放在 mycall/ 資料夾中。  
> 主要檔案：`mycall/my_get_physical_addresses.c`

### 3.1 建立自訂目錄與檔案

進入 kernel Source code 目錄內並建立資料夾：

```bash
cd ~/linux-5.15.135
mkdir mycall
```

在 `mycall` 底下建立 `my_get_physical_addresses.c`：

```bash
nano mycall/my_get_physical_addresses.c
```

核心實作程式碼（摘要）如下：

```
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <asm/page.h>
#include <asm/pgtable.h>

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

    struct mm_struct *mm = current->mm;

    pgd = pgd_offset(mm, vaddr);
    if (pgd_none(*pgd) || pgd_bad(*pgd)) return 0;

    p4d = p4d_offset(pgd, vaddr);
    if (p4d_none(*p4d) || p4d_bad(*p4d)) return 0;

    pud = pud_offset(p4d, vaddr);
    if (pud_none(*pud) || pud_bad(*pud)) return 0;

    pmd = pmd_offset(pud, vaddr);
    if (pmd_none(*pmd) || pmd_bad(*pmd)) return 0;

    pte = pte_offset_map(pmd, vaddr);
    if (!pte) return 0;
    if (pte_none(*pte)) {
        pte_unmap(pte);
        return 0;
    }

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

    pte_unmap(pte);

    return paddr;
}
```

### 3.2 實作邏輯說明（五層 page table 走訪）

- **輸入**：一個 user space 的虛擬位址 `vaddr_ptr`。  
- **步驟**：
  - 取得目前行程的 `mm_struct`：`current->mm`。  
  - 依序走訪五層 page table：`PGD → P4D → PUD → PMD → PTE`。  
  - 每一層若是 `none` 或 `bad`，代表該範圍尚未配置，直接回傳 `0`。  
  - 在 PTE 層取得 `pte_val(*pte)`，用 `PTE_PFN_MASK` 抽出 Page Frame Number（PFN），再加上虛擬位址在該頁中的 offset，組合成完整的 physical address。  
  - 完成後呼叫 `pte_unmap(pte)` 解除對 PTE 的暫時映射。  

> 這個 system call 的核心工作，就是把 CPU 的「page-table walk」流程用 C 程式碼顯式實作出來，方便 user space 直接查詢實體位址。

### 3.3 建立 `mycall/Makefile`

在 `mycall` 建立 Makefile：

```bash
nano mycall/Makefile
```

內容：

```
obj-y := my_get_physical_addresses.o
```

代表在建構核心時，會把 `my_get_physical_addresses.o` 編進 vmlinux。

### 3.4 修改頂層 `Makefile` 以加入新目錄

在 ~/Linux-5.15.135 目錄的 `Makefile` 中找到：

```text
core-y      += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/
```
![image](https://hackmd.io/_uploads/SJNJ2LY-bl.png)

修改為：

```text
core-y      += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ mycall/
```
![image](https://hackmd.io/_uploads/B1N4nLFWZx.png)




### 3.5 註冊系統呼叫（syscall_64.tbl）

系統呼叫表路徑：  
`~/linux-5.15.135/arch/x86/entry/syscalls/syscall_64.tbl`

在適當位置（例如靠近其他自訂 syscall 的地方）新增一行：

```text
449	common	my_get_physical_addresses	sys_my_get_physical_addresses
```

> 實際內容請依目前檔案狀況為準，重點是：  
> - 號碼：`449`（對應 user space 的 `#define SYS_MY_GET_PHYS 449`）  
> - 名稱：`my_get_physical_addresses`  
> - 對應 kernel 端符號：`sys_my_get_physical_addresses`

![image](https://hackmd.io/_uploads/SkTn6UFWZl.png)




### 3.6 （可選）在 `~/include/linux/syscalls.h` 宣告

對於使用 `SYSCALL_DEFINE` 宏的版本，kernel 會自動產生對應宣告，一般來說不一定需要手動在 `syscalls.h` 加 prototype。  

若要手動宣告，可在檔案結尾的 `#endif` 前加入：

```c
asmlinkage long sys_my_get_physical_addresses(void *usr_ptr);
```
![擷取2](https://hackmd.io/_uploads/SkwuAKwWWx.png)
---

## 四、Kernel 編譯與安裝流程

### 4.1 載入並調整 Kernel 設定

```bash
cp /boot/config-$(uname -r) .config
make menuconfig
# 出現藍底 GUI 選單選取 Exit > Yes
```
![image](https://hackmd.io/_uploads/BkFJWvtWZx.png)
![image](https://hackmd.io/_uploads/H1qq-vYWZg.png)



### 4.2 編譯 Kernel

在 kernel 根目錄執行：

```bash
make -j$(nproc)
```

- `-j$(nproc)`：根據 CPU 核心數平行編譯，加快速度。  


make 編譯過程與最後 Kernel ready 的畫面
![編譯完成](https://hackmd.io/_uploads/HynJ-cvbbx.png)

可透過以下指令查看檔案是否產生，若無則編譯失敗。
```
ls arch/x86/boot/bzImage
```


### 4.3 Trouble Shooting 憑證相關錯誤的處理（若有）

如果在編譯過程遇到與憑證相關的錯誤（例如找不到 Debian 官方的簽章憑證）

![憑證錯誤問題](https://hackmd.io/_uploads/SJJibXDbZl.png)

可以採用以下流程：



1. 清理舊的編譯成果與設定

```bash
make mrproper
```

2. 重新產生組態檔：

```bash
make menuconfig
```
   
3.使用 `scripts/config` 關閉憑證相關設定：
```bash
scripts/config --disable SYSTEM_TRUSTED_KEYS
scripts/config --disable SYSTEM_REVOCATION_KEYS
scripts/config --set-str CONFIG_SYSTEM_TRUSTED_KEYS ""
scripts/config --set-str CONFIG_SYSTEM_REVOCATION_KEYS ""
```
   
4. 再次執行：

```bash
make -j$(nproc)
```



### 4.4 安裝 Kernel 並更新開機項目

```bash
sudo make modules_install
sudo make install
sudo update-grub
```

接著重新開機：

```bash
sudo reboot
```

開機時按 `Shift` 或 `Esc`（或 F4，依 VM 設定）進入 GRUB 選單，選擇：

- **Advanced options for Ubuntu**
![GRUB](https://hackmd.io/_uploads/r13PWqwbWl.png)
- 再選擇剛安裝好的 `5.15.135` 核心版本。
![GRUB2](https://hackmd.io/_uploads/HJDOW5v-Zl.png)


開機後用以下指令確認版本：

```bash
uname -a
```

![uname](https://hackmd.io/_uploads/HkOxGcwZZl.png)

---

## 五、User-space 測試程式設計

我們撰寫了兩個主要的測試程式，用來驗證 `my_get_physical_addresses` 的功能，並觀察：

- **Question 1：Copy-on-Write 行為（test_cow）**  
- **Question 2：Lazy allocation / loader 行為（test_lazy）**

### 5.1 共用的 syscall wrapper

在兩個測試檔中，我們都使用相同的 wrapper 來呼叫 system call：

```startLine:endLine:Documents/workspace/Assigment1/tests/test_cow.c
#define SYS_MY_GET_PHYS 449

unsigned long my_get_physical_addresses(void *vaddr) {
    return syscall(SYS_MY_GET_PHYS, vaddr);
}
```

---

## 六、Question 1：Copy-on-Write 測試（test_cow）

### 6.1 程式碼重點

測試檔路徑：`tests/test_cow.c`

```startLine:endLine:Documents/workspace/Assigment1/tests/test_cow.c
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>

#define SYS_MY_GET_PHYS 449

unsigned long my_get_physical_addresses(void *vaddr) {
    return syscall(SYS_MY_GET_PHYS, vaddr);
}

int global_a = 123;

int main() {
    unsigned long parent_use, child_use;
    printf("====== Before Fork ======\n");
    parent_use = my_get_physical_addresses(&global_a);
    printf("Original Physical: %lx\n", parent_use);

    if (fork() == 0) {
        // Child process
        printf("=== Child Process Created ===\n");
        child_use = my_get_physical_addresses(&global_a);
        printf("Child (Before Write) Physical: %lx\n", child_use);

        printf("=== Triggering COW (Writing to variable) ===\n");
        global_a = 789; // 寫入變數，觸發 Copy-on-Write

        child_use = my_get_physical_addresses(&global_a);
        printf("Child (After Write) Physical:  %lx\n", child_use);
    } else {
        wait(NULL);
    }
    return 0;
}
```

### 6.2 測試步驟與預期行為

1. **Fork 之前**：  
   - 呼叫 `my_get_physical_addresses(&global_a)`，得到一個實體位址 `X`。  
2. **Child 剛建立時（尚未寫入）**：  
   - 在 child 中再查一次 `&global_a`，預期得到同一個實體位址 `X`，說明 parent/child 仍共用同一個 physical page。  
3. **Child 寫入 `global_a = 789` 後**：  
   - 再查 `&global_a`，預期得到另一個實體位址 `Y`，代表觸發 Copy-on-Write，child 取得新的實體頁。  

### 6.3 實際輸出示意與說明

（以下為示意格式，實際數值依執行結果為準）

```text
====== Before Fork ======
Original Physical: 2ca2c6010

=== Child Process Created ===
Child (Before Write) Physical: 2ca2c6010
=== Triggering COW (Writing to variable) ===
Child (After Write) Physical: 3661df010
```

- `Original Physical` 與 `Child (Before Write)` 相同：說明 parent/child 共用同一個 physical page。  
- `Child (After Write)` 不同：代表 child 寫入引發 page fault，kernel 為 child 建立新的實體 page（Copy-on-Write）。  

![test_cow 執行結果](https://hackmd.io/_uploads/Sy18rit-Zl.png)



---

## 七、Question 2：Lazy allocation 測試（test_lazy）

### 7.1 程式碼重點

測試檔路徑：`tests/test_lazy.c`

```startLine:endLine:Documents/workspace/Assigment1/tests/test_lazy.c
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>

#define SYS_MY_GET_PHYS 451

unsigned long my_get_physical_addresses(void *vaddr) {
    return syscall(SYS_MY_GET_PHYS, vaddr);
}

int a[2900000]; // 宣告超大陣列

int main() {
    unsigned long phy_add;

    printf("Checking a[0]...\n");
    phy_add = my_get_physical_addresses(&a[0]);
    printf("Physical address of a[0]: %lx\n", phy_add);

    printf("Checking a[1999999]...\n");
    phy_add = my_get_physical_addresses(&a[1999999]);
    printf("Physical address of a[1999999]: %lx\n", phy_add);

    printf("Writing to a[0]...\n");
    a[0] = 1;
    phy_add = my_get_physical_addresses(&a[0]);
    printf("Physical address of a[0] (After Write): %lx\n", phy_add);

    return 0;
}
```

### 7.2 測試步驟與預期行為

1. 宣告一個很大的 global array：`int a[2900000];`，讓 kernel 需要為 .bss/.data 區預留大範圍虛擬位址。  
2. 程式啟動後，什麼都還沒實際讀寫，只呼叫 system call 查詢：
   - `&a[0]` 的實體位址。  
   - `&a[1999999]` 的實體位址。  
3. 預期觀察結果：
   - `a[0]`：可能已經有實體位址（或很快被 touch），回傳值多半為非 0。  
   - `a[1999999]`：在尚未真正讀寫時，很可能尚未分配實體頁，system call 會在 `pte_none(*pte)` 的地方回傳 `0`。  
4. 之後寫入 `a[0] = 1;`，再查一次 `&a[0]`：
   - 預期 physical address 前後一致，說明這一頁本來就有 mapping 或在第一次查詢後就穩定存在。  

### 7.3 實際輸出示意與說明

（以下為示意格式，實際數值依執行結果為準）

```text
Checking a[0]...
Physical address of a[0]: 2ab228040
Checking a[1999999]...
Physical address of a[1999999]: 0
Writing to a[0]...
Physical address of a[0] (After Write): 2ab228040
```

- `a[0]` 有實體位址：代表 array 前面的頁面已經配置或被存取。  
- `a[1999999]` 回傳 0：代表對應的虛擬頁目前尚未有 PTE / 實體頁，符合 **lazy allocation / demand paging** 的行為。  
- 對 `a[0]` 寫入後再查一次，位址不變：證明這一頁在寫入前後都是同一個 physical page。  

![test_lazy 執行結果](https://hackmd.io/_uploads/ryruBitZWl.png)
