# **Semester Project** 感謝許富皓教授授予我們滿滿的乾貨,使我們獲益良多。 Group Member: 單一梵 林庭亘 林芷儀 # 專案描述: 使用您在項目 1 中編寫的系統調用編寫一個程序,以檢查同時執行該程序的兩個進程如何共享內存區域。 內存區包括代碼段、數據段、BSS段、堆段、庫、棧段。 暗示 進行檢查時,兩個相關過程都必須在進行中。因此,您可能需要使用函數 sleep() 來保證此要求。 在 Linux 內核中,您需要使用函數 copy_from_user() 和函數 copy_to_user() 將數據從/複製到用戶地址緩衝區。 Linux 線程的用戶地址空間由以下部分組成。 文本段 數據段(具有初始值的全局變量) BSS段(沒有初值的全局變量) 堆段(通過函數分配的內存區域malloc()) 圖書館 棧段 在這個項目中,你需要先添加一些新的系統調用。新系統調用的功能和數量由您決定。 編寫一個包含三個線程(主線程、線程 1 和線程 2)的多線程程序和新的系統調用,以檢查一個線程的哪些段由哪些其他線程共享。 不需要計算每個線程段的大小、星地址和結束地址。但是,如果您能獲得每個線程段的大小、星級地址和結束地址,我們將為 您的學期項目成績 加30分。 編寫一份報告來描述您的多線程程序創建的結果。您的報告應包含如下圖以總結您的結果。 ![](https://i.imgur.com/KWVYSli.png) ~~~~ Global data pointer variables and global function pointer variables are shared by all threads; hence, they are ideal items to pass information among threads. Two threads show a physical memory cell (one byte) if both of them have a virtual address that is translated into the physical address of the memory cell. The kernel usually does not allocate physical memories to store all code and data of a process when the process starts execution. Hence, if you want kernel to allocate physical memories to a piece of code, execute the code first. If you want kernel to allocate physical memories to a variable, access the variable first. Inside the Linux kernel, you need to use function copy_from_user() and function copy_to_user() to copy data from/to a user address buffer. Check the "Referenced Material" part of the Course web site to see how to add a new system call in Linux. ~~~~ # 執行環境 Kernel 與 OS 版本 * Linux Kernel 5.8.1 * Ubuntu 20.04 LTS desktop-amd64 * vm workstation # Kernel 編譯/新增System call的過程所遇到的問題 * 我們主要參考這兩篇教學的步驟: – https://dev.to/jasper/adding-a-system-call-to-the-linux-kernel-5-8-1-in-ubuntu-20-04-lts-2ga8 – https://blog.kaibro.tw/2016/11/07/Linux-Kernel編譯-Ubuntu/ * 但有加入幾個步驟修正make時遇到的問題: 問題 一 No rule to make target ‘debian/canonical-certs.pem‘, needed by ‘certs/x509_certificate_list‘ 解法:编辑.config文件,修改CONFIG_SYSTEM_TRUSTED_KEYS,将改成空值:""。 問題 二 CONFIG_X86_X32 enabled but no binutils support 解法: 編輯.config文件,修改CONFIG_X86_X32 = n # 第 1 步- 環境準備 1. 全面更新您的操作系統。 `sudo apt update && sudo apt upgrade -y` 1. 下載並安裝必要的包來編譯內核。 `sudo apt install build-essential libncurses-dev libssl-dev libelf-dev bison flex -y` 如果寧願使用vim或任何其他文本編輯器而不是nano,下面是如何安裝它的示例。 sudo apt install vim -y 1. 清理已安裝的軟件包。 `sudo apt clean && sudo apt autoremove -y` 1. 將最新穩定版 Linux 內核(截至 2020 年 8 月 12 日為 5.8.1)的源代碼下載到您的主文件夾。 `wget -P ~/ https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.8.1.tar.xz` 如果您下載了較新版本的 Linux 內核,請參閱此文檔以了解對系統調用所做的任何相關更改。 1. 將您剛剛下載的 tarball 解壓縮到您的主文件夾中。 `tar -xvf ~/linux-5.8.1.tar.xz -C ~/` 1. 重新啟動計算機。 # 第 2 步-編輯程式碼 1. 檢查當前內核的版本。 `uname -r` 5.4.0-42-generic 1. 將您的工作目錄更改為最近解壓的源代碼的根目錄。 `cd ~/linux-5.8.1/` 1. 創建系統調用的主目錄。 為您的系統調用確定一個名稱,並從現在開始保持一致。我選擇了identity。 `mkdir identity` 1. 1. 為您的系統調用創建一個 C 文件。 使用以下命令創建 C 文件。 `nano identity/identity.c` 在其中寫入以下代碼。 ` #include <linux/kernel.h> #include <linux/syscalls.h> SYSCALL_DEFINE0(identity) { printk("I am tingegg.\n"); return 0; } ` 1. 為您的系統調用創建一個 Makefile。 使用以下命令創建 Makefile。 `nano identity/Makefile` 在其中寫入以下代碼。 `obj-y := identity.o` 保存並退出文本編輯器。 1. 將系統調用的主目錄添加到內核的主 Makefile 中。 使用以下命令打開 Makefile。 `nano Makefile` 搜索core-y。在第二個結果中,您將看到一系列目錄。 `kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/` 在 Linux 5.8.1 內核的最新源代碼中,它應該在第 1073 行。 在末尾添加系統調用的主目錄,如下所示。 kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ identity/ 保存並退出編輯器。 1. 在系統調用的頭文件中為您的系統調用添加相應的函數原型。 使用以下命令打開頭文件。 `nano include/linux/syscalls.h` 導航到它的底部並在上面編寫以下代碼#endif。 `asmlinkage long sys_identity(void);` 保存並退出編輯器。 1. 將您的系統調用添加到內核的系統調用表中。 使用以下命令打開表。 `nano arch/x86/entry/syscalls/syscall_64.tbl` `440 common identity sys_identity` 在 Linux 5.8.1 內核的最新源代碼中,你的系統調用號應該是 440。 保存並退出編輯器。 # 第 3 步- 安裝新kernel 在本節中,您將安裝新內核並準備操作系統以啟動它。 1. 配置內核。 確保終端窗口已最大化。 使用以下命令打開配置窗口。 `make menuconfig` 使用Tab在選項之間移動。不做任何更改以將其保留為默認設置。 保存並退出。 1. 找出你有多少個邏輯內核。 `nproc` 以下幾個命令需要很長時間才能執行。並行處理將大大加快它們的速度。對我來說,它是12。因此,我會-j在下面的命令後面加上 12。 1. 編譯內核的源代碼。 `make -j12` 1. 準備內核安裝程序。 `sudo make modules_install -j12` 1. 安裝內核。 `sudo make install -j12` 1. 使用新內核更新操作系統的引導加載程序。 `sudo update-grub` 1. reboot。 # 第 4 步- 結果 檢查我們所編寫的 c program 在kernel 編譯完成後是否可以產生結果 1. 檢查當前內核的版本。 `uname -r` 它應該顯示以下內容。 5.8.1 1. 將您的工作目錄更改為您的主目錄。 cd ~ 1. 創建一個 C 文件來生成系統調用成功或失敗的報告。 使用以下命令創建 C 文件。 `nano report.c` 在其中寫入以下代碼。 ``` #include <linux/kernel.h> #include <sys/syscall.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <errno.h> #define __NR_identity 440 long identity_syscall(void) { return syscall(__NR_identity); } int main(int argc, char *argv[]) { long activity; activity = identity_syscall(); if(activity < 0) { perror("Sorry, Tingegg. Your system call appears to have failed."); } else { printf("Congratulations, Tingegg! Your system call is functional. Run the command dmesg in the terminal and find out!\n"); } return 0; } ``` 保存並退出編輯器。 1. 編譯您剛剛創建的 C 文件。 `gcc -o report report.c` 1. 運行剛剛編譯的 C 文件。 `./report` 1. 檢查dmesg輸出的最後一行。 `dmesg` 在底部,您現在應該看到以下內容。 `I am tingegg.` # 執行結果 * 失敗畫面! https://hackmd.io/@WeiHeng/BknsDcCBK ![](https://i.imgur.com/mDoxyW6.png) * 使用上述make遇到的問題,進行修正確認可以完成編譯 * 運用dmseg 產生結果 * 結果為可以產生,c 語言 ![](https://i.imgur.com/LDFMnMU.png) # Q&A補充部分 # fork + pthread_create 記憶體空間差異 參考資料如下: https://jasonblog.github.io/note/linux_system/fork_+_pthreadcreate_ji_yi_ti_kong_jian_cha_yi.html * pthread_create()函數的線程函數必須是靜態的函數,以標準的__cdecl的方式調用的 * C++的成員函數是以__thiscall的方式調用的,相當於一個普通函數有一個默認的const ClassType* this參數 # copy_from_user() copy_to_user() 參考資料如下: https://cloud.tencent.com/developer/article/1810457 https://stackoverflow.com/questions/51026411/convert-virtual-address-to-physical-address https://hackmd.io/@sakuraiiii/r1gFubKkF 因為copy_to_user會檢查access_ok是否使用者操作非法地址,例如避免輸入kernel space的位址會讓這個指令去修改其他kernel的資料。 * 硬體會有一個開關來確定是否開啟存取,copy_to_user會經過這個PAN,增加安全性 在read/write/ioctl等系統調用裏,經常需要從用戶空間讀取數據,或者向用戶空間的地址寫入數據。如果應用程序傳入了一個參數user_arg,指向的是用戶空間的地址。那麼我們在內核態裏能否直接從這個地址讀取數據呢?答案是肯定的,因爲內核能夠看到進程的整個地址空間,屬於這個進程的所有page在此進程的page table裏,內核函數當然可以訪問那個指針user_arg。 * 爲何一定要用copy_from_user/copy_to_user: 首先,直接使用那個地址很不安全,如果應用程序傳過來一個非法地址,就有可能panic系統。或者應用程序可以給出一個指針指向kernel地址(>PAGE_OFFSET),那樣就可以隨意訪問kernel page數據。 所以,我們需要使用內核提供的方法對應用程序傳過來的地址進行檢查和驗證:是不是用戶空間地址;是否已經使用malloc/mmap/brk等分配(即是否落於有效的VMA內)。。。 access_ok可以粗略地檢查user_arg是否位於用戶空間,見access_ok( arch/x86/include/asm/uaccess.h)。 而檢查user_arg是否屬於已分配的用戶空間地址則要推遲到page fault發生時。這就是fixup存在的原因。觸發pagefault的指令(處於進程上下文)和處理pagefault的函數(中斷上下文)處於不同的context裏,pagefault handler沒有辦法直接返回錯誤,這是與函數調用最大的不同之處。