---
# System prepended metadata

title: Linux Poject 1

---

## Linux Project 1
[toc]

**組長：**
**111504512 賴詠文** 
組員：
111504507 黃子銘 
109401553 楊雲杰 
111504008 郭奕宏 

:::success
### 題目
**you need to write a new system call `void * my_get_physical_addresses(void *)` so that a process can use it to get the physical address of a virtual address of a process.**
> 此系統呼叫的回傳值是 0 或位址值。 0表示目前沒有實體位址分配給邏輯位址。非零值表示作為其參數提交給系統呼叫的邏輯位址的物理位址（實際上，該位址只是邏輯位址的偏移量）。

https://staff.csie.ncu.edu.tw/hsufh/COURSES/FALL2024/linux_project_1.html
### Demo 時間
- 11/12 11:15~11:30

### 第一次討論
- 10/19 20:30~

### 第二次討論 
- 11/1 20:00~

### 第三次討論 
- 11/9 20:00~
:::



### Kernel 版本
`wget -P ~/ https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.137.tar.xz`

:::success
## 題目
you need to write a new system call `void * my_get_physical_addresses(void *)` so that a process can use it to get the physical address of a virtual address of a process.

此系統呼叫的回傳值是 0 或位址值。 0表示目前沒有實體位址分配給邏輯位址。非零值表示作為其參數提交給系統呼叫的邏輯位址的物理位址（實際上，該位址只是邏輯位址的偏移量）。
:::
## 前置工作
:::spoiler 下載kernel
![image](https://hackmd.io/_uploads/S1xRgy6-1x.png)




```shell=
sudo apt update
sudo apt install build-essential libncurses-dev libssl-dev libelf-dev bison flex -y
```
:::
## systemcall 添加及設置
進入解壓縮完的資料夾並在內部建立一個資料夾
```shell=
#進入 linux-5.15.137
cd usr/src/linux-5.15.137/  

# 建立一個資料夾 mycall
mkdir mycall      

# 在新建立好的資料夾中建立一個system call
vim mycall/my_get_physical_addresses.c

# 建立Makefile
vim mycall/Makefile

# 將my_get_physical_addresses.o編入kernel
obj-y := my_get_physical_addresses.o 

# 編輯原系統中的Makefile
core-y	+= kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ mycall/

# 將系統呼叫對應的函數加入到系統呼叫的標頭檔中
# 開啟檔案`include/linux/syscalls.h`
vim  include/linux/syscalls.h

# 並在 #endif前加上
asmlinkage long sys_my_get_physical_addresses(unsigned long __user *usr_ptr);

# 將syscall加入到kernel的syscall table 並開啟檔案
vim arch/x86/entry/syscalls/syscall_64.tbl

# 會發現一系列 x32 系統呼叫，在x32 system call上方加入編號449的system call
449	common    my_get_physical_addresses	sys_my_get_physical_addresses
```
## 編譯和替換kernel
清理kernel之前的所有配置
```shell=
sudo make mrproper
```
編譯設定
進入 kernel 目錄中，並將目前 Kernel Config 文件複製到當前目錄，最後生成此 kernel 的配置文件
為了避免建置大量不必要的 driver 和 kernel module，使用 `localmodconfig` 來節省時間。
```shell=
cd linux-5.15.137/
cp -v /boot/config-$(uname -r) .config
make localmodconfig
```
因為我們直接從 /boot/config-$(uname -r) 複製設定檔，所以設定檔裡面的設定的是 Debian 官方當初編譯 kernel 時憑證的路徑，若是直接編譯會報錯，因此這邊取消使用憑證，並將值設為空字串
```shell=
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 ""
```
開始編譯
```shell=
make -j12
```
### Error 處理
No rule to make target 'debian/canonical-certs.pem’
```
sudo vim .config
/ + enter + n(往下)搜尋 `debian/canonical-certs.pem `並直接清除此引號內的值
/ + enter + n(往下)搜尋 `debian/canonical-revoked-certs.pem` 並直接清除此引號內的值
```


BTF: .tmp_vmlinux.btf: pahole (pahole) is not available
```
sudo vim .config
/ + enter + n(往下)搜尋 CONFIG_DEBUG_INFO_BTF 將其值改為 n
```

### 編譯完畢進行安裝
準備kernel的安裝程式
```shell=
sudo make modules_install -j12
```
安裝kernel
```shell=
sudo make install -j12
```
重新啟動
```
sudo update-grub
sudo reboot
```
快速地連續按F4進入開機選單，點選Advanced options for Ubuntu
檢查版本 `uname -rs`
若要看kernel環境訊息或查看printk的結果可以下`sudo dmesg`指令查看
## 實作部分 (virtual address 轉為 physical address )
### 背景小知識
`#ifndef` `#define` `#endif`
#ifndef 的全稱是 "if not defined"，用來檢查某個符號或巨集是否尚未被定義。
如果符號未定義，則編譯器會處理該條件之下的代碼。
優點：避免程式重複編譯!!!
```
#ifndef SOME_MACRO
    // 這段代碼會在 SOME_MACRO 尚未定義時被編譯
#define 
    // 已編譯過會執行這段代碼
#endif
```
在pgtable.h中 offset function
trace code網址：https://elixir.bootlin.com/linux/v5.15.137/source
```cpp=
// include/linux/pgtable.h line 91

#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

// ...

// include/linux/pgtable.h line 119
/* 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
// pud_t *pud：頁上級目錄（PUD，Page Upper Directory）的指針。
static inline pud_t *pud_offset(p4d_t *p4d, unsigned long address)
{
    // p4d_pgtable(*p4d)：這個會返回 P4D 指向的頁表的基礎地址。
	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));
};
```
### 整體架構
線性地址轉physical address
![image](https://hackmd.io/_uploads/SyrZlkpWJe.png)
:::spoiler kernel內多級頁表 ![image](https://hackmd.io/_uploads/SJBVl1TZ1l.png)
- GLOBAL DIR (PML4 Index): 高 9 bits，對應 PML4 索引。
- Upper DIR (PDPT Index): 接下來的 9 bits，對應上級頁目錄索引。
- Middle DIR (PD Index): 再往下的 9 bits，對應中間頁目錄索引。
- Table (PT Index): 9 bits，對應頁表的索引。
- OFFSET: 最後 12 bits，是頁框中的偏移量，表示物理頁框內的具體位置。

4 種 page table 結構
- CR3: 開始時，CR3 寄存器存儲的是 PML4 頁表的物理地址（圖中為 CR3 physical address）。PML4 是最高層的頁表。
- PML4 (Page Map Level 4): 使用虛擬地址的前 9 位作為索引（圖中的 pgd_index）來查找對應的 PML4 頁表條目（*pgd_offset）。
- PDPT (Page Directory Pointer Table): 下一層是上級頁目錄（PDPT），使用接下來的 9 位作為索引（pud_index），查找對應的頁表條目（*pud_offset）。
- PD (Page Directory): 中級頁目錄（PD）層使用接下來的 9 位作為索引（pmd_index），查找對應的條目（*pmd_offset）。
- T (Page Table): 最後的 9 位作為頁表索引（pte_index），找到具體的頁表條目（*pte_offset_kernel）。
- Offset: 最後的 12 位是頁框內的偏移量，用於定位頁框內的具體數據（Start Byte）。
:::
- 虛擬位址的最低12位元(4KB的頁大小)和實體位址的最低12位元相同。虛擬位址的4個頁表段page_index可以看做在頁表中的索引。

每一個 Process 都會有自己的 Page Table，存在它自己的 kernel space，Page table 的 Base address 會被存在 CR3 裡面，這是一個 register，又被稱為 PDBR(page directory base register)，存的是實體位址
> 分頁目錄的基底位址是存放在 CR3（或稱 PDBR，page directory base register）




### Page directory and page table 
:::spoiler Page directory and page table architecture
![image](https://hackmd.io/_uploads/rJ5ElJ6bke.png)
- P: present p=1 => in memory
- R/W: =1 => can write
- U/S: user/supervisor 當 U/S = 1 時，表示分頁是一個 user level 的分頁
- A（accessed）旗標：在 A = 0 時，若分頁被存取
- D（dirty）旗標：在 D = 0 時，若對分頁進行寫入動作，則處理器會把它設為 1
- PS（page size）旗標：這個旗標只在分頁目錄 entry 中有作用。當 PS = 0 時，表示這是一個 4KB 的分頁
:::
to physical address 概要
```cppp=
// 當前進程的內存描述符（current->mm）中，計算虛擬地址 vaddr 在頁全局目錄（PGD）中的條目位置。這是查找過程中的第一層。
pgd = pgd_offset(current->mm, vaddr);

//通過 pmd 和 vaddr 計算頁表條目（PTE）的指針
pte = pte_offset_kernel(pmd, vaddr); 

//從頁表條目（pte）中提取出物理頁框地址
//PAGE_MASK 是用來過濾掉頁表條目中的非地址部分（例如標誌位）的掩碼
//pte_val(*pte) & PAGE_MASK，只保留頁表條目中的物理頁框地址部分，並將其存儲在 page_addr 中。
page_addr = pte_val(*pte) & PAGE_MASK;

//vaddr 是虛擬地址，它包括了頁表查找所需的高位部分和頁框內的偏移量。
//~PAGE_MASK 是 PAGE_MASK 的反碼，將物理頁框地址的高位清除，保留低位的偏移量部分。
//vaddr & ~PAGE_MASK 是提取虛擬地址中的偏移量部分，這部分表示該地址在物理頁框中的具體位置。
page_offset = vaddr & ~PAGE_MASK;

//將物理頁框地址與偏移量組合，生成最終的物理地址。
paddr = page_addr | page_offset;
```
:::info
### **轉址備註**
`0x5633733901b1`，其前9個bit為 010101100
轉為10進位為172，也就是指”**下一層table的起始位置**”存於此table的第172欄
由於每一個欄位都是由8 byte組成，所以實際的offset的address為 172*8 =1376 轉為16進制為 `0x560` 相當於 010101100 左移3個bit，即 0101 0110 0000  轉為16進制也是 `0x560`

將a.的值(**pgd** )及b.的值(**pgd_index(address)**)合併:
`0xffff9ce09c8e8000`  + `0x560`  = `0xffff9ce09c8e8560`
`0xffff9ce09c8e8560` 此位置所儲存的值即為計算下一層table起始位置所需的值

在 `*pud_offset` 中的return value為 (p4d_pgtable(*p4d) + pud_index(address))
- p4d_pgtable(*p4d) 為PUD的起始位置，而 *p4d指的是`0xffff9ce09c8e8560`位置中的值
- p4d_pgtable()則是計算起始位置的方法

:::spoiler current-> mm解釋
在這段程式碼中，`current->mm` 是 Linux 核心中的一個結構體指標，指向目前執行的進程（process）的 **記憶體描述符 (memory descriptor)**。以下是詳細解釋：

### `current->mm` 的解釋

1. **`current`**：
   - `current` 是一個巨集，用來取得當前執行的進程的 `task_struct` 結構指標。
   - `task_struct` 是 Linux 核心用來描述每個進程的資料結構，包含進程的各種信息，如進程 ID、狀態、優先級、資源限制等。
  ![image](https://hackmd.io/_uploads/Sk8R_ikzke.png)

2. **`current->mm`**：
   - 在 `task_struct` 結構中，有一個成員變數 `mm`，它是一個指向 **`mm_struct`** 結構的指標。
   - `mm_struct` 是記憶體描述符，用於描述進程的虛擬記憶體空間，包含了頁表、分段資訊、堆棧、程式段等相關的記憶體管理資訊。
   - `current->mm` 可以理解為當前進程的虛擬記憶體空間的描述符。

3. **`mm_struct` 的用途**：
   - `mm_struct` 包含了進程記憶體相關的所有重要資訊，例如：
     - **頁全域目錄 (PGD)**：存放頁表層次結構的入口點。
   - 在這段程式碼中，`current->mm` 被用來找到進程的頁全域目錄（PGD），從而開始虛擬地址到物理地址的轉換過程。

4. **`pgd_offset` 函數**：
   - `pgd_offset` 是用來計算虛擬地址 `vaddr` 在當前進程的頁全域目錄中的偏移量，並返回該地址的 `pgd_t` 型別的指標。
   - 這是虛擬地址轉換成物理地址的第一步，用來獲取頁全域目錄 (PGD) 的條目。

### 總結
在這段程式碼中，`current->mm` 是指向目前執行的進程的記憶體描述符的指標，包含該進程的所有虛擬記憶體資訊。利用 `current->mm` 可以開始從虛擬地址到物理地址的頁表查找過程，最終獲取對應的物理地址。
:::
### 完整程式碼
```c=
// SYSCALL_DEFINE1：這個表示定義一個接收一個參數的系統調用。數字 1 表示該系統調用接受一個參數。
// 0x%lx：表示以 16 進制格式打印一個長整數（long 型別）
#include<linux/syscalls.h>

SYSCALL_DEFINE1(my_get_physical_addresses, void *, addr_p)
{
        unsigned long vaddr = (unsigned long)addr_p;
        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;
        pgd= pgd_offset(current->mm,vaddr);
        printk("pgd_val = 0x%lx\n",pgd_val(*pgd));
        printk("pgd_index = %lu\n",pgd_index(vaddr));
        if(pgd_none(*pgd)){
                printk("not mapped in pgd\n");
        }

        p4d=p4d_offset(pgd,vaddr);
        printk("p4d_val = 0x%lx\n",p4d_val(*p4d));
        printk("p4d_index = %lu\n", p4d_index(vaddr));
        if(p4d_none(*p4d)){
            printk("no mapped in p4d\n");
            return 0;
        }
        pud=pud_offset(p4d,vaddr);
        printk("pud_val = 0x%lx\n", pud_val(*pud));
        printk("pud_index = %lu\n"), pud_index(vaddr));

        if(pud_none(*pud)){
            printk("no mapped in pud\n");
            return 0;
        }
        pmd=pmd_offset(pud,vaddr);
        printk("pmd_val = 0x%lx\n",pmd_val(*pmd));
        printk("pmd_index = %lu\n",pmd_index(vaddr));
        if(pmd_none(*pmd)){
            printk("no mapped in pmd\n");
            return 0;
        }

        pte = pte_offset_kernel(pmd,vaddr);
        printk("pte_val = 0x%lx\n",pte_val(*pte));
                printk("pte_index = %lu\n", pte_index(vaddr));
        if(pte_none(*pte)){
            printk("no mapped in pte\n");
            return 0;
        }

        page_addr=pte_val(*pte) & PTE_PFN_MASK & PAGE_MASK;
        page_offset = vaddr & ~PAGE_MASK;
        paddr = page_addr | page_offset;
        printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset);
        printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr);

        return paddr;

}
```
:::info
### 關於physical address 顯示
![image](https://hackmd.io/_uploads/Hydo-16-1e.png)
可以觀察到除了`pud_val`和`pmd_val`是32bit，而`pgd_val`, `p4d_val`, `pte_val`都是64bit，而最後影響到主要是`pte_val`，因此去看`pte_val`的code


```c=
// /arch/x86/include/asm/pgtable.h,

#define pte_val(x)	native_pte_val(x)
```
```c=
// / arch / x86 / include / asm / pgtable_types.h
static inline pteval_t native_pte_val(pte_t pte)
{
	return pte.pte;
}

static inline pteval_t pte_flags(pte_t pte)
{
	return native_pte_val(pte) & PTE_FLAGS_MASK;
}
```
從上面可以發現`native_pte_val`也把`pte_flag`也取出來了

```c
#define PTE_FLAGS_MASK		(~PTE_PFN_MASK)
```
而`PTE_FLAGS_MASK`定義是`(~PTE_PFN_MASK)`，因此我們將pte_val的flag濾掉就好了`page_addr=pte_val(*pte) & PTE_PFN_MASK & PAGE_MASK;`

而`pgd_val`, `p4d_val`也都是將FLAG也取出來
:::
## Question1 (see the effect of copy-on-write.)
:::success
### copy on write

`fork`時，系統會讓子進程和父進程共享相同的物理記憶體頁，而將這些頁標記為「唯讀」。當父或子進程嘗試寫入這些共享頁時，系統才會將該頁的內容複製一份（分配一個新的物理頁框），並允許進行寫入操作，這就是所謂的「寫入時複製」（Copy-on-Write）。


### 測試程式碼解釋
After Fork，由於 `global_a`的頁還沒有被寫入，父子進程此時應該共享相同的物理地址。

子進程寫入 `global_a`（設定 `global_a` = 789），這將觸發 Copy-on-Write 機制。
- 寫入時，作業系統會分配一個新的物理頁框給子進程，然後將 global_a 的新值寫入該頁框中。
- 接著，子進程再次獲取 `global_a` 的物理地址，這次它應該和父進程的 `global_a` 物理地址不同，從而顯示出 Copy-on-Write 的效果。
:::
### 測試程式碼
:::spoiler 測試程式碼
```c=
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

void * my_get_physical_addresses(void *vaddr)
{
        return (void*) syscall(449,vaddr);
}
int global_a=123;  //global variable

void hello(void)
{
    printf("======================================================================================================\n");
}


int main()
{
    int      loc_a;
    void     *parent_use, *child_use;
    int status;
    printf("===========================Before Fork==================================\n");           
    parent_use=my_get_physical_addresses(&global_a);
    printf("pid=%d: global variable global_a:\n", getpid());
    printf("Offset of logical address:[%p]   Physical address:[%p]\n", &global_a,parent_use);       
    printf("========================================================================\n");


    if(fork())
    { /*parent code*/
         printf("vvvvvvvvvvvvvvvvvvvvvvvvvv  After Fork by parent  vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n");
         parent_use=my_get_physical_addresses(&global_a);
         printf("pid=%d: global variable global_a:\n", getpid());
         printf("******* Offset of logical address:[%p]   Physical address:[%p]\n", &global_a,parent_use);
         printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n");
         wait(&status);
    }
    else
    { /*child code*/

         printf("llllllllllllllllllllllllll  After Fork by child  llllllllllllllllllllllllllllllll\n");
         child_use=my_get_physical_addresses(&global_a);
         printf("******* pid=%d: global variable global_a:\n", getpid());
         printf("******* Offset of logical address:[%p]   Physical address:[%p]\n", &global_a, child_use);
         printf("llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll\n");
         printf("____________________________________________________________________________\n");  

         /*----------------------- trigger CoW (Copy on Write) -----------------------------------*/
         global_a=789;

         printf("iiiiiiiiiiiiiiiiiiiiiiiiii  Test copy on write in child  iiiiiiiiiiiiiiiiiiiiiiii\n");
         child_use=my_get_physical_addresses(&global_a);
         printf("******* pid=%d: global variable global_a:\n", getpid());
         printf("******* Offset of logical address:[%p]   Physical address:[%p]\n", &global_a, child_use);
         printf("iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii\n");
         printf("____________________________________________________________________________\n");  
         sleep(1000);
    }
}

```
:::
### 結果
:::spoiler 結果
![image](https://hackmd.io/_uploads/SkxsdqYnWkg.png)

:::
## Question2  (whether a loader loads all data of a process before executing it)
:::success
當進程開始執行時，核心通常不會分配物理記憶體來儲存進程的所有程式碼和資料。

對於 `a[1999999]`，結果顯示物理地址為 `(nil)`示此頁面還沒有被分配物理內存。

這證明內核在進程啟動時並未立即為數組 `a` 的所有元素分配物理內存，僅在訪問` a[0] `時才分配了對應的頁面，未訪問的頁面依然未分配。

Demand paging
只有在頁面被實際訪問時，內核才會為其分配物理內存，並非所有資料在程式啟動時都已載入並分配物理記憶體，只有訪問的頁面才會被分配到物理地址
:::
### 測試程式碼
:::spoiler code
```c=
#include<stdio.h>
// 使用syscall
#include<unistd.h>
void * my_get_physical_addresses(void *vaddr)
{
        return (void*) syscall(449,vaddr);
}
int a[2000000];

int main()
{
   int      loc_a;
   void     *phy_add;


   phy_add=my_get_physical_addresses(&a[0]);
   printf("global element a[0]:\n" );
   printf("Offset of logical address:[%p]   Physical address:[%p]\n", &a[0], phy_add);              
   printf("========================================================================\n");
   phy_add=my_get_physical_addresses(&a[1999999]);
   printf("global element a[1999999]:\n" );
   printf("Offset of logical address:[%p]   Physical address:[%p]\n", &a[1999999], phy_add);        
   printf("========================================================================\n");
}
```
:::
### 結果
:::spoiler result
![image](https://hackmd.io/_uploads/SylF5F3Z1l.png)

:::
## 參考資料
> [add to system call](https://hackmd.io/aist49C9R46-vaBIlP3LDA?view)
> [學長的資料](https://satin-eyebrow-f76.notion.site/Kernel-syscall-3ec38210bb1f4d289850c549def29f9f#6f9c68a389f14319839cf04692b3e064)
> [关于Linux内存寻址与页表处理的一些细节](https://www.cnblogs.com/QiQi-Robotics/p/15630380.html)
> [進階版本](https://hackmd.io/@eugenechou/H1LGA9AiB#Project-1)
> [gcc](https://ithelp.ithome.com.tw/articles/10257387)



## 補充資料
### Page Table 相關數字
```c=
/*
 * PGDIR_SHIFT determines what a top-level page table entry can map
 */
#define PGDIR_SHIFT		39
#define PTRS_PER_PGD		512
#define MAX_PTRS_PER_P4D	1

#endif /* CONFIG_X86_5LEVEL */

/*
 * 3rd level page
 */
#define PUD_SHIFT	30
#define PTRS_PER_PUD	512

/*
 * PMD_SHIFT determines the size of the area a middle-level
 * page table can map
 */
#define PMD_SHIFT	21
#define PTRS_PER_PMD	512

/*
 * entries per page directory level
 */
#define PTRS_PER_PTE	512

#define PMD_SIZE	(_AC(1, UL) << PMD_SHIFT)
#define PMD_MASK	(~(PMD_SIZE - 1))
#define PUD_SIZE	(_AC(1, UL) << PUD_SHIFT)
#define PUD_MASK	(~(PUD_SIZE - 1))
#define PGDIR_SIZE	(_AC(1, UL) << PGDIR_SHIFT)
#define PGDIR_MASK	(~(PGDIR_SIZE - 1))

```






