频道栏目
首页 > 资讯 > C++ > 正文

memory 编程接口

12-10-25        来源:[db:作者]  
收藏   我要投稿
在Linux系统中,一般有32位(4GB)的地址空间,进程的4GB内存空间被分为两个部分 — 用户空间与内核空间,用户空间地址一般分布为0 ~ 3GB(即PAGE_OFFSET,一般等于0xc0000000),剩下的3 ~ 4GB则为内核空间。进程与内核不各自使用4GB的空间,好处就是进程进入内核不需要切换页表,降低了进出内核的消耗。内核空间从低地址到高地址又分为:物理内存映射区、vmalloc 虚拟内存分配区、高端内存映射区、保留区。每个进程的用户空间都是完全独立的,用户进程各自有不同的页表。而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。内核空间地址有自己的页表,内核的虚拟地址空间独立于其他用户进程。
一、数据结构
用户进程创建后可以访问整个用户空间的虚拟地址,这段空间是未分段的线性地址范围,在内核中进程地址空间以及与之相关的所有信息都保存在 mm_struct 中,该结构出现在进程控制结构 task_struct 中。进程用到的每段连续有效地址范围称为内存区,一个内存区由 vm_area_struct 描述符表示,每个内存区描述符都描述它所表示的一段连续地址区间。不同的内存区有不同的保护方案和特点,比如程序代码段的某些部分标记为只读,而其他部分标记为可写或可执行。
1、mm_struct
[cpp]
struct mm_struct { 
    struct vm_area_struct * mmap;       /* list of VMAs */ 
    struct rb_root mm_rb; 
    struct vm_area_struct * mmap_cache; /* last find_vma result */ 
... 
    pgd_t * pgd; 
    atomic_t mm_users;          /* How many users with user space? */ 
    atomic_t mm_count;          /* How many references to "struct mm_struct" (users count as 1) */ 
    int map_count;              /* number of VMAs */ 
    struct rw_semaphore mmap_sem; 
    spinlock_t page_table_lock;     /* Protects page tables and some counters */ 
 
    struct list_head mmlist;        /* List of maybe swapped mm's.  These are globally strung
                                     * together off init_mm.mmlist, and are protected
                                     * by mmlist_lock
                                     */ 
... 
    unsigned long total_vm, locked_vm, shared_vm, exec_vm; 
    unsigned long stack_vm, reserved_vm, def_flags, nr_ptes; 
    unsigned long start_code, end_code, start_data, end_data;  // 进程在内存中代码段、数据段的起始和结束地址 
    unsigned long start_brk, brk, start_stack;                 // 进程堆的起始和结束地址、栈的起始地址 
    unsigned long arg_start, arg_end, env_start, env_end;      // 参数、环境段的起始和结束地址 
... 
}; 
其中 mmap 参数表示进程的所有内存区描述符组成链表的头节点地址,mm_struct 通过 mmap访问该链表,而 vm_area_struct 中的 vm_next 指针将各个内存区链接起来;mmap_cache 指向进程最后一次访问的内存区描述符指针,用于提高访问效率;mm_users 存放访问该进程地址空间的进程数量;mm_count 是对 mm_struct 的使用统计,其值为0时说明没有进程使用则将其回收;map_count 存在在进程地址空间中的内存区数量,即 vm_area_struct 描述符数量。
2、vm_area_struct
[cpp]
struct vm_area_struct { 
    struct mm_struct * vm_mm;   /* The address space we belong to. */ 
    unsigned long vm_start;     /* Our start address within vm_mm. */ 
    unsigned long vm_end;       /* The first byte after our end address
                       within vm_mm. */ 
 
    /* linked list of VM areas per task, sorted by address */ 
    struct vm_area_struct *vm_next; 
 
    pgprot_t vm_page_prot;      /* Access permissions of this VMA. */ 
    unsigned long vm_flags;     /* Flags, see mm.h. */ 
 
    struct rb_node vm_rb; 
... 
    /* Function pointers to deal with this struct. */ 
    const struct vm_operations_struct *vm_ops; 
... 
}; 
其中 vm_mm 指向该内存区所属的进程地址空间;vm_start 和 vm_end 表示该虚拟内存区的起始和结束地址,考虑到性能问题,内存区的起始地址必须是页面大小的整数倍;vm_next 指向该进程的下一个虚拟内存区;vm_ops 用于操作特定的虚拟内存区,包括打开、关闭、反映射内存区等操作。
二、编程接口
在用户空间动态申请内存的函数为 malloc,释放函数为 free。内核空间申请内存涉及的函数主要包括 kmalloc、__get_free_pages 和 vmalloc 等。kmalloc 和 __get_free_pages 申请的内存位于物理内存映射区域,而且在物理也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系。而 vmalloc 在虚拟内存空间给出一块连续的内存区,实质上,这片连续的虚拟内存在物理内存中并不一定连续,其申请的虚拟内存和物理内存之间也没有简单的换算关系。
1、kmalloc
[cpp] 
/**
 * kmalloc - 分配一块指定大小的内存
 * @size:    内存区的大小
 * @flags:   分配标志,可能值有 GFP_ATOMIC、GFP_KERNEL 等
 *
 * Note: 当标志为 GFP_KERNEL 的时候可能会引起睡眠
 */ 
 
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); 
 
相关TAG标签
上一篇:两度入侵港交所网站的黑客罪名成立 自称“沙士英雄”
下一篇:android之[ WebView组件 ]
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站