[linux-nopage]内存映射虚拟字符设备驱动P119
Posted Cop & Bridegroom
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[linux-nopage]内存映射虚拟字符设备驱动P119相关的知识,希望对你有一定的参考价值。
文章目录
目的:内核空间映射到用户空间
一个虚拟字符设备驱动程序,将内核空间映射到用户空间
- 找到内核地址对应的物理地址
- 建立新的用户表项
环境:Ubuntu 20.04 linux内核源码5.11.0-37-generic(版本自选)
实验结果
- 这是最终的实验结果图,中间出现了很多错误结果【错误过程在错误分析】
实验知识点
- 加载内核模块是什么意思
将自己编写的驱动程序加载到内核当中,linux强大在一切皆文件,实现高类聚低耦合的特点,模块封装
给linux无限可能的机会
- 如何加载
实验难点
-
没接触过linux 编译,前期知识点
Makefile 文件的作用- 类似脚本,将编译的大部分内容写在Makefile文件内(该文件要和编译的.c文件在同一个目录下)
刚开是只有
这三个文件,其他文件大部分是经过make 编译产生的
这是教程的一张图,解释大部分内容对应我下面的Makefile 文件内容
make 指令的作用
- make 会进入Makefile 文件;根据Makefile 进行编译
根据Makefile文件编译源代码、连接、生成目标文件、可执行文件。 简单理解就是执行Makefile这个脚本;
- 类似脚本,将编译的大部分内容写在Makefile文件内(该文件要和编译的.c文件在同一个目录下)
- 向内核添加模块:
-
编写驱动程序文件
-
将驱动文件放置到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) //这个函数跟之前版本的函数不同,这个形参只有一个,这个要在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;
}
调试过程
实验出现的错误:
- 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
结束
以上是关于[linux-nopage]内存映射虚拟字符设备驱动P119的主要内容,如果未能解决你的问题,请参考以下文章
Linux 内核 内存管理内存映射原理 ① ( 物理地址空间 | 外围设备寄存器 | 外围设备寄存器的物理地址 映射到 虚拟地址空间 )