Linux学习之内核模块编程
前言
之前成功編譯了內核,這次學習如何修改增加刪除內核模塊,為了保證內核的純凈,我特意重新編譯安裝了一個新的5.11.8的內核,其他內核同理。
本文原創,創作不易,轉載請注明!!!
本文鏈接
個人博客:https://ronglin.fun/?p=169
PDF鏈接:見博客網站
CSDN: https://blog.csdn.net/RongLin02/article/details/115422459
通過內核模塊顯示進程控制塊信息
環境
主機:聯想Y7000P;64位windows10;CPU:i7-9750H;顯卡:GTX 1660 Ti;內存:16G
虛擬機:Ubuntu 18.04 LTS;硬盤100G;內存4G;64位;4核心
Linux內核:5.11.8
實驗簡述
首先先看實驗內容,實驗內容來自指導書,如有侵權聯系我刪除:
實驗說明
在內核中,所有進程控制塊都被一個雙向鏈表連接起來,該鏈表中的第一個進程控制塊為init_task。編寫一個內核模塊,模塊接收用戶傳遞的一個參數num,num指定要打印的進程控制塊的數量;若用戶不指定num或者num<0,模塊則打印所有進程控制塊的信息。需要打印的進程控制塊信息有:進程PID和進程的可執行文件名。
解決方案
(1)定義模塊參數
該模塊需要接受用戶傳遞的參數,在使用該參數之前,需要在代碼中預先定義好該參數,將該參數的類型設置為整型,并且在sysfs文件系統中的權限是只讀的。定義的方法為:
static int num=-1;
module _param(num, int, S_IRUGO);
該參數的初始值被設置為-1。-1將作為打印所有進程控制塊的標記,默認值為-1,意味著當用戶不傳入任何參數時,模塊將打印所有的進程的信息。
(2)訪問進程控制塊鏈表
在內核中,進程控制塊被組織成多個雙向鏈表,其中有一個雙向鏈表包含所有的進程
精袈
塊,只需要訪問該雙向鏈表,就可以訪問到所有進程控制塊。Linux內核中幾乎所有雙向
都采用相同的數據結構來實現,內核中定義list_head通用數據結構,其定義如下:
list_head中,next指向鏈表中的下一個list_head 數據結構,prev指向鏈表中的前一個list_head 數據結構。之所以說該結構是用于實現一個通用的雙向鏈表是因為:如果一個數據結構包含list_head結構,開發者就可以通過內核提供的一組宏創建并操作一個雙向鏈表,而該鏈表中元素的類型為該數據結構,如圖12-1所示:
在進程控制塊task_struct中,包含一個名為tasks 的成員,該成員的類型為list_head,這意味著進程控制塊能夠通過該成員將進程控制塊串成一個雙向鏈表。Linux內核通過該成員將所有的進程都放入同一個雙向鏈表,因為 list_head 結構中的next 和 prev指針并不是指向包含list_head 的數據結構,而是指向另一個list_head數據結構。為了訪問包含list_head的數據結構,內核提供一個宏:
在該宏中, ptr是一個指向list_head的指針, type是包含list_head的數據結構類型,而member是list_head在該數據結構中的成員名。例如,若一個進程控制塊中的tasks 的地址為p,為了訪問該進程控制塊,可以采用:
list_entry(p,struct task _struct,tasks);該宏便會返回該進程控制塊的地址。
知道如何使用雙向鏈表后,就可以方便地訪問內核中所有的進程控制塊,因為它通過tasks成員串成一個雙向鏈表,如果得到一個進程控制塊的地址p,開發者可以通過:
訪問該雙向鏈表中的下一個進程控制塊。在該雙向鏈表中,第一個進程控制塊為init_task,如果開發者發現下一個進程控制塊為init_task時,說明已經完整地遍歷過所有進程控制塊。
內核定義宏for_each process用于遍歷所有的進程控制塊,開發者通過該宏就能將所有的進程控制塊訪問一遍,該宏展開的形式為:
(3)輸出進程控制塊信息
進程控制塊中包含進程大部分信息,根據實驗要求,模塊需要打印進程的 pid和可執行文件名,在進程控制塊的數據結構中,成員pid為進程的PD,而成員comm包含進程的可執行文件名。在內核中,模塊可以通過 printk( )內核函數將這些信息打印到系統日志中。
程序框架
實操
實驗說明太多了,看的有點頭大,幸好它給了源碼,直接上手實操,出了問題再看資料。
模塊文件知識
先把程序框架中的代碼敲出來,exp_exit()函數中就添加一句
printk("Good bye,Ronglin\n");先大概的講述一下c文件的內容:
頭文件聲明
頭兩行是模塊頭文件,頭文件module.h和 init.h是必不可少的。module.h包含加載模塊需要的函數和符號定義; init.h中包含模塊初始化和清理函數的定義。如果在加載時允許用戶傳遞參數,模塊還應該包含moduleparam.h頭文件
模塊許可聲明
從內核v2.4.10版本開始,模塊必須通過MODULE_LICENSE宏聲明此模塊的許可證,否則在加載此模塊時,內核會顯示“kernel tainted”(內核被污染)的警告信息。從linux/module.h文件中可看到,被內核接受的許可證有GPL,GPL v2,GPL and additional rights,Dual BSD/GPL,Dual MPL/GPL,Dual MIT/GPL和Proprietary。
初始化和清理函數
聲明內核模塊必須調用宏module_init和 module_exit 去注冊初始化與清理函數。在模塊源代碼的最后兩行已聲明該模塊被加載時的初始化函數是exp_init(),模塊被卸載時的清理函數是 exp_exit()。需要注意,初始化與清理函數必須在宏module_init和 module_exit使用前定義,否則會出現編譯錯誤。這兩個函數配對使用,例如,當module_init()申請一個資源,那么module_exit()中就應釋放這個資源,使得模塊不留下任何副作用。
一般來說有這上述3部分足以。
大概了解了c文件結構,再來看看makefile文件的結構
obj-m:=listprocess.o KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd)default:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesobj-m:=listprocess.o其實核心部分就是這一句,obj-m:=listprocess.o,生成的內核模塊名為listprocess.ko。如果需要生成一個名為mymodule.ko 的內核模塊,并且該內核模塊的源代碼來源于modulesrc1.c和 modulesrc2.c兩個文件, makefile文件應該寫成如下形式:
obj -m:-mymodule.o module objs:-modulesrc1.o modulesrc2.o如果用戶采用這種makefile,在調用make命令時,需要將內核源代碼所在目錄作為一個參數傳遞給make命令。
例如,如果v2.6的內核源代碼位于/usr/src/linux-2.6目錄下,用戶模塊源代碼所在目錄應該使用的make命令為:
makefile還提供另一種形式,用戶可指定內核源代碼所在的目錄,而不用每次都把該目錄作為參數傳遞給make命令。對于“hello world!”示例,在 makefile 中指定內核源代碼的方式為上面的開頭的代碼。
在這個makefile文件中,KERNELDIR指定內核的源代碼目錄,該目錄通過當前運行內核使用的模塊目錄中的build符號鏈接指定。
巴拉巴拉這么一大堆理論看著頭疼馬上開始實操。
安裝模塊
將兩個文件放在Ubuntu下,并且命名為mykernel。本機文件結構如下圖
在mykernel文件夾下,直接make編譯,如果沒有make指令的話,可以輸入sudo apt install make安裝make。
輸入
直接make編譯報錯了,看輸出信息
查了資料,是函數參數表沒指明,如果沒有參數表應該寫void而不能為空,用nano打開.c文件,改一下,輸入
sudo nano listprocess.c在exp_init和exp_exit的括號里加上void
然后Ctrl+O寫入,回車確定,然后Ctrl+X退出,然后再輸入sudo make編譯
又報錯了,提示
用nano打開makefile,然后然后刪除最后一句,輸入
sudo nano Makefile
刪除最后一句的obj-m:=listprocess.o
然后Ctrl+O寫入,回車確定,然后Ctrl+X退出,然后再輸入sudo make編譯
沒有報錯了
開始安裝,輸入
注意是.ko文件,然后輸入dmesg查看效果
有了pid,成功了,卸載的話輸入
然后輸入dmesg查看效果
卸載成功,模塊測試成功,=w=
補充:
問題:for_each_process報錯
原因:有些人編譯的時候會有報錯沒找到 for_each_process 函數,可能是對于不同的內核版本,for_each_process 位置不同,可以去查看一下源碼,找一下for_each_process 函數。以本人嘗試結果為例,4.11以后,for_each_process 在 include/linux/sched/signal.h中。
解決方案:在.c文件的頭中加入一行代碼#include <linux/sched/signal.h>就行了
總結
以上是生活随笔為你收集整理的Linux学习之内核模块编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [1] SDK Tools安装
- 下一篇: linux 其他常用命令