Linux设备驱动之内存管理(3)

内核中有些地方的内存分配是不允许失败的,内核开发者建立了一种称为内存池的抽象。内存池其实就是某种形式的高速后备缓存,它试图始终保持空闲的内存以便在紧急状态下使用。mempool很容易浪费大量内存,应尽量避免使用。

#include <linux/mempool.h> /* 创建 */ mempool_t *mempool_create(int min_nr, /* 需要预分配对象的数目 */ mempool_alloc_t *alloc_fn, /* 分配函数,一般直接使用内核提供的mempool_alloc_slab */ mempool_free_t *free_fn, /* 释放函数,一般直接使用内核提供的mempool_free_slab */ void *pool_data); /* 传给alloc_fn/free_fn的参数,一般为kmem_cache_create创建的cache */ /* 分配释放 */ void *mempool_alloc(mempool_t *pool, int gfp_mask); void mempool_free(void *element, mempool_t *pool); /* 回收 */ void mempool_destroy(mempool_t *pool); 内存映射

一般情况下,用户空间是不可能也不应该直接访问设备的,但是,设备驱动程序中可实现mmap()函数,这个函数可使得用户空间直能接访问设备的物理地址。
这种能力对于显示适配器一类的设备非常有意义,如果用户空间可直接通过内存映射访问显存的话,屏幕帧的各点的像素将不再需要一个从用户空间到内核空间的复制的过程。
从 file_operations 文件操作结构体可以看出,驱动中 mmap()函数的原型如下:

int(*mmap)(struct file *, struct vm_area_struct*);

驱动程序中 mmap()的实现机制是建立页表,并填充 VMA 结构体中 vm_operations_struct 指针。VMA 即 vm_area_struct,用于描述一个虚拟内存区域:

struct vm_area_struct { unsigned long vm_start; /* 开始虚拟地址 */ unsigned long vm_end; /* 结束虚拟地址 */ unsigned long vm_flags; /* VM_IO 设置一个内存映射I/O区域; VM_RESERVED 告诉内存管理系统不要将VMA交换出去 */ struct vm_operations_struct *vm_ops; /* 操作 VMA 的函数集指针 */ unsigned long vm_pgoff; /* 偏移(页帧号) */ void *vm_private_data; ... } struct vm_operations_struct { void(*open)(struct vm_area_struct *area); /*打开 VMA 的函数*/ void(*close)(struct vm_area_struct *area); /*关闭 VMA 的函数*/ struct page *(*nopage)(struct vm_area_struct *area, unsigned long address, int *type); /*访问的页不在内存时调用*/ /* 当用户访问页前,该函数允许内核将这些页预先装入内存。驱动程序一般不必实现 */ int(*populate)(struct vm_area_struct *area, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock); ...

建立页表的方法有两种:使用remap_pfn_range函数一次全部建立或者通过nopage VMA方法每次建立一个页表。

remap_pfn_range
remap_pfn_range负责为一段物理地址建立新的页表,原型如下:

int remap_pfn_range(struct vm_area_struct *vma, /* 虚拟内存区域,一定范围的页将被映射到该区域 */ unsigned long addr, /* 重新映射时的起始用户虚拟地址。该函数为处于addr和addr+size之间的虚拟地址建立页表 */ unsigned long pfn, /* 与物理内存对应的页帧号,实际上就是物理地址右移 PAGE_SHIFT 位 */ unsigned long size, /* 被重新映射的区域大小,以字节为单位 */ pgprot_t prot); /* 新页所要求的保护属性 */

demo:

static int xxx_mmap(struct file *filp, struct vm_area_struct *vma) { if (remap_pfn_range(vma, vma->vm_start, vm->vm_pgoff, vma->vm_end - vma->vm_start, vma->vm_page_prot)) /* 建立页表 */ return - EAGAIN; vma->vm_ops = &xxx_remap_vm_ops; xxx_vma_open(vma); return 0; } /* VMA 打开函数 */ void xxx_vma_open(struct vm_area_struct *vma) { ... printk(KERN_NOTICE "xxx VMA open, virt %lx, phys %lx\n", vma->vm_start, vma->vm_pgoff << PAGE_SHIFT); } /* VMA 关闭函数 */ void xxx_vma_close(struct vm_area_struct *vma) { ... printk(KERN_NOTICE "xxx VMA close.\n"); } static struct vm_operations_struct xxx_remap_vm_ops = { /* VMA 操作结构体 */ .open = xxx_vma_open, .close = xxx_vma_close, ... };

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/14646.html