为何要将内核空间的显存映射到用户空间
有些驱动在使用时须要频繁的操作内核空间的某一片显存(如显示屏驱动,须要频繁的读写内存),若采用传统的read和write会存在大量的显存拷贝(由于用户空间难以直接访问内核空间的地址),这将减少程序效率,此时可以将内核空间虚拟地址所对应的数学显存映射到用户空间,借此降低显存拷贝。
显存映射时的应用层操作
在应用层可以通过函数void*mmap(void*addr,size_tlength,intprot,intflags,intfd,off_toffset)将文件(包括设备文件)映射到应用层的虚拟地址空间linux设备驱动程序 视频,此时应用程序便可以通过映射后的虚拟地址访问文件,在使用完成后应用层还应调用intmunmap(void*addr,size_tlength)来取消mmap的映射。
显存映射时的驱动层实现
当应用层调用mmap时会调用到驱动层的int(*mmap)(structfile*filp,structvm_area_struct*vma)函数adobe air linux,此函数将按照用户传递的参数调用intremap_pfn_range(structvm_area_struct*vma,unsignedlongaddr,unsignedlongpfn,unsignedlongsize,pgprot_tprot)完成显存映射,下边对着两个函数进行详尽介绍:
/** 对应到应用层的mmap函数
* filp 文件句柄
* vma 用于描述一个独立的虚拟内存区域
*/
int (*mmap) (struct file *filp, struct vm_area_struct *vma)
/** 将物理地址映射到虚拟地址空间,对于物理上不连续的内存空间可以循环调用此函数将这些不连续的物理内存映射到连续的虚拟地址空间
* vma 用于描述一个独立的虚拟内存区域
* addr 起始虚拟地址
* pfn 物理内存页框号
* size 映射空间的大小,最小为一页内存
* prot 访问权限,如果不想被cache可以采用prot = pgprot_noncached(prot)方式添加nocache标志
*/
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot)
怎么获取数学显存页框号
采用__get_free_pages、__get_free_page、kmalloc分配的用virt_to_phys可将虚拟地址转换为化学地址,之后将化学地址左移PAGE_SHIFT位即可的到化学地址页框号
采用vmalloc分配的用vmalloc_to_pfn可将虚拟地址转换为化学地址页框号
驱动代码实现
驱动程序以一个共享显存实现,它在内核空间分配一片显存,应用层可以通过read读取显存中的内容,也可以通过mmap将显存映射到应用层虚拟地址空间,如下是相应的代码实现,须要注意的是假如内核空间的显存在数学地址上不连续(如采用vmalloc分配),而期望映射到应用层虚拟地址空间是一片连续的地址时可以通过多次remap_pfn_range将数学上不连续的显存空间映射到连续的虚拟地址空间
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//#define USING_VMALLOC
#define GMEM_ORDER 3
#define GMEM_PAGES (2^(GMEM_ORDER))
#define GMEM_SIZE (GMEM_PAGES*PAGE_SIZE)
//次设备号,为MISC_DYNAMIC_MINOR表示自动分配
#define GMEM_MINOR MISC_DYNAMIC_MINOR
//设备文件名
#define GMEM_NAME "gmem"
//全局内存地址
static uint8_t *gmem_buffer;
//打开设备
static int gmem_open(struct inode *inode, struct file *file)
{
return 0;
}
//释放设备
int gmem_release(struct inode *inode, struct file *file)
{
return 0;
}
//读数据
ssize_t gmem_read(struct file *file, char __user *buffer, size_t size, loff_t *pos)
{
int ret;
size_t length = (size > GMEM_SIZE) ? GMEM_SIZE : size;
//拷贝数据到应用层
ret = copy_to_user(buffer, gmem_buffer, length);
return length - ret;
}
static int gmem_mmap(struct file *file, struct vm_area_struct *vma)
{
int result = 0;
#ifdef USING_VMALLOC
/* 采用vmalloc分配的内存在物理上不连续,所以进行映射时最好一页一页的进行映射 */
//获取应用层传递的偏移
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
//计算偏移后的内核空间虚拟地址
unsigned long map_offset = (unsigned long)gmem_buffer + offset;
//根据内核空间虚拟地址计算物理地址页框号
unsigned long pfn_start = (unsigned long)vmalloc_to_pfn((void*)map_offset);
//计算期望映射的大小
unsigned long size = vma->vm_end - vma->vm_start;
//用户空间虚拟地址起始值,可能是用户指定,可能是系统分配
unsigned long vmstart = vma->vm_start;
//映射大小必须是PAGE_SIZE的整倍数
if((size % PAGE_SIZE) != 0)
size = (size + PAGE_SIZE) / PAGE_SIZE;
//最大不超过全局内存大小
if(size > GMEM_SIZE)
size = GMEM_SIZE;
//增加no cache属性
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
printk("phy: 0x%lx, offset: 0x%lx, size: 0x%lxrn", pfn_start << PAGE_SHIFT, offset, size);
/* 对 vmalloc 分配的内存一页一页的进行映射 */
while (1)
{
//进行内存映射,每次映射一页,因为vmalloc申请的内存可能不连续
result = remap_pfn_range(vma, vmstart, pfn_start, PAGE_SIZE, vma->vm_page_prot);
if(result != 0)
{
printk("remap_pfn_range failed at rn");
break;
}
//完成对所有内存页的映射
if(size <= PAGE_SIZE)
break;
//剩余大小递减
size -= PAGE_SIZE;
//用户空间虚拟地址递加
vmstart += PAGE_SIZE;
//内核空间虚拟地址递加
map_offset += PAGE_SIZE;
//根据内存地址计算物理地址页框号
pfn_start = vmalloc_to_pfn((void *)map_offset);
}
return result;
#else
//获取应用层传递的偏移
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
//计算偏移后的内核空间虚拟地址
unsigned long map_offset = (unsigned long)gmem_buffer + offset;
//根据内核空间虚拟地址计算物理地址页框号
unsigned long pfn_start = virt_to_phys((void*)map_offset) >> PAGE_SHIFT;
//计算期望映射的大小
unsigned long size = vma->vm_end - vma->vm_start;
//用户空间虚拟地址起始值,可能是用户指定,可能是系统分配
unsigned long vmstart = vma->vm_start;
//映射大小必须是PAGE_SIZE的整倍数
if((size % PAGE_SIZE) != 0)
size = (size + PAGE_SIZE) / PAGE_SIZE * PAGE_SIZE;;
//最大不超过全局内存大小
if(size > GMEM_SIZE)
size = GMEM_SIZE;
//增加no cache属性
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
//进行内存映射
printk("phy: 0x%lx, offset: 0x%lx, size: 0x%lxrn", pfn_start << PAGE_SHIFT, offset, size);
result = remap_pfn_range(vma, vmstart, pfn_start , size, vma->vm_page_prot);
return result;
#endif
}
struct file_operations fops = {
.open = gmem_open,
.release = gmem_release,
.read = gmem_read,
.mmap = gmem_mmap,
};
struct miscdevice gmem_misc = {
.minor = GMEM_MINOR,
.name = GMEM_NAME,
.fops = &fops,
};
static int __init gmem_init(void)
{
int err = 0;
printk("global memory initrn");
#ifdef USING_VMALLOC
//采用vmalloc分配内存
gmem_buffer = vmalloc(GMEM_SIZE);
#else
//采用__get_free_pages分配内存
gmem_buffer = (uint8_t *)__get_free_pages(GFP_KERNEL, GMEM_ORDER);
#endif
if(gmem_buffer == NULL)
{
printk("allocate mem failedrn");
return -ENOMEM;
}
printk("gmem_buffer = 0x%lxrn", (unsigned long)gmem_buffer);
//注册misc设备
err = misc_register(&gmem_misc);
if(err != 0)
{
printk("register misc failedrn");
return err;
}
return 0;
}
static void __exit gmem_exit(void)
{
printk("global memory exitrn");
#ifdef USING_VMALLOC
//采用vmalloc分配的内存必须用vfree释放
vfree(gmem_buffer);
#else
//采用__get_free_pages分配的内存必须用free_pages释放
free_pages((unsigned long)gmem_buffer, GMEM_ORDER);
#endif
//注销misc设备
misc_deregister(&gmem_misc);
}
module_init(gmem_init);
module_exit(gmem_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lf");
MODULE_DESCRIPTION("mmap test");
MODULE_ALIAS("gmem");
驱动测试程序实现
驱动测试程序主要验证mmap操作是否成功,它首先通过mmap将内核空间的显存映射到应用层虚拟地址空间,之后通过读写显存的形式向其中写入数据,最好在通过read函数将写入的数据读取下来
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char * argv[])
{
int fd;
char *start;
int i;
char buf[32];
//打开设备
fd = open("/dev/gmem", O_RDWR);
if (fd == -1)
goto fail;
//映射内核空间虚拟地址到用户空间,虚拟地址系统自动分配,映射大小32B(实际映射了一页),支持读写,共享,便宜为0
start = mmap(NULL, 32, PROT_READ|PROT_WRITE, MAP_SHARED, fd, sysconf(_SC_PAGESIZE)*0);
if (start == MAP_FAILED)
goto fail;
//采用内存拷贝方式写入“abcdefghijklmnopqrstuvwxyz”
for (i = 0; i < 26; i++)
*(start + i) = 'a' + i;
*(start + i) = '';
//通过read函数读取内核空间共享内存的数据
if (read(fd, buf, 27) == -1)
goto fail;
//输出读取的数据,这里应该是"abcdefghijklmnopqrstuvwxyz"
puts(buf);
//取消mmap的映射
munmap(start, 32);
//关闭设备
close(fd);
return 0;
fail:
perror("mmap test");
exit(EXIT_FAILURE);
}
上机实验从这儿下载代码,进行编译linux压缩,并拷贝到目标板跟文件系统的root目录中通过加载insmodmmap.ko驱动,之后再通过./app.out执行linux设备驱动程序 视频,可见应用程序输出字符串”abcdefghijklmnopqrstuvwxyz“,这个字符串首先通过mmap映射得到的地址写入显存,之后又通过read函数读出并输出
本文原创地址://gulass.cn/tsqdxlnhkjnc.html编辑:刘遄,审核员:暂无