注意:这些是对设备驱动的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
同样成功。我个人认为第二中写法比较好。
如有错误,请指出,谢谢!!!