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