Lab1:Linux內核編譯及添加系統調用(詳細版)


實驗一:Linux內核編譯及添加系統調用(HDU)

花了一上午的時間來寫這個,良心制作,發現自己剛學的時候沒有找到很詳細的,就是泛泛的說了下細節地方也沒有,於是自己寫了這個,有點長,如果你認真的看完了,也應該是懂了。

一、前期准備工作

  1. 需要准備虛擬機上安裝Ubuntu,筆者安裝的是Ubuntu18.04,安裝的教程自行百度解決,教程很多。有幾點需要提一下,就是內存分配至少60G,核分配4個最好,為了在編譯的時候別崩潰。
    建議去熟悉一下Linux下面的文件目錄結構,根目錄下每個目錄一般會存放什么樣的文件
    ,然后常見命令操作也要熟悉一下。
  2. 下載Linux內核地址,自行選擇版本,建議選擇4.xx版本,因為版本高出錯的概率也大。
    下載好了之后,會放在自己的Ubuntu中的Downloads目錄下,同時是一個壓縮文件,到時候需要解壓到放內核目錄文件下。首先進入到該Downloads文件目錄下,查看是否下載好了。
$cd ~/Downloads
$ls
linux-4.19.25.tar.xz

之后開始解壓上面的那個壓縮文件到存放內核的地方,就是Linux系統的/usr/src目錄下,此目錄用來存放內核源碼的。從上圖也可以了解到。

cd ~/Downloads
tar xvJf linux-4.19.25.tar.xz -C /usr/src

進入/usr/src目錄查看是否有,如果有就可以開始后續工作了。

二、實驗要求和內容

1. 內容要求:

(1)添加一個系統調用,實現對指定進程的nice值的修改或讀取功能,並返回進程最新的nice值及優先級。建議調用原型是int mysetniec(pid_t pid, int flag, int nicevalue, void_user* prio, void_user* nice);
參數含義:
pid:進程ID
flag:若為0,則表示讀取nice的值;若為1,則表示修改nice的值。
nicevalue:為指定的進程設置新的nice。
prio,nice:指向進程的優先級和nice值。
返回值:系統調用成功時返回0;失敗時返回錯誤碼EFAULT。
(2)寫一個簡單的應用程序測試(1)中添加的系統調用。
(3)若系統調用了Linux的內核函數,要求深入閱讀相關的源碼。

2. Linux系統調用的基本概念

實質是指調用內核函數,於內核態中運行,Linux中的用戶通過執行一條訪管指令“int $0x80”來調用系統調用,該指令會產生一個訪管中斷,從而讓系統暫停當前的進程執行,而轉去執行系統調用處理程序。通過用戶態傳入的系統調用號從系統調用表中找到相應的服務例程的入口並執行,完成后返回。
(1)系統調用號與系統調用表:Linux內核中設置了一張系統調用表,用於關聯系統調用號及其相對應的服務例程入口地址,定義在./arch/x86/entry/syscalls/syscall_64.tbl文件中,每個系統調用占一個表項,一旦分配好就不可以有任何變更。
(2)系統調用服務例程:每個系統調用都對應一個內核服務例程來實現系統調用的功能,其命名的格式都是以"sys_開頭。其代碼通常放在./kernel/sys.c中,服務例程的原型聲明則是放在./include/linux/syscall.h中。如sys_open,通常格式是asmlinkage long sys_open(int flag......)。其中的amslinkage是一個必需的限定詞,用於通知編譯器從堆棧中提取函數的參數,而不是從寄存器中。
在sys.c中編程時,格式是SYSCALL_DEFINE5(mysetnice, pid_t, pid, int, flag, int, nicevalue, void __user *, prio, void __user *, nice) N=5代表參數的個數。
(3)系統調用參數傳遞:在X86中,Linux通過6個寄存器來傳入參數,其中一個eax是傳遞系統調用號,后面的5個傳遞參數。
(4)系統調用參數驗證

三、開始實驗

1. 切換到root權限下,防止權限不夠,導致出錯。

$ sudo passwd root
[sudo] password for leslie: 
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully
$ su root
Password:

(1)首先,你安裝Linux系統時,它會讓你設置一個你的用戶名和用戶密碼,在這里我設置的用戶名是leslie,即綠色字體leslie@tp50的前半部分,后半部分tp50是我的主機名。
sudo放在命令首,意思是當前指令以管理員權限運行。
(2)passwd是一條命令,用來修改用戶密碼,參數root是超級用戶名,擁有系統最高的權限。passwd root的意思是修改超級用戶的密碼,在創建Ubuntu時,默認超級用戶是沒有密碼的(也可能是一個隨機數之類的我不記得了),用這條命令重新設定一個密碼。
(3)su是一條命令,用來切換當前用戶,在第一章你會認識到,Linux是一個多用戶多任務的操作系統。參數root是用戶名,指示切換到的用戶,在這里su root意為切換到root用戶,當參數缺省時,默認切換到超級用戶。
(4)你會發現,它在提示輸入密碼時,雖然鍵盤已經輸入了密碼,但是終端沒有任何響應,不要擔心,這正是Unix和Linux的特點,為了確保安全,在輸入密碼時不顯示輸入的內容,在輸入密碼后,直接按下回車就好了。

2. 分配系統調用號,修改系統調用表

(1)查看系統調用表,並修改

gedit /usr/src/linux-4.19.25/arch/x86/entry/syscalls/syscall_64.tbl

你只需要將linux-4.19.25換成你自己下載好的版本即可。
你會看見這個格式

應用二進制接口分為三種:64、x32和common,即三種不同的調用約定,這里不需考慮太多,三種任意選擇一種即可,按照上述格式編寫新的系統調用表表項如下:

335	64	first_compile		__x64_sys_first_compile


(2)聲明系統調用服務例程
查看系統調用頭文件

gedit  /usr/src/linux-4.19.25/include/linux/syscalls.h

同樣將linux-4.19.25換成你自己的版本即可。

(3)實現自己的系統調用服務例程
首先進入解壓后的文件目錄,就是開始解壓放入的目錄,如下圖:

$cd /usr/src/linux-4.19.34(換成自己的版本即可)/kernel
vim sys.c


函數說明:
這一步與上一步的關系,就是C語言中頭文件與實現文件的關系,上一步我們對函數進行了聲明,這里給函數一個具體的實現。

首先要明確,我們要實現一個什么樣的功能,根據內容要求可知,這個系統調用需要具備對指定進程的nice值的修改及讀取的功能,同時返回進程最新的nice值及優先級prio。

把功能分拆成一個一個小塊,我們需要做到的有以下幾點:

根據進程號pid找到相應的進程控制塊PCB(因為進程控制塊中記錄了用於描述進程情況及控制進程運行所需要的全部信息,nice值和優先級正是其中的一部分);
根據PCB讀取它的nice值和優先級prio;
根據PCB對相應進程的nice值進行修改;
將得到的nice值和優先級prio進行返回。


SYSCALL_DEFINE5(first_compile, pid_t, pid, int, flag, int, nicevalue, void __user *, prio, void __user *, nice)
{
        int cur_prio, cur_nice;
        struct pid *ppid;
        struct task_struct *pcb;

        ppid = find_get_pid(pid);

        pcb = pid_task(ppid, PIDTYPE_PID);

        if (flag == 1)
        {
                set_user_nice(pcb, nicevalue);
        }
        else if (flag != 0)
        {
                return EFAULT;
        }

        cur_prio = task_prio(pcb);
        cur_nice = task_nice(pcb);

        copy_to_user(prio, &cur_prio, sizeof(cur_prio));
        copy_to_user(nice, &cur_nice, sizeof(cur_nice));

        return 0;
}

(4)開始編譯內核
首先,用下面這條命令查漏補缺,很有用處,用來它,我編譯是一次通過的,沒有遇見什么其他麻煩。

sudo apt-get install libncurses5-dev make openssl libssl-dev bison flex

然后定位到,源碼在的目錄,也就是解壓后放的目錄。

然后運行命令

make menuconfig


然后會出現以下界面,根據下面的圖片所指的按鈕來,通過左右鍵來確定光標停在選的按鈕上,enter鍵是確定鍵




准備工作做好后,開始編譯,耗時最長

sudo make -j4 2> error.log

-j4表示使用四線程進行編譯,這個過程大概持續一個小時,后面的重定向將錯誤信息輸出到了error.log這個文件里面,方便我們之后進行錯誤排查,不至於一兩個小時坐在電腦面前盯着信息輸出生怕出現一個錯誤而自己錯過了,之后修改只能靠兩眼排查,相信我,那不是一種好的體驗。
開始等待吧,結束后就可以安裝內核了。
(5)安裝內核
此時還是在你原來的目錄路徑下
安裝模塊:

sudo make modules_install

使用這一行命令進行模塊的安裝,模塊的安裝持續時間大概在十幾分鍾左右,視你分配的資源多寡這個時間會適當地增加或減少。
結束后
安裝內核:

sudo make install

使用這一行命令進行內核的安裝,內核的安裝持續時間大概是幾分鍾,視你分配的資源多寡這個時間會適當地增加或減少。

這一步完成且沒有任何錯誤后,恭喜你,你已經完成了絕大多數的工作了,剩下的都是一些簡單且容易調試的內容,重啟你的電腦/虛擬機。
這個時候可以查看你的/lib/module目錄下有無安裝好的內核了。

(6)重啟系統
查看內核版本的命令,如下,你可以看自己現在的版本是否是新安裝的內核

uname -a

之后可能會彈出這個界面,選擇你自己剛剛編譯好的內核即可

(7)測試
自己選擇一個文件夾,存放自己的測試代碼,創建.c文件

vim test.c

#include <unistd.h>
#include <sys/syscall.h>
#include <stdio.h>
#define _SYSCALL_MYSETNICE_ 335
#define EFALUT 14

int main()
{
    int pid, flag, nicevalue;
    int prev_prio, prev_nice, cur_prio, cur_nice;
    int result;

    printf("Please input variable(pid, flag, nicevalue): ");
    scanf("%d%d%d", &pid, &flag, &nicevalue);

    result = syscall(_SYSCALL_MYSETNICE_, pid, 0, nicevalue, &prev_prio,
                     &prev_nice);
    if (result == EFALUT)
    {
        printf("ERROR!");
        return 1;
    }

    if (flag == 1)
    {
        syscall(_SYSCALL_MYSETNICE_, pid, 1, nicevalue, &cur_prio, &cur_nice);
        printf("Original priority is: [%d], original nice is [%d]\n", prev_prio,
               prev_nice);
        printf("Current priority is : [%d], current nice is [%d]\n", cur_prio,
               cur_nice);
    }
    else if (flag == 0)
    {
        printf("Current priority is : [%d], current nice is [%d]\n", prev_prio,
               prev_nice);
    }

    return 0;
}

之后使用gcc進行編譯,根據要求輸入對應的值。
結束了。到這一步,你就熟悉了過程了,對於編譯和添加內核有個基本的了解了。

最后附上需要用到的內核源碼截圖。

Linux源碼地址
第一張圖和第二張圖是一個函數set_user_nice()

task_on_rq_quened()



task_current()

effective_prio()








免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM