# 2025 NCU Linux Project 1 [Group 5] ### 組長:林緯宸(114525001) ### 組員:簡文勝(114522008)、林暐智(114522014)、張文瀚(114522087) ## ## Part 1: Source Code & Add a System Call 下載kernel ``` wget -P ~/ https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.137.tar.xz tar -xvf linux-5.15.137.tar.xz -C /usr/src ``` 創建mygetphy資料夾放入以下Source Code ``` cd /usr/src/linux-5.15.137 mkdir mygetphy cd mygetphy ``` **Source Code** ```c #include <linux/kernel.h> #include <linux/syscalls.h> #include <linux/mm.h> #include <linux/sched/mm.h> #include <asm/pgtable.h> // pgd/p4d/pud/pmd helpers, pmd_large, masks SYSCALL_DEFINE1(mygetphy, void *, va) { unsigned long vaddr; unsigned long paddr; struct mm_struct *mm; // 描述 process 的整個 virtual mem. space(哪裡是 code / heap / stack / mmap) pgd_t *pgd; p4d_t *p4d; pud_t *pud; pmd_t *pmd; pte_t *pte; spinlock_t *ptl; // 修改/讀取 PTE 時避免race condition phys_addr_t base; // Physical page frame address base(加上 offset 後才是Physical address) vaddr = (unsigned long)va; paddr = 0; mm = current->mm; mmap_read_lock(mm); // 對這個 process 的 mem. 資料結構加 read_lock,避免同時有在改 page table pgd = pgd_offset(mm, vaddr); // 用 vaddr 算出它在 PGD 裡的那個項目 if (pgd_none(*pgd) || pgd_bad(*pgd)) goto out; // 如果空或壞,直接跳到 out,回傳 0 p4d = p4d_offset(pgd, vaddr); // 從 PGD 找到 P4D if (p4d_none(*p4d) || p4d_bad(*p4d)) goto out; pud = pud_offset(p4d, vaddr); // 從 P4D 找到 PUD if (pud_none(*pud) || pud_bad(*pud)) goto out; pmd = pmd_offset(pud, vaddr); // 從 PUD 找到 PMD if (pmd_none(*pmd) || pmd_bad(*pmd)) goto out; /*都沒提早跳,代表這個 VA 所在的「大範圍」在 page table 中是有東西的,接下來要看是 huge page or 4KB page*/ /* THP(Transparent Huge Page), huge page(2MB) at PMD level, no need PTE anymore */ #ifdef PMD_PAGE_MASK if (pmd_large(*pmd)) { // 如果是 huge page base = (phys_addr_t)pmd_pfn(*pmd) << PAGE_SHIFT; // 從 PMD entry 拿到 PFN,PAGE_SHIFT 轉成這個 huge page 的 page frame address base paddr = base | (vaddr & ~PMD_PAGE_MASK); // 加上 offset 得到最終的 physical address goto out; } #endif /* Normal 4KiB page 要進到最後一層 PTE */ pte = pte_offset_map_lock(mm, pmd, vaddr, &ptl); // 算出該 VA 對應哪一個 PTE,把那一小塊 page table 映射到 kernel address space,同時把 ptl(那個 page table 對應的 lock)鎖住。 if (!pte) goto out; if (pte_present(*pte)) { base = (phys_addr_t)pte_pfn(*pte) << PAGE_SHIFT; // 從 PTE entry 拿到 PFN,PAGE_SHIFT 轉成這個 page 的 page frame address base paddr = base | (vaddr & ~PAGE_MASK); // 加上 offset 得到最終的 physical address } pte_unmap_unlock(pte, ptl); out: mmap_read_unlock(mm); return paddr; /* 0 表示未映射 */ } ``` 在mygetphy下創建Makefile ``` obj-y := mygetphy.o ``` 回到kernel資料夾並編輯該資料夾下原有的Makefile ``` cd /usr/src/linux-5.15.137 ``` 在第二個core-y後加上 mygetphy/ ``` core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ mygetphy/ ``` 修改 syscall_64.tbl檔 在最後一行添加上 system call ``` 450 common mygetphy sys_mygetphy ``` 編輯 syscalls.h 檔加上 ```c asmlinkage long sys_mygetphy(void); ``` 編譯與安裝kernel 並重啟系統 ``` make localmodconfig make -j4 make modules_install -j4 update-grub reboot ``` ## Part2: Result ### Question 1 在任意資料夾下新增q1.c ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define SYS_GET_PHY 450 // <-- 這裡換成你的 syscall number unsigned long my_get_physical_addresses(void *va) { return syscall(SYS_GET_PHY, va); } int main(void) { unsigned long length = 4 * 4096; // malloc void *addr = malloc(length); if (addr == NULL) { perror("mmap"); return 0; } printf("malloc() returned address = %p\n\n", addr); printf("Check VA -> PA before access:\n"); for (size_t i = 0; i < length; i += 4096) { unsigned long pa = my_get_physical_addresses((void *)addr + i); printf(" VA: %p -> PA: %p\n", (char *)addr + i, (void *)pa); } printf("\nTouching memory (write 1 byte per page)...\n"); for (size_t i = 0; i < length; i += 4096) { ((char *)addr)[i] = 42; } printf("\nCheck VA -> PA after access:\n"); for (size_t i = 0; i < length; i += 4096) { unsigned long pa = my_get_physical_addresses((void *)addr + i); printf(" VA: %p -> PA: %p\n", (char *)addr + i, (void *)pa); } return 0; } ``` 編譯&執行結果 ``` gcc -o q1 q1.c ./q1 ``` ![Q1_result](https://hackmd.io/_uploads/B14bQL7xbg.png) ### Question 2 在任意資料夾下建立python虛擬環境並執行 ``` python -m venv venv source ./venv/bin/activate ``` 新增my_get_phy.c並編譯 ```c // my_get_phy.c #include <unistd.h> #define SYS_mygetphy 450 // 你設定的 syscall number // Python 會用 ctypes 呼叫這個函數 unsigned long my_get_physical_addresses(void *virtual_addr) { return (unsigned long)syscall(SYS_mygetphy, virtual_addr); } ``` ``` gcc -Wall -O2 -fPIC -shared -o lib_my_get_phy.so my_get_phy.c ``` 新增q2.py ```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 ``` 執行結果 ``` python q2.py ``` ![Q2_result](https://hackmd.io/_uploads/ByzGX8mxZl.png)