OS 實驗一 | linux 內核編譯及添加系統調用

設計目的

Linux 是開源操作系統,用戶可以根據自身系統需要裁剪、修改內核,定製出功能更加

合適、運行效率更高的系統,因此,編譯 linux 內核是進行內核開發的必要基本功。

在系統中根據需要添加新的系統調用是修改內核的一種常用手段,通過本次實驗,讀

者應理解 linux 系統處理系統調用的流程以及增加系統調用的方法。

內容要求

(1)添加一個系統調用,實現對指定進程的 nice 值的修改或讀取功能, 並返回進程最

新的 nice 值及優先順序 prio。建議調用原型為:

int mysetnice(pid_t pid, int flag, int nicevalue, void __user * prio, void __user * nice);

參數含義:

  • pid:進程 ID。
  • flag:若值為 0,表示讀取 nice 值;若值為 1,表示修改 nice 值。
  • prio、nice:進程當前優先順序及 nice 值。

返回值: 系統調用成功時返回 0,失敗時返回錯誤碼 EFAULT。

(2)寫一個簡單的應用程序測試(1)中添加的系統調用。

(3)若程序中調用了 linux 的內核函數,要求深入閱讀相關函數源碼。

實驗準備

我使用的是美如畫的 Deepin 15.5 64 位操作系統,與 Windows10 並存的雙系統中。

使用的內核源碼是從 The Linux Kernel Archives下載的教老版本的穩定內核 4.4.102。將其解壓保存到了主目錄下。

注意點:

  • 使用虛擬機的話建議不要吝嗇存儲空間,建議設置30G~50G大小。太小會導致沒有空間存儲編譯後的文件,編譯出現錯誤。
  • 盡量調高虛擬機的硬體配置,以減少編譯所需要的時間。
  • 沒必要選擇過新的內核版本,會導致編譯時間的增長,對於新版本的資料相對也比較少。
  • 如果對 Linux 系統的 boot 分區大小單獨進行設置的話,建議選擇 3G~5G 的大小,否則會導致新內核編譯完成安裝時提示 boot 分區大小不足,出現錯誤。

實驗內容

(1)分配系統調用號,修改系統調用表

  • 查看系統調用表(./arch/x86/entry/syscalls/syscall_64.tbl)

  • 在這裡我們需要選擇一個未使用過的系統調用號進行分配,當前系統使用到325號,所以我們選擇新添加的系統調用號為326號。

(2)申明系統調用服務常式原型

Linux系統調用服務常式的原型申明在文件 (./include/linux/syscalls.h) 中,我們可以在末尾添加如圖所示的內容:

每個系統調用都對應一個內核服務常式來實現該系統調用的具體功能,其命名格式都是以「sys_」開頭。

其中「asmlinkage」是一個必須的限定詞,用於通知編譯器僅從堆棧中提取該函數的參數,而不是從寄存器中,因為在執行服務常式之前系統已經將通過寄存器傳遞過來的參數值壓入內核堆棧了。

(3)實現系統調用服務

下面為新調用的 mysetnice 編寫服務常式 sys_musetnice, 通常添加在 sys.c 文件中,其路徑為 (./kernel/sys.c )。

/* * my scall * 添加一個系統調用,實現對指定進程的 nice 值的修改或讀取功能, * 並返回進程最新的 nice 值及優先順序 prio。 * 參數含義: * pid:進程 ID。 * flag:若值為 0,表示讀取 nice 值;若值為 1,表示修改 nice 值。 * prio、nice:進程當前優先順序及 nice 值。 * 返回值:系統調用成功時返回 0,失敗時返回錯誤碼 EFAULT。 */SYSCALL_DEFINE5(mysetnice, pid_t, pid, int, flag, int, nicevalue, void __user *, prio, void __user *, nice){ struct pid * kpid; struct task_struct * task; int nicebef; int priobef; kpid = find_get_pid(pid); // 返回 pid task = pid_task(kpid, PIDTYPE_PID); // 返回 task_struct nicebef = task_nice(task); priobef = task_prio(task); if(flag == 1){ set_user_nice(task, nicevalue); printk("修改前的nice值:%d 修改後的nice值:%d
", nicebef, nicevalue); return 0; } else if(flag == 0){ copy_to_user(nice, (const void*)&nicebef, sizeof(nicebef)); copy_to_user(prio, (const void*)&priobef, sizeof(priobef)); printk("該進程的nice值是:%d
", nicebef); printk("該進程的prio值是:%d
", priobef); return 0; } printk("你輸入的 flag 有誤,請重新輸入!
"); return EFAULT;}

這是一個簡單的實現讀取進程 nice 值和修改進行 nice 值的服務。當參數 flag 為 0 的時候讀取 nice 值,並將數據從內核空間拷貝到用戶空間。當 flag 為 1 的時候修改 nice 值為 nicevalue 的值。

在新版本的內核中,引入了宏「SYSCALL_DEFINEN(sname)」對服務常式原型進行了封裝,其中的「N」是該系統調用所需要參數的個數, 在這裡我們使用了 5 個參數所以是 DEFINE5。

內核編譯

(1)清除殘留的.config 和.o 文件

編譯出錯需要重新編譯或不是第一次編譯,都需要清除殘留的.config 和.o 文件,方法是進入Linux內核所在的子目錄, 執行以下命令:

# make mrproper

這裡可能會提醒安裝 ncurses 包,在 ubuntu 中 ncurses 庫的名字是 libncurses5-dev,所

以安裝命令是:

# apt-get install libncurses5-dev

安裝完成後,再重新執行清楚編譯結果的指令即可。

(2)配置內核

運行的命令是:

# make menuconfig

運行該命令過程中,可能會報錯提示缺少一個套件 ncurses devel,那麼你需要手動對其進行安裝,安裝方法同上安裝 ncurses 包的過程。

在執行 make menuconfig 命令的時候會出現一個配置對話框,我們一般採用默認值:選擇<save>保存配置信息,然後選擇<exit>退出對話框。

(3)編譯內核,生成啟動映像文件

內核配置完後,編譯內核,生成啟動映像文件 bzlmage ,位於 (./arch/x86_64/boot/bzlmage) 中:

執行命令:

# make

這裡也許會提示你沒有安裝 openssl,安裝的方法如下:

# apt-get install libssl-dev

內核編譯時間可能較長,視硬體配置而定,一般情況為2小時左右,可以喝杯茶,看看小說放鬆放鬆再來。

(4)編譯模塊

執行命令:

# make modules

(5)安裝內核

安裝模塊:

# make modules_install

安裝內核:

# make install

(6)配置 grub 引導程序

只需要執行命令:

# update-grub2

就會自動修改 grub。

(7)重啟系統

重啟系統後,我們可以通過終端來查看新內核的版本。輸入如下指令

uname -r

觀察顯示的內核版本是否與我們重新編譯的內核版本一致。

編寫用戶態測試程序

到目前為止,一切看起來都十分美好。

下面,我們就要對我們直接寫的系統調用服務進行檢驗了。如下是我編寫的測試程序:

#define _GNU_SOURCE#include <unistd.h>#include <sys/syscall.h>#include <stdio.h>#define __NR_mysetnice 326 //系統調用號int main() { pid_t tid; int nicevalue; int prio = 0; int nice = 0; tid = getpid(); syscall(__NR_mysetnice,tid,0,-5,&prio,&nice);//read printf("pid: %d
prio: %d
nice: %d
", tid, prio,nice); syscall(__NR_mysetnice,tid,1,-5,&prio,&nice);//set printf("pid: %d
prio: %d
nice: %d
", tid, prio,nice); syscall(__NR_mysetnice,tid,0,-5,&prio,&nice);//read printf("pid: %d
prio: %d
nice: %d
", tid, prio,nice); printf("*******************************
"); syscall(__NR_mysetnice,tid,0,-15,&prio,&nice);//read printf("pid: %d
prio: %d
nice: %d
", tid, prio,nice); syscall(__NR_mysetnice,tid,1,-15,&prio,&nice);//set printf("pid: %d
prio: %d
nice: %d
", tid, prio,nice); syscall(__NR_mysetnice,tid,0,-15,&prio,&nice);//read printf("pid: %d
prio: %d
nice: %d
", tid, prio,nice); return 0;}

在這裡我們設置的 nice 值參數表示的是與執行指令的優先權等級,等級範圍為 -20 ~ 19。數值越小,等級越高。只有系統管理者才可以設置負數的等級。更多關於linux進程調度,優先順序、進程nice值的講解可以觀看 linux進程調度,優先順序、進程nice值 - CSDN博客 。

我們在測試程序中先後設置了 nice 值為 -5 和 -15 的情況,觀察當前進程調度塊的 nice 值和 prio 值的變化。顯示結果如下:

這裡我們沒有在控制台顯示 printk 所列印的內容。這個情況與你的操作系統本身有關,也許你的系統是可以顯示的。如果你也和我一樣不能顯示 printk 列印的結果,可以使用如下的方法使其進行顯示:Ubuntu下查看Printk的輸出_Linux教程_Linux公社-Linux系統門戶網站。

打開另外的一個終端,用一個終端不停地監視並且列印輸出當前系統的日誌信息:

while true do sudo dmesg -c sleep 1 done

這樣這個終端就會每1秒查看當前系統的日誌並清空。

我的顯示結果如下:

關於本次實驗的全部代碼可以查閱:shaonianruntu/OSlab


推薦閱讀:

  • linux內核實現添加系統調用,實現修改和讀取nice值 - 後台君
  • linux進程調度,優先順序、進程nice值 - CSDN博客
  • Ubuntu下查看Printk的輸出_Linux教程_Linux公社-Linux系統門戶網站

推薦閱讀:

生物實驗室的翡翠鑒定入門
這是一種能推斷出我們的大腦在經歷什麼的研究方法
高轉化率的著陸頁巧用實驗提高著陸頁信息的可信度
一系列堪比藝術大片的彩虹實驗,在家就能DIY,有視頻有圖文

TAG:操作系統 | 實驗 |