zoukankan      html  css  js  c++  java
  • (笔记)Linux内核中内存相关的操作函数

     

    linux内核中内存相关的操作函数

     

     

    1、kmalloc()/kfree()

    static __always_inline void *kmalloc(size_t size, gfp_t flags)

    内核空间申请指定大小的内存区域,返回内核空间虚拟地址。在函数实现中,如果申请的内存空间较大的话,会从buddy系统申请若干内存页面,如果申请的内存空间大小较小的话,会从slab系统中申请内存空间。有关buddy和slab,请参见《linux内核之内存管理.doc》

    gfp_t flags 的选项较多。参考内核文件gfp.h。

    在函数kmalloc()实现中,如果申请的空间较小,会根据申请空间的大小从slab中获取;如果申请的空间较大,如超过一个页面,会直接从buddy系统中获取。

     

    2、vmalloc()/vfree()

    void *vmalloc(unsigned long size)

    函数作用:从高端(如果存在,优先从高端)申请内存页面,并把申请的内存页面映射到内核的动态映射空间。vmalloc()函数的功能和alloc_pages(_GFP_HIGHMEM)+kmap() 的功能相似,只所以说是相似而不是相同,原因在于用vmalloc()申请的物理内存页面映射到内核的动态映射区(见下图),并且,用vmalloc()申请的页面的物理地址可能是不连续的。而alloc_pages(_GFP_HIGHMEM)+kmap()申请的页面的物理地址是连续的,被映射到内核的KMAP区。

    vmalloc分配的地址则限于vmalloc_start与vmalloc_end之间。每一块vmalloc分配的内核虚拟内存都对应一个vm_struct结构体(可别和vm_area_struct搞混,那可是进程虚拟内存区域的结构),不同的内核虚拟地址被4k大小的空闲区间隔,以防止越界——见下图)。与进程虚拟地址的特性一样,这些虚拟地址与物理内存没有简单的位移关系,必须通过内核页表才可转换为物理地址或物理页。它们有可能尚未被映射,在发生缺页时才真正分配物理页面。

    如果内存紧张,连续区域无法满足,调用vmalloc分配是必须的,因为它可以将物理不连续的空间组合后分配,所以更能满足分配要求。vmalloc可以映射高端页框,也可以映射底端页框。vmalloc的作用只是为了提供逻辑上连续的地址。。。

    注意:在申请页面时,如果注明_GFP_HIGHMEM,即从高端申请。则实际是优先从高端内存申请,顺序为(分配顺序是HIGH, NORMAL, DMA )。

     

    3、alloc_pages()/free_pages()

    内核空间申请指定个数的内存页,内存页数必须是2^order个页。

    alloc_pages(gfp_mask, order) 中,gfp_mask 是flag标志,其中可以为_ _GFP_DMA、_GFP_HIGHMEM 分别对应DMA和高端内存。

    注:该函数基于buddy系统申请内存,申请的内存空间大小为2^order个内存页面。

    参见《linux内核之内存管理.doc》

    通过函数alloc_pages()申请的内存,需要使用kmap()函数分配内核的虚拟地址。

     

    4、__get_free_pages()/__free_pages()

    unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

    作用相当于alloc_pages(NORMAL)+kmap(),但不能申请高端内存页面。

     

    __get_free_page()只申请一个页面。

     

    5、kmap()/kunmap()

    返回指定页面对应内核空间的虚拟地址。

    #include <linux/highmem.h>

    void *kmap(struct page *page);

    void kunmap(struct page *page);

    kmap 为系统中的任何页返回一个内核虚拟地址. 

    对于低端内存页,它只返回页的逻辑地址; 

    对于高端内存页, kmap在“内核永久映射空间”中创建一个特殊的映射. 这样的映射数目是有限, 因此最好不要持有过长的时间. 

    使用 kmap 创建的映射应当使用 kunmap 来释放;    

    kmap 调用维护一个计数器, 因此若2个或多个函数都在同一个页上调用kmap也是允许的. 

    通常情况下,“内核永久映射空间”是 4M 大小,因此仅仅需要一个页表即可,内核通过来 pkmap_page_table 寻找这个页表。

    注意:不用时及时释放。

     

    kmalloc()和vmalloc()相比,kmalloc()总是从ZONE_NORMAL(下图中的直接映射区)申请内存。kmalloc()分配的内存空间通常用于linux内核的系统数据结构和链表。因内核需要经常访问其数据结构和链表,使用固定映射的ZONE_NORMAL空间的内存有利于提高效率。

    使用vmalloc()可以申请非连续的物理内存页,并组成虚拟连续内存空间。vmalloc()优先从高端内存(下图中的动态映射区)申请。内核在分配那些不经常使用的内存时,都用高端内存空间(如果有),所谓不经常使用是相对来说的,比如内核的一些数据结构就属于经常使用的,而用户的一些数据就属于不经常使用的。

    alloc_pages(_GFP_HIGHMEM)+kmap() 方式申请的内存使用内核永久映射空间(下图中的KMAP区),空间较小(通常4M线性空间),不用时需要及时释放。另外,可以指定alloc_pages()从直接映射区申请内存,需要使用_GFP_NORMAL属性指定。

    __get_free_pages()/__free_pages() 不能申请高端内存页面,操作区域和kmalloc()相同(下图中的动态映射区)。

     

    6、virt_to_page()

     

    其作用是由内核空间的虚拟地址得到页结构。见下面的宏定义。

    #define virt_to_pfn(kaddr)    (__pa(kaddr) >> PAGE_SHIFT)

    #define pfn_to_virt(pfn)       __va((pfn) << PAGE_SHIFT)

     

    #define virt_to_page(addr)    pfn_to_page(virt_to_pfn(addr))

    #define page_to_virt(page)    pfn_to_virt(page_to_pfn(page))

     

    #define __pfn_to_page(pfn)  (mem_map + ((pfn) - ARCH_PFN_OFFSET))

    #define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + ARCH_PFN_OFFSET)

    7、物理地址和虚拟地址之间转换

    #ifdef CONFIG_BOOKE

    #define __va(x) ((void *)(unsigned long)((phys_addr_t)(x) + VIRT_PHYS_OFFSET))

    #define __pa(x) ((unsigned long)(x) - VIRT_PHYS_OFFSET)

    #else

    #define __va(x) ((void *)(unsigned long)((phys_addr_t)(x) + PAGE_OFFSET - MEMORY_START))

    #define __pa(x) ((unsigned long)(x) - PAGE_OFFSET + MEMORY_START)

    #endif

     

    8、ioremap()/iounmap()

    ioremap()的作用是把device寄存器和内存的物理地址区域映射到内核虚拟区域,返回值为内核的虚拟地址。

    注明:在内核中操作内存空间时使用的都是内核虚拟地址,必须把device的空间映射到内核虚拟空间。

    #include <asm/io.h>

    void *ioremap(unsigned long phys_addr, unsigned long size);

    void *ioremap_nocache(unsigned long phys_addr, unsigned long size);  映射非cache的io内存区域

    void iounmap(void * addr);

     

    为了增加可移植性,最好使用下面的接口函数读写io内存区域,

    unsigned int ioread8(void *addr);

    unsigned int ioread16(void *addr);

    unsigned int ioread32(void *addr);

    void iowrite8(u8 value, void *addr);

    void iowrite16(u16 value, void *addr);

    void iowrite32(u32 value, void *addr);

    如果你必须读和写一系列值到一个给定的 I/O 内存地址, 你可以使用这些函数的重复版本:

    void ioread8_rep(void *addr, void *buf, unsigned long count);

    void ioread16_rep(void *addr, void *buf, unsigned long count);

    void ioread32_rep(void *addr, void *buf, unsigned long count);

    void iowrite8_rep(void *addr, const void *buf, unsigned long count);

    void iowrite16_rep(void *addr, const void *buf, unsigned long count);

    void iowrite32_rep(void *addr, const void *buf, unsigned long count);

    这些函数读或写 count 值从给定的 buf 到 给定的 addr. 注意 count 表达为在被写入的数据大小; ioread32_rep 读取 count 32-位值从 buf 开始.

     

    9、request_mem_region()

    本函数的作用是:外设的io端口映射到io memory region中。在本函数实现中会检查输入到本函数的参数所描述的空间(下面成为本io空间)是否和io memory region中已存在的空间冲突等,并设置本io空间的parent字段等(把本io空间插入到io 空间树种)。

    注明:io memory region 空间中是以树形结构组织的,默认的根为iomem_resource描述的io空间,其name为"PCI mem"。

    request_mem_region(start,n,name) 输入的参数依次是设备的物理地址,字节长度,设备名字。函数返回类型如下

    struct resource {

           resource_size_t start;

           resource_size_t end;

           const char *name;

           unsigned long flags;

           struct resource *parent, *sibling, *child;

    };

     

    10、SetPageReserved()

    随着linux的长时间运行,空闲页面会越来越少,为了防止linux内核进入请求页面的僵局中,Linux内核采用页面回收算法(PFRA)从用户进程和内核高速缓存中回收内存页框,并根据需要把要回收页框的内容交换到磁盘上的交换区。调用该函数可以使页面不被交换。

    #define SetPageReserved(page)                set_bit(PG_reserved, &(page)->flags)

    PG_reserved 的标志说明如下。

    * PG_reserved is set for special pages, which can never be swapped out. Some

     * of them might not even exist (eg empty_bad_page)...

    可参考下面的文章

    http://blog.csdn.net/bullbat/article/details/7311205

    http://blog.csdn.net/cxylaf/article/details/1626534

     

    11、do_mmap()/do_ummap()

    内核使用do_mmap()函数为进程创建一个新的线性地址区间。但是说该函数创建了一个新VMA并不非常准确,因为如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并为一个。如果不能合并,那么就确实需要创建一个新的VMA了。但无论哪种情况, do_mmap()函数都会将一个地址区间加入到进程的地址空间中--无论是扩展已存在的内存区域还是创建一个新的区域。

    同样,释放一个内存区域应使用函数do_ummap(),它会销毁对应的内存区域。

     

    12、get_user_pages()

    作用是在内核空间获取用户空间内存的page 描述,之后可以通过函数kmap() 获取page 对应到内核的虚拟地址。

    int get_user_pages(struct task_struct *tsk, struct mm_struct *mm,
    unsigned long start, int len, int write, int force,
    struct page **pages, struct vm_area_struct **vmas)

    参数说明

    参数tsk:指示用户空间对应进程的task_struct数据结构。只是为了记录错误信息用,该参数可以为空。

    参数mm:从该mm struct中获取start 指示的若干页面。

    参数start:参数mm空间的起始地址,即用户空间的虚拟地址。

    参数len:需要映射的页数。

    参数write:可以写标志。

    参数force:强制可以写标志。

    参数pages:输出的页数据结构。

    参数vmas:对应的需要存储区,(没有看明白对应的代码)

    返回值:数返回实际获取的页数,貌似对每个实际获取的页都是给页计数值增1,如果实际获取的页不等于请求的页,要放弃操作则必须对已获取的页计数值减1。

     

    13、copy_from_user()和copy_to_user()

    主要应用于设备驱动中读写函数中,通过系统调用触发,在当前进程上下文内核态运行(即当前进程通过系统调用触发)。

    copy_from_user的目的是防止用户程序欺骗内核,将一个非法的地址传进去,如果没有它,这一非法地址就检测不到,内和就会访问这个地址指向的数据。因为在内核中访问任何地址都没有保护,如果不幸访问一个错误的内存地址会搞死内核或发生更严重的问题

    copy_from_user调用了access_ok,所以才有“自己判断功能“

    access_ok(),可以检查访问的空间是否合法。

    注意:中断代码时不能用copy_from_user,因为其调用了might_sleep()函数,会导致睡眠。

     

    unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

    通常用在设备读函数或ioctl 中获取参数的函数中:其中“to”是用户空间的buffer地址,在本函数中将内核buffer“from”除的n个字节拷贝到用户空间的“to”buffer。

     

    unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)

    通常用在设备写函数或ioctl中设置参数的函数中:“to”是内核空间的buffer指针,要写入的buffer;“from”是用户空间的指针,数据源buffer。

     

    14、get_user(x, ptr)

    本函数的作用是获取用户空间指定地址的数值并保存到内核变量x中,ptr为用户空间的地址。用法举例如下。

    get_user(val, (int __user *)arg)

    注明:函数用户进程上下文内核态,即通常在系统调用函数中使用该函数。

     

    15、put_user(x, ptr)

    本函数的作用是将内核空间的变量x的数值保存到用户空间指定地址处,prt为用户空间地址。用法举例如下。

    put_user(val, (int __user *)arg)

    注明:函数用户进程上下文内核态,即通常在系统调用函数中使用该函数。

     

  • 相关阅读:
    LeetCode Array Easy 414. Third Maximum Number
    LeetCode Linked List Medium 2. Add Two Numbers
    LeetCode Array Easy 283. Move Zeroes
    LeetCode Array Easy 268. Missing Number
    LeetCode Array Easy 219. Contains Duplicate II
    LeetCode Array Easy 217. Contains Duplicate
    LeetCode Array Easy 189. Rotate Array
    LeetCode Array Easy169. Majority Element
    LeetCode Array Medium 11. Container With Most Water
    LeetCode Array Easy 167. Two Sum II
  • 原文地址:https://www.cnblogs.com/tdyizhen1314/p/4151831.html
Copyright © 2011-2022 走看看