文章目录
一、实验目的
掌握字符设备驱动程序中利用nopage进行内存映射的方法。
掌握利用get_free_pages进行连续物理地址空间申请的方法。
二、实验环境
ubuntu 12.04 内核3.2.14
三、实验内容及实验原理
写一个简单的驱动程序,要求:
在加载驱动程序时利用get_free_pages函数申请一片64KB的连续物理地址空间;
利用vma fault机制实现对申请到的64KB地址空间进行内存映射;
编写应用程序利用mmap进行内存映射,读写映射内存区域,通过打印输出观察具体每个页面实际进行内存映射的时机;
在卸载驱动程序时利用free_pages释放申请到的64KB空间
四、实验结果及其分析
1.编译模块(设备驱动程序)
(1)创建模块文件xxx.c
gedit rwbuf.c
// 模块
#include <linux/module.h>
// 内核
#include <linux/kernel.h>
// struct file_operations
#include <linux/fs.h>
// kmalloc和kfree
#include <linux/slab.h>
// 内存映射
#include <linux/mm.h>
#include <asm/page.h>
#include <linux/gfp.h>
#include <linux/vmalloc.h>
#include <linux/fcntl.h>
#include <linux/unistd.h>
#define DEVICE_CNT 1
#define DEVICE_NAME "rwbuf"
// 全局地址
static unsigned long addr;
void vma_open(struct vm_area_struct *vma)
{
printk("[vma_open-success] virt = %lx, phys = %lx\n", vma->vm_start, vma->vm_pgoff<<PAGE_SHIFT);
}
void vma_close(struct vm_area_struct *vma)
{
printk("[vma_close-success]\n");
}
struct page *vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
struct page *pageptr;
unsigned long baseaddr = addr;
unsigned long virtaddr = vmf->virtual_address - vma->vm_start + baseaddr;
pageptr = virt_to_page(virtaddr);
if(!pageptr)
{
return VM_FAULT_SIGBUS;
}
get_page(pageptr);
vmf->page=pageptr;
printk("[vma_fault-success]\n");
return pageptr;
}
static struct vm_operations_struct fault_vm_ops={
.open = vma_open,
.close = vma_close,
.fault = vma_fault,
};
static int fault_mmap(struct file* filp, struct vm_area_struct *vma){
unsigned long offset = vma->vm_pgoff<<PAGE_SHIFT;
if(offset>=__pa(high_memory)||(filp->f_flags & O_SYNC))
vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED;
vma->vm_ops = &fault_vm_ops;
vma_open(vma);
printk("[fault_mmap-success]\n");
return 0;
}
// 设备的读操作
static int rwbuf_open(struct inode *inode, struct file *filp)
{
printk("[rwbuf_open-success]\n");
return 0;
}
// 设备的关闭
static int rwbuf_release(struct inode *inode, struct file *filp)
{
printk("[rwbuf_release-success]\n");
return 0;
}
// rwbuf_fops要在rwf_buf_init()前面声明,因为register_chrdev()函数要使用到
struct file_operations rwbuf_fops={
open:rwbuf_open,
release:rwbuf_release,
mmap:fault_mmap,
};
// module_init()内的初始化函数:返回-1表示错误;返回0表示成功
static int __init rwbuf_init(void)
{
// 表示注册成功与否:-1表示失败,0表示成功(同register_chrdev返回值)。初始化为-1
int ret = -1;
/*
参数1:设备的种类,即主设备号
参数2:设备的名称
参数3:和VFS对接的接口,即上面的结构体变量
*/
ret = register_chrdev(60, DEVICE_NAME, &rwbuf_fops);
// 注册失败
if (ret == -1)
{
printk("[rwbuf_init-register-failed]\n");
}
// 注册成功
else
{
printk("[rwbuf_init-register-success]\n");
int order = get_order(64*1024);
printk("[rwbuf_init-order] %d\n",order);
addr = __get_free_pages(GFP_KERNEL,order);
printk("[rwbuf_init-get_free_pages-addr] %lu\n",addr);
}
// 返回ret(同register_chrdev返回值)
return ret;
}
// module_exit()内的退出函数。
static int __exit rwbuf_exit(void)
{
int order = get_order(64*1024);
if(addr){
free_pages(addr,order);
printk("[rwbuf_exit-free_pages-success]\n");
}else{
printk("[rwbuf_exit-free_pages-error]\n");
}
unregister_chrdev(60, DEVICE_NAME);
printk("[rwbuf_exit-success]\n");
return 0;
}
// 内核模块入口,相当于main()函数,完成模块初始化
module_init(rwbuf_init);
// 卸载时调用的函数入口,完成模块卸载
module_exit(rwbuf_exit);
// GPL协议证书
MODULE_LICENSE("GPL");
(2)Makefile
gedit Makefile
内容:
obj-m := rwbuf.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers
(3)编译
sudo make
2.创建设备文件(设备进入点)
(1)创建
sudo mknod /dev/rwbuf c 60 0
意思:
在/dev目录下创建一个名为rwbuf的设备文件
设备文件的类型是c(字符型设备)
主设备号是60
次设备号是0
(2)赋权
sudo chmod 777 /dev/rwbuf
更改设备的权限。没有读写权限的话,就会读出一堆无意义的乱码。
3.插入内核模块(加载设备驱动程序)
先清理一下缓存,不然一会就可能输出一大堆多余东西,影响到我们想要看到的输出东西
sudo dmesg -c
插入内核模块(加载设备驱动程序)
sudo insmod rwbuf.ko N=1000
查看是否成功
dmesg
4.测试用户应用程序(调用驱动程序)
(1)读写
gedit test.c
#include <stdio.h>
#include <stdlib.h>
// 为了open()中的O_RDWR
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <string.h>
// 定义设备进入点(设备名)
#define DEVICE_NAME "/dev/rwbuf"
// 定义大小
#define SHARE_MEM_SIZE 4096
#define SHARE_MEM_PAGE_COUNT 16
int main()
{
int fd;
char *ptrdata;
fd=open(DEVICE_NAME,O_RDWR|O_NDELAY);
if (fd == -1)
{
printf("[open-error]\n");
return 0;
}
else
{
printf("[open-success]\n");
}
ptrdata=(char*)mmap(0, SHARE_MEM_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
int loop;
for(loop=0;loop<SHARE_MEM_PAGE_COUNT;loop++)
{
printf("[%-10s----]\n",ptrdata+4096*loop);
}
munmap(ptrdata,SHARE_MEM_SIZE);
close(fd);
return 0;
}
rmmod rwbuf
dmesg
五、心得体会与建议
感觉最后应用程序不太会。