资讯详情

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

文章目录

      • 目的:内核空间映射到用户空间
      • 环境:Ubuntu 20.04 linux内核源码5.11.0-37-generic(版本自选)
      • 实验结果
      • 实验知识点
      • 实验难点
      • 实验代码
        • nopage.c
        • Makefile
        • np_test.c
      • 调试过程
        • make
        • sudo insmod nopage.ko
        • sudo mknod /dev/nopage c 92 0 (这里的92是你之前申请的设备号)
        • gcc np_test.c -o ntest

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

虚拟字符设备驱动程序将核心空间映射到用户空间

  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

结束

标签: p119举升传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台