memory 编程接口(二)

2014-11-24 09:39:06 · 作者: · 浏览: 1
EL 的时候可能会引起睡眠
*/

void *kmalloc(size_t size, int flags);
使用 kmalloc 分配的内存应该用 kfree 释放。
2、mmap
[cpp]
/**
* mmap - 将物理地址映射至用户空间
* @addr: 指定文件应被映射到进程空间的起始地址,一般被指定为NULL,此时选择起始地址的任务留给内核来完成
* @len: 映射到用户空间的字节数
* @prot: 指定被映射空间的访问权限,可取如下几个值的或:
* PROT_READ(可读),PROT_WRITE (可写),PROT_EXEC (可执行),PROT_NONE(不可访问)
* @flags: 由以下几个常值指定:MAP_SHARED,MAP_PRIVATE,MAP_FIXED,MAP_ANON
* @fd: 映射到用户空间的文件的描述符,一般由open()返回
* 同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射
* @offset: 被映射内存区在文件中的偏移值
*/

void* mmap(void * addr, size_t len, int prot, int flags, int fd, off_t offset);
该函数映射文件描述符 fd 指定文件的 [offset, offset + len] 物理内存区至调用进程的 [addr, addr + len] 的用户空间虚拟内存区,通常用于内存的共享或者用户空间程序控制硬件设备,函数的返回值为最后文件映射到用户空间的地址,进程可直接操作该地址。当用户调用 mmap 的时候,内核会根据 fd 找到相对应设备驱动或者文件 系统的file_operations,比如在 camera 里面就是 v4l2_file_operations,然后调用里面定义的 mmap 操作,一个常见的处理流程如下:
[cpp]
static int xxx_mmap(struct file *file, struct vm_area_struct *vma)
{
int ret;
unsigned long size = vma->vm_end - vma->vm_start; // 计算将要映射的内存大小
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; // 计算映射内存区的偏移值
struct xxx_info *info = file->private_data;

mutex_lock(&info->lock);

if (size > info->gbuffers * info->gbufsize - offset) { // 如果要映射的区域过大则返回错误值
mutex_unlock(&info->lock);
return -EINVAL;
}

if (!info->mmap_start_base) { // 检测物理地址是否有效
mutex_unlock(&info->lock);
return -EIO;
}

vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);// 对映射区域增加 nocache 属性
ret = remap_pfn_range(vma, vma->vm_start, // 调用 remap_pfn_range 函数创建页表
(uint32_t)(info->mmap_start_base + offset) >> PAGE_SHIFT,
size, vma->vm_page_prot);
if(ret) {
mutex_unlock(&info->lock);
return -EAGAIN;
}

mutex_unlock(&info->lock);
return 0;
}
在驱动程序中,我们能使用 remap_pfn_range 映射内存中的保留页和设备 I/O 内存,另外,kmalloc 申请的的内存若要被映射到用户空间可以通过mem_map_reserve 设置为保留页后进行,示例代码如下:
[cpp]
buffer = kmalloc(size, GFP_KERNEL); // 申请buffer

for(page = virt_to_page(buffer); page < virt_to_page(buffer + size); page++)
mem_map_reserve(page); // 设置页为保留页
我们再看一下 remap_pfn_range 的说明:
[cpp]
/**
* remap_pfn_range - 映射物理地址到用户空间
* @vma: 虚拟内存区域指针,由内核根据用户请求自动填充,函数将物理地址区域映射至该虚拟内存区
* @addr: 虚拟内存区的起始地址,函数将为 addr ~ addr + size 的虚拟地址区域构造页表
* @pfn: 被映射物理地址的页帧号,即将物理地址右移PAGE_SHIFT(12位,一般PAGE_SIZE为4KB)
* @size: 被映射内存区域大小
* @prot: 页表的保护属性
*
* Note: 调用者需要持有 mm 信号量
*/

int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size, pgprot_t prot);
总结 mmap 的执行顺序如下:
(1)在用户进程创建一个虚拟内存区 - vma
(2)驱动程序调用 ramap_pfn_range 为被映射的物理内存区构造页表
(3)将页表分配给虚拟内存区 - vma
3、ioremap_nocache
[cpp]
/**
* ioremap_nocache - 将物理地址映射至内核空间
* @phys_addr: 物理地址起始值
* @size: 要映射的空间大小
*
* Note: 必须由 iounmap() 释放
*/

void __iomem *ioremap_nocache (unsigned long phys_addr, unsigned long size)
{
return __ioremap(phys_addr | MEM_NON_CACHEABLE, size, 0);
}
在驱动中申请到一片连续的物理内存后,通常需要将物理地址映射到内核的虚拟地址空间,然后就可以在驱动代码里面直接使用虚拟地址访问那段物理内存了,用法如下:
[cpp]
vbase = ioremap_nocache((unsigned long)phys_start_base, cam_total_buf_size);