Linux-mmap映射物理内存到用户空间

  • Post author:
  • Post category:linux


转自:

https://blog.csdn.net/u014792216/article/details/79020326

在内核申请一片物理内存,映射到用户空间使用的方法。环境:Linux ubuntu 4.10.0-42-generic。


方法经博主测试,测试环境:ubuntu 16.04,内核版本:linux-4.15

一、内核驱动模块

map.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 <asm/uaccess.h>  
#include <asm/io.h>  
#include <asm/page.h>  
#include <linux/mm.h>  
#include <linux/platform_device.h>  
#include <linux/device.h>  
#include <linux/moduleparam.h>  
#include <linux/cdev.h>  
#include <linux/slab.h> 
#include <linux/pci.h>
#include <linux/dma-mapping.h>
#include <linux/dmapool.h>
#include <linux/device.h>

#define MMAPIOMEM_DEV_NAME "mmapiomem"  
#define MMAPIOMEM_DEV_MAJOR 280  
#define MMAP_BUF_SIZE 0x500  

char *mmap_buf_ptr;  
int mmapiomem_open(struct inode *inode,struct file *filp)  
{  
    	return 0;  
}  

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

int mmapiomem_mmap(struct file *filp,struct vm_area_struct *vma)  
{  
	int result;  
	unsigned long page;

	vma->vm_flags |= (VM_IO | VM_LOCKED | (VM_DONTEXPAND | VM_DONTDUMP)); 

	vma->vm_flags|=VM_IO;

	page = virt_to_phys(mmap_buf_ptr); 
    	result = remap_pfn_range(vma, vma->vm_start, ((unsigned int)page)>>PAGE_SHIFT, PAGE_SIZE*4, vma->vm_page_prot);  

    	if(result)
	{  
        	return -EAGAIN;  
    	}  

    	return 0;  
}  

struct file_operations mmapiomem_fops={  

	.owner=THIS_MODULE,  
	.open=mmapiomem_open,  
	.release=mmapiomem_release,  
	.mmap=mmapiomem_mmap,  
};  

struct cdev *mmap_cdev;  
struct class *mmap_class;  

int mmapiomem_init(void)  
{  
	int result;  
	int devno = MKDEV(MMAPIOMEM_DEV_MAJOR,0);  

	mmap_buf_ptr = (char*)__get_free_pages(GFP_KERNEL,2);
	printk("%s,the mmap_buf_ptr is 0x%p\n",__func__,mmap_buf_ptr);  

	memset(mmap_buf_ptr,0,PAGE_SIZE*4);   

	*((unsigned int *)(mmap_buf_ptr)) = 0xc3a21b44;
	*((unsigned int *)(mmap_buf_ptr+0x4)) = 0xab8812df; 

	mmap_cdev = cdev_alloc();  

	result = register_chrdev_region(devno,1,"mmap_char_mem");  

	cdev_init(mmap_cdev,&mmapiomem_fops);  

	mmap_cdev->owner = THIS_MODULE;  

	result = cdev_add(mmap_cdev, devno,1);  

	mmap_class = class_create(THIS_MODULE,"mmap_char_class");  

	if (IS_ERR(mmap_class)) {  
		result= PTR_ERR(mmap_class);  

		return -1;  
	}  
    	device_create(mmap_class, NULL, devno, NULL, MMAPIOMEM_DEV_NAME);  

    	return 0;  
}  

void mmapiomem_exit(void)  
{  

	if (mmap_cdev != NULL)  
	cdev_del(mmap_cdev);  

	device_destroy(mmap_class,MKDEV(MMAPIOMEM_DEV_MAJOR,0));  

	class_destroy(mmap_class);  

	unregister_chrdev_region(MKDEV(MMAPIOMEM_DEV_MAJOR,0),1);  
}  

module_init(mmapiomem_init);  
module_exit(mmapiomem_exit);  

MODULE_LICENSE("Dual BSD/GPL");

Makefile:

obj-m := map.o
all:
	$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
	$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

以上是内核部分,主要是创建一个字符设备,配置其mmap函数,分配连续内存。分配了4pages,并将首地址开始初始化了8bytes。

具体使用方法是:make    insmod map.ko    dmesg-c

最后打印出:

mmapiomem_init,the mmap_buf_ptr is 0xffff880129880000

二、用户态测试

user_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>  
#include <linux/vt.h>  
#include <stdint.h> 
#include <stdlib.h>

#define DEVICE_FILENAME "/dev/mmapiomem"  
#define MMAP_SIZE 0x8000  

int main()  
{  
	int ttydev;  

	int dev,i;  

	uint8_t *ptrdata;  
	dev=open(DEVICE_FILENAME,O_RDWR|O_NDELAY);  
    	if(dev>=0)
	{  
		printf("2)open the dev success\n");  
		ptrdata=(uint8_t*)mmap((void*)0x000000000f000000, MMAP_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, dev, 0); 

        	if(ptrdata!=NULL)
		{  
        		printf("the value of ptrdata0 is 0x%02x\n",*ptrdata);
        		printf("the value of ptrdata1 is 0x%02x\n",*(ptrdata+0x1));
        		printf("the value of ptrdata2 is 0x%02x\n",*(ptrdata+0x2));
			printf("the value of ptrdata3 is 0x%02x\n",*(ptrdata+0x3));
			printf("the value of ptrdata4 is 0x%02x\n",*(ptrdata+0x4));
        		printf("the value of ptrdata5 is 0x%02x\n",*(ptrdata+0x5));
			printf("the value of ptrdata6 is 0x%02x\n",*(ptrdata+0x6));
			printf("the value of ptrdata7 is 0x%02x\n",*(ptrdata+0x7));
			printf("the addr_map is: %p\n",ptrdata);  
        	}  	
    	}  

    	close(dev);  
    	printf("6)here close the dev\n");  

    	return 0;  
}   

在虚拟机上测试结果如下:

可以看到在虚拟机下测试不成功,移步真机得到如下测试结果:

真机测试成功,可以看到,已经映射成功并且读取了我们之前初始化的值,返回了一个用户空间内存地址。因为程序结束后映射会取消掉,所以如果需要使用该地址的话,直接把这个addr_map传给自己的其他接口函数就行。