设为首页 收藏本站
查看: 325|回复: 0

[经验分享] kernel 3.10代码分析--vmalloc(非连续内存页分配)

[复制链接]
累计签到:77 天
连续签到:1 天
发表于 2014-8-14 09:02:48 | 显示全部楼层 |阅读模式
1、为什么需要vmalloc
在分配内存时,总是希望能分配到连续的物理内存页,愿望是美好的,但系统中可能没有太多的连续内存可用(比如内存碎片严重时),此时就需要一种非连续内存的分配方式。于是乎,就产生了vmalloc,vmalloc用于分配不连续的物理内存页,但将其映射到内核虚拟地址空间中后,其虚拟地址是连续的,使用vmalloc分配内存有三个特点:
1)物理内存不一定连续
2)返回的虚拟内存是连续的。
3)优先分配高端内存,所以vmalloc也是在内核态使用高端内存的最主要的方式。
在用户态,应用程序直接看到的是虚拟地址空间,物理内存和虚拟内存间通过页表映射,用户太分配的内存在虚拟地址空间中总是连续的,而物理内存是否连续不必关心,所以这对用户态来说其实没啥影响(除了会稍影响性能和占用TLB)。

2、基本原理(针对IA32)
内核虚拟地址空间中,有一段专门的区间用于vmalloc,称之vmalloc区,位于线性映射区之后,准确的说是从892M+8M(VMALLOC_START)到VMALLOC_END之间,其中892M是线性映射区,用于线性映射低端内存,之后的8M是安全间隙,用于区间隔离,防止越界。
vmalloc区中包含一个个独立的子区域,每个子区域用于一次独立映射,各个子区域间通过一个内存页进行隔离,防止不正确的内存访问操作。内核中管理vmalloc区中的子区域,使用了vm_struct数据结构



    /*
      * vmalloc区域中的子内存区,ioremap也使用了该区域
      * 所有的vm_struct组成一个链表,管理着vmalloc区域
      * 中已经建立的各个子区域,该链表头保存于
      * 全局变量vmlist中。
      */
    struct vm_struct {
        struct vm_struct    *next;
        //该内存区在虚拟地址空间中的起始地址
        void            *addr;
        // 该内存区的大小
        unsigned long        size;
        /*
         * 与该内存区关联的标志集合,用于指定内存
         * 区域类型,可选值有3:
         * VM_ALLOC指定由vmalloc产生的子区域
         * VM_MAP表示将现存pages集合映射到连续的虚拟
         地址空间中。
         * VM_IOREMAP表示将IO内存映射到vmalloc区域中。
         */
        unsigned long        flags;
        /*
          * 指向page指针的数组,每个数组成员表示一个
          * 映射到虚拟地址空间中的物理内存页的实例。
         */
        struct page        **pages;
        // pages数组项的数目,即该内存区对应的物理内存页数
        unsigned int        nr_pages;
        /*
         * ioremap时使用,用来保存该区域映射的物理
         * 内存地址,在通常的vmalloc流程中不使用该
         * 字段,因为vmalloc流程中会分配物理内存,并
         * 通过修改内核页表来实现虚拟地址到物理
         * 地址见的映射。
        */
        phys_addr_t        phys_addr;
        const void        *caller;
    };

vmalloc基本流程如下:
vmalloc()
-->__vmalloc()
     -->__vmalloc_node()
          -->__vmalloc_node_range()
               -->get_vm_area_node()     //从vmalloc区中获取空闲的子区域
               -->__vmalloc_area_node() //使用alloc_page分配物理内存,并修改页表,进行映射。

3、代码分析
__vmalloc_node_range:



    // vmalloc主处理函数
    void *__vmalloc_node_range(unsigned long size, unsigned long align,
                unsigned long start, unsigned long end, gfp_t gfp_mask,
                pgprot_t prot, int node, const void *caller)
    {
        //vmalloc区域中的子内存区
        struct vm_struct *area;
        void *addr;
        unsigned long real_size = size;

        // 分配内存区大小进行页对齐
        size = PAGE_ALIGN(size);
        if (!size || (size >> PAGE_SHIFT) > totalram_pages)
            goto fail;

        /*
         * 从vmalloc区中的子区域链表vmlist(全局变量)中
         * 获取空闲的子区域vm_struct。
         */
        
        area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNLIST,
                     start, end, node, gfp_mask, caller);
        if (!area)
            goto fail;

        /*
         * 分配物理内存,并通过修改内核页表建立物理地址和虚拟
         * 地址间的映射,返回值为vmalloc区的虚拟地址
         */
        addr = __vmalloc_area_node(area, gfp_mask, prot, node, caller);
        if (!addr)
            return NULL;

        /*
         * In this function, newly allocated vm_struct has VM_UNLIST flag.
         * It means that vm_struct is not fully initialized.
         * Now, it is fully initialized, so remove this flag here.
         */
        clear_vm_unlist(area);

        /*
         * A ref_count = 3 is needed because the vm_struct and vmap_area
         * structures allocated in the __get_vm_area_node() function contain
         * references to the virtual address of the vmalloc'ed block.
         */
        kmemleak_alloc(addr, real_size, 3, gfp_mask);

        return addr;

    fail:
        warn_alloc_failed(gfp_mask, 0,
                 "vmalloc: allocation failure: %lu bytes\n",
                 real_size);
        return NULL;
    }

__vmalloc_area_node:



    static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
                     pgprot_t prot, int node, const void *caller)
    {
        const int order = 0;
        struct page **pages;
        unsigned int nr_pages, array_size, i;
        gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;

        nr_pages = (area->size - PAGE_SIZE) >> PAGE_SHIFT;
        array_size = (nr_pages * sizeof(struct page *));

        // 此区域需要分配的物理内存页数
        area->nr_pages = nr_pages;
        /* Please note that the recursion is strictly bounded. */
        if (array_size > PAGE_SIZE) {
            /*
             * 如果区域大小大于1页,则进行迭代,注意
             * 此处传入的内存分配标记中有__GFP_HIGHMEM,
             * 表示优先从高端内存中分配,这也是vmalloc
             * 的用处所在。
             */
            pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM,
                    PAGE_KERNEL, node, caller);
            area->flags |= VM_VPAGES;
        // 迭代直到分配的区域大小小于1页,则使用kmalloc分配
        } else {
            pages = kmalloc_node(array_size, nested_gfp, node);
        }
        area->pages = pages;
        area->caller = caller;
        if (!area->pages) {
            remove_vm_area(area->addr);
            kfree(area);
            return NULL;
        }

        // 对于整页,逐页进行分配
        for (i = 0; i < area->nr_pages; i++) {
            struct page *page;
            gfp_t tmp_mask = gfp_mask | __GFP_NOWARN;

            if (node < 0)
                // 分配实际的物理页
                page = alloc_page(tmp_mask);
            else
                page = alloc_pages_node(node, tmp_mask, order);

            if (unlikely(!page)) {
                /* Successfully allocated i pages, free them in __vunmap() */
                area->nr_pages = i;
                goto fail;
            }
            // 将分配的page填入vm_struct结构中
            area->pages[i] = page;
        }

        /*
         * 将新分配的区域进行映射,即修改内核页表
         * (进程页表在page fault中更新),建立虚拟地址到
         * 物理地址间的映射。
         */
        if (map_vm_area(area, prot, &pages))
            goto fail;
        return area->addr;

    fail:
        warn_alloc_failed(gfp_mask, order,
                 "vmalloc: allocation failure, allocated %ld of %ld bytes\n",
                 (area->nr_pages*PAGE_SIZE), area->size);
        vfree(area->addr);
        return NULL;
    }



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-23736-1-1.html 上篇帖子: 关于open source bbs体系架构的介绍 下篇帖子: Ubuntu下实现SunPinyin翻页
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表