# linux project 1
___
## <font color="#F7A004">系統環境</font>
虛擬機:virtual box
作業系統:ubantu 22.04 (這邊怕版本不相容下載舊版)
kernel:Linux 5.15.137
_____
## <font color="#F7A004">編譯過程</font>
### step 1 :kenrel_syscall_前置處理
```c=
#進入 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
# 直接加入底部
548 common my_get_physical_addresses sys_my_get_physical_addresses
```

遇到的問題:
1. 一開始編譯遇到的一個問題就是syscall的位置明明寫對了,但卻始終編譯不了
2. vim 模式 不熟悉,但使用nano常常誤改程式
3. 看不懂終端錯誤原因,尤其linker出錯,沒想到是core設定出錯
- **因此下個專題在解決終端錯誤前,要重新檢驗各階段到底在幹嘛?他們的目的是甚麼**
:::danger
mycall必須放在: core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ 的後面 --->>>
core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ mycall/,在編譯通過前我都是把牠們分開導致編譯通過不了!
:::
### step 2 調整設定
``` c=
make localmodconfig
# 查看邏輯核心數量
nproc
# make 時輸入剛剛看到的核心數量
make -j8
# 安裝kernel
sudo make modules_install -j8
sudo make install -j8
# 更新作業系統的bootloader成新的kernel
sudo update-grub
# 重新啟動
reboot
# 查看有沒有成功更新kernel
uname -r
```
重啟後不知道原因,系統會有閃頻問題,但不影響實作
## <font color="#F7A004">修改system call </font>
:::success
目標:實作system call,使完成malloc中lazy allocation 的機制
:::
```c=
#include <linux/mm.h> // struct mm_struct
#include <linux/sched.h> // current 宏
#include <linux/syscalls.h> // SYSCALL_DEFINE 宏
#include <linux/uaccess.h> // __user 宏
#include <asm/pgtable.h> // 頁表相關的結構和宏 (pgd_t, pmd_t, pte_t)
SYSCALL_DEFINE1(get_phy, void __user *, user_addr)
{
unsigned long vaddr = (unsigned long)user_addr;
unsigned long phys_addr = 0;
// 獲取當前行程的記憶體管理結構
struct mm_struct *mm = current->mm;
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd;
pte_t *ptep;
pte_t pte;
printk(KERN_INFO "SYS_GET_PHY: 檢查虛擬地址 0x%lx\n", vaddr);
// 嘗試獲取 MMU 讀鎖,以防止頁表在讀取過程中被修改
if (!mmap_read_trylock(mm))
return -EAGAIN;
/* 步驟 1: 5 級頁表遍歷 (唯讀) */
// 檢查 Page Global Directory (PGD)
pgd = pgd_offset(mm, vaddr);
if (pgd_none(*pgd) || pgd_bad(*pgd))
goto out_unlock;
// 檢查 Page 4th Directory (P4D)
p4d = p4d_offset(pgd, vaddr);
if (p4d_none(*p4d) || p4d_bad(*p4d))
goto out_unlock;
// 檢查 Page Upper Directory (PUD)
pud = pud_offset(p4d, vaddr);
if (pud_none(*pud) || pud_bad(*pud))
goto out_unlock;
// 檢查 Page Middle Directory (PMD)
pmd = pmd_offset(pud, vaddr);
if (pmd_none(*pmd) || pmd_bad(*pmd))
goto out_unlock;
/* 步驟 2: 找到 Page Table Entry (PTE) */
ptep = pte_offset_map(pmd, vaddr);
if (!ptep)
goto out_unlock;
pte = *ptep;
/* 步驟 3: 檢查 PTE 是否有效 (Present) */
if (pte_present(pte)) {
// 提取 PFN (Page Frame Number) 並轉換為實體地址
phys_addr = (unsigned long)(pte_pfn(pte) << PAGE_SHIFT);
// 加上頁面內的偏移量
phys_addr |= (vaddr & ~PAGE_MASK);
}
// 解除 PTE 映射
pte_unmap(ptep);
out_unlock:
// 解除 MMU 讀鎖
mmap_read_unlock(mm);
// 返回實體地址
return (long)phys_addr;
}
```
```
```
)
下圖為四級頁表轉換示意圖(linux可採用3,4,5級轉換)

### 系統架構圖:
1. 系統在context switch時會將cr3占存器時設成下一個progess的pgd entry,並且current指標總是指向當前task_struct結構體

3. 下方為task strcut 中的mm_strcut結構體,可以看到他在裡面分存取了pgd的位置,因此我們可以找到mm_struct的結構,再去找到vm_area_struct
```c=
struct mm_struct {
struct {
/*
* Fields which are often written to are placed in a separate
* cache line.
*/
struct {
/**
* @mm_count: The number of references to &struct
* mm_struct (@mm_users count as 1).
*
* Use mmgrab()/mmdrop() to modify. When this drops to
* 0, the &struct mm_struct is freed.
*/
atomic_t mm_count;
} ____cacheline_aligned_in_smp;
struct maple_tree mm_mt;
unsigned long mmap_base; /* base of mmap area */
unsigned long mmap_legacy_base; /* base of mmap area in bottom-up allocations */
#ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES
/* Base addresses for compatible mmap() */
unsigned long mmap_compat_base;
unsigned long mmap_compat_legacy_base;
#endif
unsigned long task_size; /* size of task vm space */
pgd_t * pgd;
```
3 而malloc就是拿取當前task_的vm_area,當系統第一次時Page_table時,pte_entry的valid_invaild bit by default會是0,因此當前進程第一次存取整個page_table時,系統會產生page_fault,因而造成interupt,此時交接給page_fault_handler去處理,去設置valid_bit為一

4.當正式要寫入memory前,會再跑一次頁表pgd->p4d->pud->pmd-pte進行轉換
5.,因為pte_valid_bit已設為一,因此可以直接找到實體記憶體位置!
## <font color="#F7A004">測試結果</font>

**觀察上圖可以看到,在寫入memory前虛擬記憶體已經配置好,但實體記憶體尚未分配,而分配後有觀察到記憶體分配出連續的空間**
## <font color="#F7A004">user program</font>
:::success
目標:將syscall封包,可以在python的user_spcae使用
:::
需先創建檔案,用來封包syscall,我們叫他my_get_phy,c
```c=
// my_get_phy.c
#include <unistd.h>
#define sys_get_phy 548 // 你設定的 syscall number
// Python 會用 ctypes 呼叫這個函數
unsigned long my_get_physical_addresses(void *virtual_addr) {
return (unsigned long)syscall(SYS_get_phy, virtual_addr);
}
```
接著我們必須編譯他
```c=
gcc -Wall -O2 -fPIC -shared -o lib_my_get_phy.so my_get_phy.c
```
### python interface
```python
import ctypes
# -----------------------------
# C syscall wrapper
# -----------------------------
lib = ctypes.CDLL('./lib_my_get_phy.so')
lib.my_get_physical_addresses.argtypes = [ctypes.c_void_p]
lib.my_get_physical_addresses.restype = ctypes.c_ulong
def virt2phys(addr):
"""VA -> PA"""
return lib.my_get_physical_addresses(ctypes.c_void_p(addr))
# -----------------------------
# sbrk(0) break
# -----------------------------
libc = ctypes.CDLL("libc.so.6")
libc.sbrk.restype = ctypes.c_void_p
libc.sbrk.argtypes = [ctypes.c_long]
def get_heap_break():
return libc.sbrk(0)
PAGE_SIZE = 4096
print("=== 初始 program break ===")
break_before = get_heap_break()
print(f"program break: {hex(break_before)}\n")
malloc_size = 4096 # 每次 malloc 4KB
buffers = []
# 循環 malloc 觀察 break 變化
for i in range(50):
buf = ctypes.create_string_buffer(malloc_size)
buffers.append(buf)
break_now = get_heap_break()
# 代表 heap 變大了
if break_now != break_before:
print(f"malloc {i+1}: break = {hex(break_now)}")
print(f" -> program break 增加了 {break_now - break_before} bytes")
# 列出新增 break 範圍的 VA -> PA
start_va = break_before
end_va = break_now
print("\n=== 新增 heap VA->PA ===")
for va in range(start_va, end_va, PAGE_SIZE):
try:
pa = virt2phys(va)
if pa == 0:
pa_str = "未分配"
else:
pa_str = f"0x{pa:x}" # 十六進位格式
except Exception:
pa_str = "未分配"
print(f"VA: 0x{va:x} -> PA: {pa_str}")
print()
break_before = break_now
```
#### 測試結果:


**可以看到上圖驗證了lazy allocation ,當我們要了一個很大的空間,但卻沒有實際去配置他時,他僅僅只在虛擬內存空間作分配,直到寫入後,頁表轉換為她才會去配置實際的記憶體,因此可以節省實體記憶體的開銷**
## <font color="#F7A004">Reference</font>
>[一文读懂 Linux 内存分配全过程](https://zhuanlan.zhihu.com/p/452139283)
> [奔跑吧 CH 3.1 進程的誕生](https://hackmd.io/@PIFOPlfSS3W_CehLxS3hBQ/S14tx4MqP)
>[add a system call教學網址 by 助教](https://hackmd.io/aist49C9R46-vaBIlP3LDA?both )
[Linux記憶體管理by Joseph](https://hackmd.io/@sysprog/linux-memory)
[肝下 50 万字的《Linux 内核精通》笔记,你的底层原理水平将从入门到入魔
](https://xie.infoq.cn/article/29552089e9a4bdaaca916c10d)