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

Posted Cop & Bridegroom

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[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

结束

以上是关于[linux-nopage]内存映射虚拟字符设备驱动P119的主要内容,如果未能解决你的问题,请参考以下文章

Linux 内核 内存管理内存映射原理 ① ( 物理地址空间 | 外围设备寄存器 | 外围设备寄存器的物理地址 映射到 虚拟地址空间 )

技巧.在虚拟机Vmware中使用HID设备(如USB免驱键盘)

linux内核源码分析之虚拟内存映射

是否可以将套接字映射到虚拟内存?

内存映射(Linux设备驱动程序)

[QNX Hypervisor 2.2用户手册]6.3.3 使用共享内存(shmem)虚拟设备