[linux-nopage]内存映射虚拟字符设备驱动【P119】

目的:内核空间映射到用户空间

一个虚拟字符设备驱动程序,将内核空间映射到用户空间

  1. 找到内核地址对应的物理地址
  2. 建立新的用户表项

环境:Ubuntu 20.04 linux内核源码5.11.0-37-generic(版本自选)

在这里插入图片描述

实验结果

  1. 这是最终的实验结果图,中间出现了很多错误结果【错误过程在错误分析】
    在这里插入图片描述

实验知识点

  1. 加载内核模块是什么意思

将自己编写的驱动程序加载到内核当中,linux强大在一切皆文件,实现高类聚低耦合的特点,模块封装
给linux无限可能的机会

  1. 如何加载

实验难点

  1. 没接触过linux 编译,前期知识点
    Makefile 文件的作用

    1. 类似脚本,将编译的大部分内容写在Makefile文件内(该文件要和编译的.c文件在同一个目录下)
      刚开是只有
      在这里插入图片描述
      这三个文件,其他文件大部分是经过make 编译产生的
      这图来自教程
      这是教程的一张图,解释大部分内容对应我下面的Makefile 文件内容

    make 指令的作用

    1. make 会进入Makefile 文件;根据Makefile 进行编译
      根据Makefile文件编译源代码、连接、生成目标文件、可执行文件。 简单理解就是执行Makefile这个脚本;
  • 向内核添加模块:
  1. 编写驱动程序文件

  2. 将驱动文件放置到linux 内核源码相应的目录下

  3. 在目录Kconfig 文件中添加新驱动程序对应的项目编译选择

  4. 在目录Makefile文件中添加新的驱动程序编译语句

  5. 怎么编译进内核

根据Makefile 指定的内核地址进入到内核中;
在这里插入图片描述
这是我调试错误过程的一种图,看见执行make先进入指定的内核地址,内核内也有Makefile文件;
在这里插入图片描述定位到140行:在这里插入图片描述
中间花费了一点时间在这里调试,后期发现主要bug不在这里;

主要bug 还是因为没有安装gcc
这里花费时间是因为一开始我检测系统发现已经有gcc, 后来在编译文件才发现系统的gcc不能用,前两个是系统自带的。才导致我花了大量时间
最后一个是我后来安装上的 sudo apt install gcc
在这里插入图片描述

  1. 遇到错误怎么排错

google baidu 教材

  1. 课本的指令是什么意思

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) //这个函数跟之前版本的函数不同,这个形参只有一个,这个要在mm.h下查看才能确定不同版本不同
{
		struct page *page;
		unsigned long offset;
		void *page_ptr;
 		struct vm_area_struct *vma=vmf->vma; //早期版本该变量时形参,现在已经在vm_fault 结构体内定义直接使用
		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); // 虚拟存储区中断地址变量为address 更之前也有差别,在mm.h 下可以找到对应 变量为long unsigned int 跟之前的有区别
		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);
		/*printk("%-25s %d\n","7)PAGE_SHIFT",PAGE_SHIFT);*/
 
		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) //执行insmod 时进入这个函数
{
		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) //执行rmmod 时进入该函数
{
		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) //pwd 表示当前工作目录 present work direction
KERSRC := /lib/modules/$(shell uname -r)/build/ //unama -r 指向当前的内核版本

modules:
	$(MAKE) -C $(KERSRC) M=$(PWD) modules // 执行内核模块的编译
moules_install:
	$(MAKE) -C $(KERSRC) M=$(PWD) modules_install // 将模块安装到对应的模块路径只有modules_install 执行才触发
.PHONY: modules modules_install clean //wei
clean:
	-rm -rf *.o *.cmd.* *.ko //删除中间标识文件
else
modules-objs :=nopage.o 
obj-m := nopage.o //将nopage.o 编译为nopage.ko

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/mmapnopage"
#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); //设备打开成功,返回句柄大于0
		if(dev < 0)printf("can't open nopage\n");		 // return < 0 fail!				    
		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;
}

调试过程

实验出现的错误:

  1. Makefile 文件
    Makefile missing separator. Stop.

这是由于Makefile 文件中的空格键个数和tab键导致的
No rule to make target ‘make’, needed by ‘modules’
解决方法:文本打开直接使用tab 键,不要用空格代替,Makefile以空格为命令行的分界,对符号敏感

在这里插入图片描述

进入Makefile文件

在这里插入图片描述
没有进入ifeq条件
猜想应该是.config 文件这个变量设置问题
找到.config 发现没有找到该变量

.config 在当前源码里面下
在这里插入图片描述针对这个问题查看资料源码花费了很多时间还是没能解决
就进入下一个问题

  1. 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

结束
在这里插入图片描述

posted @ 2022-05-02 19:40  jucw  阅读(84)  评论(0编辑  收藏  举报