转自:
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传给自己的其他接口函数就行。