# 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 ``` ![582116085_1369947167873949_5067527698500628642_n](https://hackmd.io/_uploads/r1BLpfweZx.png) 遇到的問題: 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級轉換) ![page_table](https://hackmd.io/_uploads/HJtf2Dwlbx.png) ### 系統架構圖: 1. 系統在context switch時會將cr3占存器時設成下一個progess的pgd entry,並且current指標總是指向當前task_struct結構體 ![hWJh8f8](https://hackmd.io/_uploads/B1n-gX_xWe.png) 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為一 ![image](https://hackmd.io/_uploads/B1RDPUue-g.png) 4.當正式要寫入memory前,會再跑一次頁表pgd->p4d->pud->pmd-pte進行轉換 5.,因為pte_valid_bit已設為一,因此可以直接找到實體記憶體位置! ## <font color="#F7A004">測試結果</font> ![582721741_1332781291660040_6193217228839204498_n](https://hackmd.io/_uploads/SJxcP7ul-e.png) **觀察上圖可以看到,在寫入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 ``` #### 測試結果: ![581786627_1728363234503937_2194650492205630891_n](https://hackmd.io/_uploads/ByuIomdlbg.png) ![582204628_25326257273657230_6449595408017005402_n](https://hackmd.io/_uploads/SkRNh7uxZl.png) **可以看到上圖驗證了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)