首页 > 系统相关 >内存管理-11-buddy伙伴子系统-1-初探

内存管理-11-buddy伙伴子系统-1-初探

时间:2024-07-02 14:42:11浏览次数:19  
标签:11 buddy zone pages order 内存 初探 pfn page

基于msm-5.4

一、伙伴系统概述

1. 简介

伙伴系统是物理内存的三大管理机制之一,另外两个是slab缓存和per-cpu页帧缓存。##### 管理物理内存实际上就是管理 page 结构,将page添加到不同链表上进行管理。当用户申请内存的时候,从链表上拿一个page返还给用户,然后用户根据page可以找到对应的物理内存,进行读写操作。

伙伴系统中的4M的最大内存块,也被称为页块,pageblock。

在最初刚初始化的时候,伙伴系统里面什么也没有,伙伴系统会将所有的物理内存划分成 pageblock 的大小挂入到伙伴系统中。

伙伴系统是以分区为单位进行实现的。伙伴系统对一个分区上绝大多数物理内存进行管理,也即将绝大多数物理页被放到伙伴系统的 zone::free_area[] 的链表中进行管理。

zone中除了 free_area[] 外,还有一个 lowmem_reserve[], 它里面会留一些备份,并不会将当前分区中的所有物理页都放到伙伴系统中。######

伙伴系统对应的头文件是 linux/gfp.h.


2. 分配释放逻辑

当用户申请时,根据申请的大小到对应order对应的链表上去查找,若没有可用空闲内存块,则会去 order+1 对应的链表上去查找,若还没有,则依次增大order重复这个过程,直到分配到了内存。

当用户释放时,将内存块插入到其大小对应order对应的链表上,在插入到链表上时,可能做一些合并操作。合并条件是,插入后相邻两个物理内存块大小相等,且满足地址整除条件。


3. 版本迭代

新版本 free_area[] 中又有好几个链表,见 enum migratetype,分为不可移动的、可移动的、可回收的、CMA类型的、PCP类型的、ISOLATE类型的。

物理内存会用作不同的用途,比如运行一个程序的时候,需要把程序加载到内存中,此时物理页主要是用来存放代码的,我们知道代码是有地址的,在编译链接的时候这个地址是固定的,加载到内存中后就不要移动了,否则程序运行可能出问题。


二、相关数据结构

1. struct page

struct page {
    unsigned long private; //page_order
    atomic_t _mapcount;
    atomic_t _refcount;
}

private: 表示当前内存块的大小。当从伙伴系统申请内存快的时候,返回的是内存块第一个物理页的page指针,用户根据此成员表示的order值获知申请到的内存块的大小。
_mapcount: 初始化为-1,若是没有从伙伴系统中分走,就还是-1. 若这个值发生变化了,就说明做了映射了,这个页面给用户使用了。
_refcount: 物理页的引用计数。


2. 数据结构间的关系


三、接口函数

1. 释放物理页到伙伴系统

//include/linux/gfp.h

#define __free_page(page) __free_pages((page), 0)
#define free_page(addr) free_pages((addr), 0)
void __free_pages(struct page *page, unsigned int order);
void free_pages(unsigned long addr, unsigned int order);


2. 从伙伴系统中分配物理页

//include/linux/gfp.h

#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0);
#define alloc_pages(gfp_mask, order) alloc_pages_node(numa_node_id(), gfp_mask, order)

返回值是struct page 指针,可以根据 page 找到对应的物理内存,通过转换可以找到对应的页帧号,甚至对应的虚拟地址。

gfp_mask 可以决定去哪个zone中去分配,如 GFP_DMA,优先去DMA分区去查找。每个zone分区上都有一个伙伴系统,这里的mask主要是决定优先从哪个zone分配而已,若指定的分区分配不到,还会去其它分区去分配。


四、实现逻辑

1. alloc_pages实现

分配内存有快速路径和慢速路径。

/*
 * alloc_pages(gfp_mask, order) --> alloc_pages_node(0, gfp_mask, order) --> __alloc_pages_node(0, gfp_mask, order) 
 * -->__alloc_pages(gfp_mask, order, 0) --> __alloc_pages_nodemask(gfp_mask, order, 0, NULL) 恒: (gfp_mask, order, 0, NULL)
 */
struct page * __alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,    nodemask_t *nodemask) //page_alloc.c
{
    /* fastpath: 从伙伴系统空闲链表中获取page。若是申请的是单个页,优先从cpu本地缓存去拿 */
    page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);

    /* slowpath: 对内存进行整理、迁移、压缩、交换,腾出大块连续物理内存 */
    page = __alloc_pages_slowpath(alloc_mask, order, &ac);
}


五、相关调试接口

1. /proc/buddyinfo

# cat /proc/buddyinfo
Node 0, zone   Normal   6424   2088   5924   2829   1361    595    152     52     15      4    212

需要使能 CONFIG_PROC_FS 才会打印,实现函数:

/* frag_show() 中遍历 pgdat->node_zones[],对于每一个zone都调用这个函数 */
static void frag_show_print(struct seq_file *m, pg_data_t *pgdat, struct zone *zone) //mm/vmstat.c
{
    int order;

    seq_printf(m, "Node %d, zone %8s ", pgdat->node_id, zone->name);
    for (order = 0; order < MAX_ORDER; ++order)
        seq_printf(m, "%6lu ", zone->free_area[order].nr_free);
}

嵌入式设备上只有一个node,所以是node 0,这里只有一个 Normal zone,后面11个数字,依次表示 order=0-10的链表上的内存块的个数。


2. /proc/pagetypeinfo

要想做更细致的查看每个order对应链表的迁移类型,可以使用这个接口。

# cat /proc/pagetypeinfo
Page block order: 10
Pages per block:  1024

Free pages count per migrate type at order       0      1      2      3      4      5      6      7      8      9     10 //order 0-10
Node    0, zone   Normal, type    Unmovable      0      1    165      3      7      2      0      1      1      2      0
Node    0, zone   Normal, type      Movable   6297   2088   5741   2825   1354    592    152     51     15      2    212
Node    0, zone   Normal, type  Reclaimable      0      0      1      1      0      1      0      0      0      0      0
Node    0, zone   Normal, type          CMA      0      0      0      0      0      0      0      0      0      0      0
Node    0, zone   Normal, type   HighAtomic      0      0      0      0      0      0      0      0      0      0      0
Node    0, zone   Normal, type      Isolate      0      0      0      0      0      0      0      0      0      0      0

Number of blocks type     Unmovable      Movable  Reclaimable          CMA   HighAtomic      Isolate
Node 0, zone   Normal         1280         2058          100           36            0            0

Number of mixed blocks    Unmovable      Movable  Reclaimable          CMA   HighAtomic      Isolate
Node 0, zone   Normal            0            0            1            0            0            0

注:buddyinfo 和 pagetypeinfo 这两个文件是同时cat的。

打印内存分四部分,依次为:

static int pagetypeinfo_show(struct seq_file *m, void *arg) //mm/vmstat.c
{
    pg_data_t *pgdat = (pg_data_t *)arg;

    /* 第一部分 */
    seq_printf(m, "Page block order: %d\n", pageblock_order); //(MAX_ORDER-1)=10
    seq_printf(m, "Pages per block:  %lu\n", pageblock_nr_pages); //(1UL << pageblock_order)=1<<10
    /* 第二部分:打印所有zone每个order每种迁移类型空闲链表上###对应的内存块个数 */
    pagetypeinfo_showfree(m, pgdat);
    /* 第三部分:打印每种迁移类型对应pageblock的个数(应该包含已分配出去的) ##### */
    pagetypeinfo_showblockcount(m, pgdat);
    /* 第四部分: 打印包含其它类型页面的pageblock数量 */
    pagetypeinfo_showmixedcount(m, pgdat);
}

/* 第三部分数值 */
static void pagetypeinfo_showblockcount_print(struct seq_file *m, pg_data_t *pgdat, struct zone *zone) //vmstat.c
{
    unsigned long start_pfn = zone->zone_start_pfn, end_pfn = zone_end_pfn(zone);
    unsigned long count[MIGRATE_TYPES] = { 0, };

    /* 依次遍历所有的pageblock */
    for (pfn = start_pfn; pfn < end_pfn; pfn += pageblock_nr_pages) { //1k
        page = pfn_to_online_page(pfn);
        mtype = get_pageblock_migratetype(page);
        count[mtype]++;
    }

    /* Print counts */
    seq_printf(m, "Node %d, zone %8s ", pgdat->node_id, zone->name);
    for (mtype = 0; mtype < MIGRATE_TYPES; mtype++)
        seq_printf(m, "%12lu ", count[mtype]);
}

/* 第四部分数值 */
void pagetypeinfo_showmixedcount_print(struct seq_file *m, pg_data_t *pgdat, struct zone *zone)
{
    for (; pfn < end_pfn; ) {
        page = pfn_to_online_page(pfn);
        pageblock_mt = get_pageblock_migratetype(page);
        for (; pfn < block_end_pfn; pfn++) {
            page_owner = get_page_owner(page_ext);
            page_mt = gfpflags_to_migratetype(page_owner->gfp_mask);
            if (pageblock_mt != page_mt) {
                if (is_migrate_cma(pageblock_mt))
                    count[MIGRATE_MOVABLE]++;
                else
                    count[pageblock_mt]++;
        }
    }
}

注:由于 struct free_area 中只有一个 nr_free 记录此order大小的空闲内存块的个数,并没有记录此大小内存块每种迁移类型对应的内存块的个数,在cat时需要遍历链表实现,最大显示个数限制为10万个,超过后显示">100000",因为遍历耗时可能导致spin lock临界区过长。

TODO: 看第三第四部分具体代码


六、实验

1. 分配物理页地址转换实验

/* ---- mt1 ---- */

#include <linux/mm.h>
#include <linux/gfp.h>
#include <asm/pgtable.h>

struct mm_test_1 {
    unsigned long virt_addr;
    struct page *page;
};

struct mm_test_1 mt1;

static void mem_test_1_init(void)
{
    mt1.page = alloc_pages(GFP_KERNEL, g_debug_arg);
    if (!mt1.page) {
        pr_info("no memory!\n");
    }
    pr_info("pfn=0x%lx\n", page_to_pfn(mt1.page));
    pr_info("paddr=0x%llx\n", page_to_phys(mt1.page));
    pr_info("vaddr=0x%p\n", page_address(mt1.page));
    mt1.virt_addr = (unsigned long )page_to_virt(mt1.page);
    pr_info("vaddr=0x%lx\n", mt1.virt_addr); 
    
    pr_info("vmemmap=0x%p, mt1.page=0x%p\n", vmemmap, mt1.page);
    pr_info("mt1.page-vmemmap=%lu\n", (unsigned long)(mt1.page - vmemmap));
    pr_info("mt1.page->private=%d\n", mt1.page->private);
}

static void mem_test_1_exit(void)
{
    /* 分别以page指针和虚拟地址为参数 */
    free_pages(mt1.virt_addr, g_debug_arg);
    //__free_pages(mt1.page, g_debug_arg);
}

地址转换的三个宏 page_to_pfn() page_to_phys() page_to_virt() 打印出来的值,手动校验,都对应的上。多次编译,vmemmap的值是固定的。

 

标签:11,buddy,zone,pages,order,内存,初探,pfn,page
From: https://www.cnblogs.com/hellokitty2/p/18279826

相关文章

  • 诺森德塔防游戏启动故障:msvcp110.dll文件缺失的高效解决策略
    《诺森德塔防》是一部以二战为背景的“肉鸽塔防”游戏,拥有着极为火爆的战场表现,让你能充分感受到收割成片敌人的快感,同时在玩法及策略性上都有着突出表现,然而最近很多用户都遇到了启动故障:msvcp110.dll文件缺失的问题,下面一起来看看解决方法介绍吧!重新安装MicrosoftVisualC......
  • 深入理解 C++11 多线程编程:从入门到实践
    C++多线程编程是指使用C++提供的多线程库来并行执行代码块,从而提高程序的性能和响应能力。C++11标准引入了多线程支持,使得在C++中进行多线程编程变得更加容易和直观。以下是C++多线程编程的基本知识,并附有例子代码。多线程的基本概念线程(Thread):线程是进程中的一个执......
  • 11.优化算法之栈
    1.删除字符串中的所有相邻重复项可以用数组模拟栈结构 classSolution{publicStringremoveDuplicates(Strings){if(s.length()<=1){returns;}StringBufferret=newStringBuffer();for(inti=0;i<s......
  • 11、 Django-模型基础-models-ORM框架-管理器对象
     #概述django根据属性的类型确定以下信息当前选择的数据库支持字段的类型渲染管理表单时使用的默认htm1控件在管理站点最低限度的验证django会为表增加自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后则django不会再生成默认的主键列#属性命......
  • VMware安装Win11环境
    准备Win11的iso镜像下载链接:https://www.microsoft.com/zh-cn/software-download/windows11/?open_in_browser=true配置步骤步骤一——创建虚拟机1、点击创建新虚拟机2、使用典型模式3、选择镜像位置4、选择虚拟机存放位置5、输入密码,此密码可以随便写6、选择单个......
  • 0基础学C++ | 第11天 | 基础知识 | 引用
    目录引用的基本使用 引用的注意事项 引用做函数参数 引用做函数的返回值引用的本质引用的基本使用作用:引用就是给变量起别名,它不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。我的理解就是......
  • C++11中如何创建线程
    在C++11中,创建线程变得非常简单和标准化,因为C++11引入了线程库(<thread>)。这个库提供了std::thread类,使得创建和管理线程变得更加直接和方便。以下是如何在C++11中创建线程的基本步骤:包含线程库:首先,你需要包含<thread>头文件,以便使用std::thread。定义线程将要执行的函数或可......
  • win11添加开机自启动
    方法1win+R打开运行,输入shell:startup会打开一个文件夹将想要启动的程序快捷方式放进文件夹在设置里面搜索“启动”,可以看到开机启动项,确认已经打开。以上,针对不用管理员权限启动的程序,有效。方法2下面看需要管理员权限的:按Win+R,输入regedit,打开注册表编辑......
  • 不用虚拟机在Windows上安装Linux子系统(win11)
    打开终端输入以下命令查看是否支持安装systeminfo最底下是4个yes代表支持 在开始菜单输入如下搜索 打开拉到最底下,勾选这两个选项 按照提示重启电脑 打开终端输入以下命令会自动安装最新的Ubuntu发行版wsl--install可以通过如下命令查看其他版本wsl--list......
  • springboot校企对接实习管理系统 毕业设计-附源码11959
    摘 要校企合作实习是一种重要的实践教学模式,但是在实际的推行过程中,存在许多管理问题。其中包括远程指导困难、学生管理困难、校企信息沟通不畅等问题一直困扰着校方负责管理实习的教师们。随着互联网系统开发技术的发展,应用web技术开发B/s模式的实习管理系统,根据用户需......