花天岑 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来确定是否完成了模块的加载和退出

insmodrmmod分别是把模块装载、移除

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前后的时间,再通过一系列函数来进行计算和转化

其余关于模块的事情和上面的实验是一样的