对mmap的一些测试-linux设备驱动开发

注意:这些是对设备驱动的mmap测试,对于文件映射mmap的实现有所不同

首先用户空间的申明为void *mmap(void *addr,size_t length,int prot, int flags, int fd, off_t offset).

内核驱动中mmap(struct file *filp,struct vm_area_struct *vma)

我们跟踪系统调用do_mmap(unsigned long addr, unsigned long len, unsigned long prot, unsigned long flags, unsigned long fd, off_t off)

->do_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT)

->mmap_region(struct file *file, unsigned long addr,unsigned long len, unsigned long flags,vm_flags_t vm_flags, unsigned long pgoff)

在mmap_region有如下代码片段vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL)我们分配了一个struct vm_area_struct

接下来还可以看到

    vma->vm_mm = mm;
    vma->vm_start = addr;
    vma->vm_end = addr + len;
    vma->vm_flags = vm_flags;
    vma->vm_page_prot = vm_get_page_prot(vm_flags);
    vma->vm_pgoff = pgoff;

而pgoff=offset>>PAGE_SHIFT,从这里可以看到内核已经为我们分配了一个线性区.

接下来有一条error = file->f_op->mmap(file, vma)语句.这里开始调用我们驱动的mmap函数。而在用户空间调用的时候,我们要传入要映射物理地址作为参数,例如:我们要映射桢缓冲区,这传入的参数为设备内存的地址。从上面的赋值语句可知,虚拟线性區已与物理地址建立了联系,然而映射关系没有建立。那么在我们自己的驱动中就要建立相应的页表。驱动示例如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/gfp.h>
#include <linux/string.h>
#include <linux/mm_types.h>
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/kernel.h>
#define KSTR_DEF	"Hello world from kernel virtual space"

static struct cdev *pcdev;
static dev_t ndev;
static struct page *pg;
static struct timer_list timer;
static struct class *fa_cls;
static struct device *fadev;
static char *kstr;
static void timer_func(unsigned long data)
{
	printk("timer_func:%s\n",(char *)data);
	timer.expires = jiffies + 2 * HZ;
	add_timer(&timer);
}

static int demo_open(struct inode *inode,struct file *filp)
{
	return 0;
}

static int demo_release(struct inode *inode,struct file *filp)
{
	return 0;
}

static int demo_mmap(struct file *filp,struct vm_area_struct *vma)
{
	int err = 0;
	unsigned long start = vma->vm_start;
	unsigned long size = vma->vm_end - vma->vm_start;

	err = remap_pfn_range(vma,start,vma->vm_pgoff,size,vma->vm_page_prot);
	
	//err = remap_pfn_range(vma,start,page_to_phys(pg)>>PAGE_SHIFT,size,vma->vm_page_prot);
	return err;
	
}

static struct file_operations mmap_fops=
{
	.owner		= THIS_MODULE,
	.open		= demo_open,
	.release	= demo_release,
	.mmap		= demo_mmap,
};

static int map_init(void)
{
	int err = 0;
	
	pg = alloc_pages(GFP_HIGHUSER,0);
	SetPageReserved(pg);
	kstr = (char *)kmap(pg);
	strcpy(kstr,KSTR_DEF);
	printk("_________________kernel string = %s\n",kstr);
	printk("kpa = 0x%X,kernel string = %s\n",page_to_phys(pg),kstr);

	pcdev = cdev_alloc();
	cdev_init(pcdev,&mmap_fops);
	alloc_chrdev_region(&ndev,0,1,"mmap_dev");
	printk("major=%d,minor=%d\n",MAJOR(ndev),MINOR(ndev));
	pcdev->owner = THIS_MODULE;
	cdev_add(pcdev,ndev,1);
			
	fa_cls = class_create(THIS_MODULE,"fa_dev");
	if(IS_ERR(fa_cls))
		return PTR_ERR(fa_cls);
	fadev = device_create(fa_cls,NULL,ndev,NULL,"fa_dev");
	if(IS_ERR(fadev))
		return PTR_ERR(fadev);
	//err = device_create_file(fadev,NULL);

	init_timer(&timer);
	timer.function = timer_func;
	timer.data = (unsigned long)kstr;
	
	timer.expires = jiffies + HZ*10;
	add_timer(&timer);
	return err;
}

static void map_exit(void)
{
	//device_remove_file(fadev,NULL);
	device_destroy(fa_cls,ndev);
	class_destroy(fa_cls);

	del_timer_sync(&timer);
	cdev_del(pcdev);
	unregister_chrdev_region(ndev,1);
	kunmap(pg);
	ClearPageReserved(pg);
	__free_pages(pg,0);
	
}

module_init(map_init);
module_exit(map_exit);
MODULE_LICENSE("GPL");
在map_init函数中我们申请了一页空间作为我们要映射的物理地址,然后为这一页建立映射关系.注意:此时并不是真正与用户空间建立关系,只是为了方便写入及读出测试建立映射。然后创建了一个定时器,这个定时器每隔2*HZ输出这一页的值。而在我们demo_mmap中真正建立了物理内存与虚拟地址的映射关系,即页表。

把这个模块添加到内核中输出为:

root@ubuntu:/mnt/hgfs/workspace/mmap# insmod mmap.ko
root@ubuntu:/mnt/hgfs/workspace/mmap# dmesg
[ 4545.426479] _________________kernel string = Hello world from kernel virtual space
[ 4545.426488] kpa = 0x2E490000,kernel string = (null)
[ 4545.426494] major=250,minor=0

我们申请的物理地址是0x2E490000,即为我们要映射的物理地址。

测试程序为:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

#define MAP_SIZE	4096
#define USTR_DEF	"string chaned from the user Space"
#define PATH	"/dev/fa_dev"
int main(int argc,char *argv[])
{
	int fd;
	char *pdata;
	fd = open(PATH,O_RDWR | O_NDELAY);
	if(fd >= 0)
		{
			pdata = (char *)mmap(0,MAP_SIZE,PROT_READ | PROT_WRITE,
				MAP_SHARED,fd,strtoul(argv[1],0,16));
			printf("userAddr=%p,Data from kernel:%s\n",pdata,pdata);
			printf("Writing a string to the kernel space....");
			strcpy(pdata,USTR_DEF);
			printf("Done\n");
			munmap(pdata,MAP_SIZE);
			close(fd);
		}
	return 0;
}

root@ubuntu:/mnt/hgfs/workspace/mmap# ./a.out 0x2e490000
userAddr=0xb77b7000,Data from kernel:Hello world from kernel virtual space
Writing a string to the kernel space....Done
结果为

[ 4863.882052] timer_func:Hello world from kernel virtual space
[ 4865.884939] timer_func:Hello world from kernel virtual space
[ 4867.887972] timer_func:Hello world from kernel virtual space
[ 4869.890770] timer_func:Hello world from kernel virtual space
[ 4871.893965] timer_func:Hello world from kernel virtual space
[ 4873.896406] timer_func:Hello world from kernel virtual space
[ 4875.899317] timer_func:Hello world from kernel virtual space
[ 4877.902221] timer_func:string chaned from the user Space
[ 4879.905039] timer_func:string chaned from the user Space
[ 4881.908230] timer_func:string chaned from the user Space
[ 4883.911033] timer_func:string chaned from the user Space
[ 4885.913842] timer_func:string chaned from the user Space
[ 4887.916654] timer_func:string chaned from the user Space

由结果可知,映射已成功。

在demo_mmap中我们将

err = remap_pfn_range(vma,start,vma->vm_pgoff,size,vma->vm_page_prot);
替换为 err = remap_pfn_range(vma,start,page_to_phys(pg)>>PAGE_SHIFT,size,vma->vm_page_prot);
既要映射的地址写死,测试

root@ubuntu:/mnt/hgfs/workspace/mmap# ./a.out 0
userAddr=0xb77bb000,Data from kernel:Hello world from kernel virtual space
Writing a string to the kernel space....Done
root@ubuntu:/mnt/hgfs/workspace/mmap# dmesg -c
[ 5185.802305] _________________kernel string = Hello world from kernel virtual space
[ 5185.802313] kpa = 0x33658000,kernel string = (null)
[ 5185.802318] major=250,minor=0
[ 5195.819679] timer_func:Hello world from kernel virtual space
[ 5197.822186] timer_func:Hello world from kernel virtual space
[ 5199.825049] timer_func:Hello world from kernel virtual space
[ 5201.827891] timer_func:Hello world from kernel virtual space
[ 5203.831034] timer_func:Hello world from kernel virtual space
[ 5205.833989] timer_func:Hello world from kernel virtual space
[ 5207.836946] timer_func:Hello world from kernel virtual space
[ 5209.839672] timer_func:Hello world from kernel virtual space
[ 5211.842431] timer_func:Hello world from kernel virtual space
[ 5213.845351] timer_func:string chaned from the user Space
[ 5215.848172] timer_func:string chaned from the user Space
[ 5217.851562] timer_func:string chaned from the user Space
同样成功。我个人认为第二中写法比较好。
如有错误,请指出,谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值