花天岑 231830117
实验一:中断处理的内核模块
Makefile
学习了,makefile的基本语法,大致了解了其工作原理和部分基础语法
ifdef KERNELRELEASE
obj-m := tasklet_interrupt.o
else
KERNELDIR ?=/usr/lib/modules/$(shell uname -r)/build
HRHR := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(HRHR) modules
endif
.PHONY:clean
clean:
-rm *.mod.c *.o *.mod *.order *.symvers *.ko
其中ifdef
表示如果定义了…..否则 在这里指:
如果 KERNELRELEASE
被设置(表示在内核源码树中编译模块),则将 tasklet_interrupt.o
添加到 obj-m
变量中。
如果 KERNELRELEASE
未被设置(表示在用户空间编译模块),则设置 KERNELDIR
为当前内核版本的编译目录,并将当前工作目录路径赋值给 HRHR
其中$(shell uname -r)
中$(shell )
表示返回在shell中运行后面那个命令得到的结果,而uname -r
返回linux内核版本的release版本
:=
表示把右边的赋值给左边
对于这个我觉得很奇怪为什么不用=
,于是我STFW了一下,得知了两者的区别
:=
立即赋值,适用于需要在 Makefile 被解析时就确定值的变量=
延迟赋值,适用于需要在构建过程中动态计算值的变量
pwd
是用于返回当前工作目录的命令(printing working directory),在这个语境里,我需要的pwd
是一个固定的目录,所以用:=
default
表示make在没有指定目标是默认的目标,我在终端输入make
时,会自动执行这个
$(make)
表示调用make命令本身 -C
用于指定make的工作目录 M
用于指定模块的构建目录 modules
则是一个make
命令的一个目标,用于告诉make
构建内核模块
.PHONY
用于声明一个伪目标clean
-rm
是shell中用于删除文件的命令,*
是通配符,表示匹配所有符合模式的文件
tasklet_interrupt.c
#include <linux/module.h>
#include <linux/interrupt.h>
MODULE_LICENSE("GPL");//开源许可证
static struct tasklet_struct my_tasklet;
//任务调动处理函数
static void tasklet_handler(unsigned long data){
printk("tasklet is working...\n");
}
//初始化模块
static int __init mytasklet_init(void){
printk("Start tasklet module...\n");
tasklet_init(&my_tasklet, tasklet_handler, 0);
tasklet_schedule(&my_tasklet);
return 0;
}
//退出模块
static void __exit mytasklet_exit(void){
tasklet_kill(&my_tasklet);
printk("Exit tasklet module...\n");
}
// 这个宏用来注册模块的初始化函数。当模块被加载时,内核会调用 mytasklet_init函数
module_init(mytasklet_init);
// 这个宏用来注册模块的清理函数。当模块被卸载时,内核会调用 mytasklet_exit函数
module_exit(mytasklet_exit);
这段代码的目的是完成模块的初始化和退出的函数,而初始化这个模块时需要用到tasklet
的初始化和调度,具体内容在注释中标出,对于其中调用的一些函数,手册中也写的很清楚
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long),
unsigned long data);
/* 初始化tasklet,func指向要执行的函数,
data为传递给函数func的参数 */
tasklet_schedule(&my_tasklet) /*调度执行指定的tasklet*/
void tasklet_kill(struct tasklet_struct *t) /*移除指定tasklet*/
void tasklet_disable(struct tasklet_struct *t) /*禁用指定tasklet*/
void tasklet_enable(struct tasklet_struct *t) /*启用先前被禁用的tasklet*/
printk
是表示将内容输出到log中,然后我们通过检查log来确定是否完成了模块的加载和退出
insmod
和rmmod
分别是把模块装载、移除
dmesg | tail -n 3
倒是让我了解到了|
管道的用法,dmesg
返回内核缓冲区的信息,通过管道将这个输出作为后面的输入,从而获得后三行的信息
实验二:信息获取、设置、线程操作
Makefile
ifdef KERNELRELEASE
obj-m := process_info.o
else
KERNELDIR ?=/usr/lib/modules/$(shell uname -r)/build
WD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(WD) modules
endif
.PHONY:clean
clean:
-rm *.mod.c *.o *.mod *.order *.symvers *.ko
这个makefile和上面的那个没什么区别,就不细说了
process_info.c
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
MODULE_LICENSE("GPL");
struct task_struct *p;
static int __init process_info_init(void) {
printk("Start process_info...\n");
for_each_process(p) {
//_state = 0表示进程正在运行
if (p->__state == 0) {
printk("1)name:%s\n 2)pid:%d\n 3)state:%d\n", p->comm, p->pid, p->__state);
}
}
return 0;
}
static void __exit process_info_exit(void) {
printk("Exit process_info...\n");
}
module_init(process_info_init);
module_exit(process_info_exit);
这部分实验其实是打印正在运行的进程的name、pid、state
值得注意的是,我在实际操作中选择使用dmesg
来获取所有log信息,因为我不能确定有几个进程正在运行,手册中只打印后三行可能病不能打印出我所想看到的全部信息
实验三:程序运行时间
Makefile
ifdef KERNELRELEASE
obj-m := sum_time.o
else
KERNELDIR ?=/usr/lib/modules/$(shell uname -r)/build
WD := $(shell pwd)
endif
default:
$(MAKE) -C $(KERNELDIR) M=$(WD) modules
.PHONY:clean
clean:
-rm *.mod.c *.o *.mod *.order *.symvers *.ko
这个和上面那个区别不大,不多赘述
sum_time.c
#include <linux/kernel.h>
#include <linux/ktime.h>
#include <linux/module.h>
#include <linux/timekeeping.h>
MODULE_LICENSE("GPL");
#define NUM 100000
//求和函数
static long mysum(int num) {
long total = 0;
for (int i = 1; i <= num; i++){
total += i;
}
printk("The sum of 1 to %d is: %ld\n", num, total);
return total;
}
static int __init sum_init(void) {
ktime_t start, end;
u64 timecost;
printk("Start sum_time module...\n");
start = ktime_get();
mysum(NUM);
end = ktime_get();
timecost = ktime_to_ns(ktime_sub(end, start)); //ktime_sub计算时间差 ktime_to_ns将时间转换为纳秒
printk("The cost time of sum from 1 to %d is: %llu ns\n", NUM, timecost);
return 0;
}
static void __exit sum_exit(void) {
printk("Exit sum_time module...\n");
}
module_init(sum_init);
module_exit(sum_exit);
这个实验是计算sum所用的时间 其实就是通过两次调用ktime_get
来获得sum前后的时间,再通过一系列函数来进行计算和转化
其余关于模块的事情和上面的实验是一样的