# 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
```

### 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
```
