目的:内核空间映射到用户空间
一个虚拟字符设备驱动程序,将内核空间映射到用户空间
- 找到内核地址对应的物理地址
- 建立新的用户表项
环境:Ubuntu 20.04 linux内核源码5.11.0-37-generic(版本自选)
实验结果
- 这是最终的实验结果图,中间出现了很多错误结果【错误过程在错误分析】
实验知识点
- 加载内核模块是什么意思
将自己编写的驱动程序加载到内核当中,linux强大在一切皆文件,实现高类聚低耦合的特点,模块封装 给linux无限可能的机会
- 如何加载
实验难点
-
没接触过linux 编译,前期知识点 Makefile 文件的作用
- 类似脚本,将编译的大部分内容写在Makefile文件内(该文件要和编译的.c文件在同一个目录下)
刚开是只有 这三个文件,其他文件大部分是经过make 编译产生的 这是教程的一张图,解释大部分内容对应我下面的Makefile 文件内容
make 指令的作用
- make 会进入Makefile 文件;根据Makefile 进行编译
根据Makefile文件编译源代码、连接、生成目标文件、可执行文件。 简单理解就是执行Makefile这个脚本;
-
编写驱动程序文件 -
将驱动文件放置到linux 内核源码相应的目录下 -
在目录Kconfig 文件中添加新驱动程序对应的项目编译选择 -
在目录Makefile文件中添加新的驱动程序编译语句 -
怎么编译进内核
根据Makefile 指定的内核地址进入到内核中; 这是我调试错误过程的一种图,看见执行make先进入指定的内核地址,内核内也有Makefile文件; 定位到140行: 中间花费了一点时间在这里调试,后期发现主要bug不在这里;
主要bug 还是因为没有安装gcc 这里花费时间是因为一开始我检测系统发现已经有gcc, 后来在编译文件才发现系统的gcc不能用,前两个是系统自带的。才导致我花了大量时间 最后一个是我后来安装上的 sudo apt install gcc
- 遇到错误怎么排错
google baidu 教材
- 课本的指令是什么意思
make # 编译 insmod xxx.ko # 根据编译结果会产生.ko文件,此时会执行module_init(xxx)函数 通过命令 dmesg | tail 最后几行可以发现 经过insmod 之后进入init函数 前两句是插入的模块未在模块树内,也就是外来模块会提示这一消息,学习过程可以忽略;
实验代码
nopage.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/vmalloc.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <asm/page.h>
#include <linux/mm.h>
#define MMAPNOPAGE_DEV_NAME "nopage"
#define MMAPNOPAGE_DEV_MAJOR 92
#define SHARE_MEM_PAGE_COUNT 4
#define SHARE_MEM_SIZE (PAGE_SIZE*SHARE_MEM_PAGE_COUNT)
char *share_memory=NULL;
vm_fault_t mmapnopage_vm_fault(struct vm_fault *vmf)
{
struct page *page;
unsigned long offset;
void *page_ptr;
struct vm_area_struct *vma=vmf->vma;
printk("\n");
printk("%-25s %08x\n","1)vma->flags",vmf->flags);
printk("%-25s %08lx\n","2)vmf->pgoff",vmf->pgoff);
printk("%-25s %08lx\n","3)vmf->virtual_address",vmf->address);
printk("%-25s %08lx\n","4)vma->vm_start",vma->vm_start);
printk("%-25s %08lx\n","5)vma->vm_end",vma->vm_end);
printk("%-25s %08lx\n","6)vma->vm_pgoff",vma->vm_pgoff);
page_ptr=NULL;
if((NULL==vma)||(NULL==share_memory)){
printk("return VM_FAULT_SIGBUS!\n");
return VM_FAULT_SIGBUS;
}
offset=vmf->address-vma->vm_start;
if(offset>=SHARE_MEM_SIZE){
printk("return VM_FAULT_SIGBUS!");
return VM_FAULT_SIGBUS;
}
page_ptr=share_memory+offset;
page=vmalloc_to_page(page_ptr);
get_page(page);
vmf->page=page;
return 0;
}
struct vm_operations_struct mmapnopage_vm_ops={
.fault=mmapnopage_vm_fault,
};
int mmapnopage_mmap(struct file *filp,struct vm_area_struct *vma)
{
vma->vm_flags |= VM_NORESERVE;
vma->vm_ops=&mmapnopage_vm_ops;
return 0;
}
struct file_operations mmapnopage_fops={
.owner=THIS_MODULE,
.mmap=mmapnopage_mmap,
};
int mmapnopage_init(void)
{
int lp;
int result;
result=register_chrdev(MMAPNOPAGE_DEV_MAJOR,
MMAPNOPAGE_DEV_NAME,
&mmapnopage_fops);
if(result<0){
printk("regist fails!");
return result;
}
share_memory=vmalloc(SHARE_MEM_SIZE);
for(lp=0;lp<SHARE_MEM_PAGE_COUNT;lp++){
sprintf(share_memory+PAGE_SIZE*lp,"TEST %d",lp);
}
printk("registing...!");
return 0;
}
void mmapnopage_exit(void)
{
if(share_memory!=NULL){
vfree(share_memory);
}
unregister_chrdev(MMAPNOPAGE_DEV_MAJOR,
MMAPNOPAGE_DEV_NAME);
}
module_init(mmapnopage_init);
module_exit(mmapnopage_exit);
MODULE_LICENSE("Dual BSD/GPL");
Makefile
下面的Makefile 文件我添加了注释可能在命令后面多了空格,make会识别错误
ifeq ($(KERNELRELEASE),)
CONFIG_MODULE_SIG=n
PWD :=$(shell pwd)
KERSRC := /lib/modules/$(shell uname -r)/build/
modules:
$(MAKE) -C $(KERSRC) M=$(PWD) modules
moules_install:
$(MAKE) -C $(KERSRC) M=$(PWD) modules_install
.PHONY: modules modules_install clean
clean:
-rm -rf *.o *.cmd.* *.ko
else
modules-objs :=nopage.o
obj-m := nopage.o
endif
这个Makefile 和 上面是一样的;
ifeq ($(KERNELRELEASE),)
CONFIG_MODULE_SIG=n
PWD :=$(shell pwd)
KERSRC := /lib/modules/$(shell uname -r)/build/
modules:
$(MAKE) -C $(KERSRC) M=$(PWD) modules
moules_install:
$(MAKE) -C $(KERSRC) M=$(PWD) modules_install
.PHONY: modules modules_install clean
clean:
-rm -rf *.o *.cmd.* *.ko
else
modules-objs :=mmapnopage.o
obj-m := mmapnopage.o
endif
np_test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#define DEVICE_FILENAME "/dev/nopage"
#define SHARE_MEM_PAGE_COUNT 4
#define SHARE_MEM_SIZE (4096*SHARE_MEM_PAGE_COUNT)
int main()
{
int dev;
int loop;
char *ptrdata;
dev=open(DEVICE_FILENAME,O_RDWR|O_NDELAY);
if(dev < 0)printf("can't open nopage\n");
if(dev>=0){
printf("open file success!\n");
ptrdata=(char*)mmap(0,
SHARE_MEM_SIZE,
PROT_READ|PROT_WRITE,
MAP_SHARED,
dev,
0);
for(loop=0;loop<SHARE_MEM_PAGE_COUNT;loop++){
printf("[%-10s----%s]\n",ptrdata+4096*loop,ptrdata+4096*loop);
}
munmap(ptrdata,SHARE_MEM_SIZE);
close(dev);
}
return 0;
}
调试过程
实验出现的错误:
- Makefile 文件
Makefile missing separator. Stop.
这是由于Makefile 文件中的空格键个数和tab键导致的 No rule to make target ‘make’, needed by ‘modules’ 解决方法:文本打开直接使用tab 键,不要用空格代替,Makefile以空格为命令行的分界,对符号敏感
进入Makefile文件
没有进入ifeq条件 猜想应该是.config 文件这个变量设置问题 找到.config 发现没有找到该变量
.config 在当前源码里面下 针对这个问题查看资料源码花费了很多时间还是没能解决 就进入下一个问题
- gcc 没有发现,首先查找有没有安装
发现有安装(最后一个是最后安装上的)(其实主要原因还是没有安装gcc)系统自带的不能用;
后面尝试编译一个文件gcc hello.c -o test # 这个是我用来测试用的 发现这个时候找不到gcc 于是
sudo apt-get install gcc 产生这么多错误的原因是没有 sudo apt install gcc; 在排除上面这个问题的过程中花了很多时间 现在终于进入到错误里面
上面是环境问题还没进入到代码里;
接下来调试才真正进入调试阶段;
进入到头文件
这是mm.h文件下
两个错误修改地址后 note: each undeclared identifier is reported only once for each function it appears in
71行20列定位;
make
Make 编译成功:
sudo insmod nopage.ko
insmod: ERROR: could not insert module mmapnopage.ko: Device or resource busy 这是因为申请的设备号被占用了 查看当前字符设备号使用情况
cat /proc/devices 查看设备号使用情况 将240改为 90
重新执行命令
sudo mknod /dev/nopage c 92 0 (这里的92是你前面申请的设备号)
92 可以通过命令 grep nopage /proc/devices 返回得到 参数c 代表的是字符设
gcc np_test.c -o ntest
./ntest 出现错误
np_test.c源码 查看是否加载模块成功 黄色代表字符设备
后来想到是不是权限问题 我先查看了一下权限 在/dev 下ll 先看mmapnopage字符设备对于普通用户只有读权限(nopage 是我修改过的,原本和mmapnopage一样) 修改为可读可写 chmod 666 nopage #(666 = rw-=4+2+0)
之后再编译一下gcc np_test.c -o ntest ./ntest
结束
|