深入浅出内存管理-memblock

本文介绍了Linux内核启动阶段使用的memblock内存管理机制。memblock主要用于内核启动至通用内存管理初始化期间,采用简单的逆向最先适配策略,并基于静态数组实现。文章详细解释了memblock的数据结构、核心API及其实现原理。

memblock 介绍


memblock 内存管理机制主要用于Linux Kernel 启动阶段(kernel启动 -> kernel 通用内存管理初始化完成.) 或者可以认为free_initmem 为止. 在启动阶段, 内存分配器并不需要很复杂, memblock 是基于静态数组, 采用的逆向最先适配的分配策略.

memblock 数据结构


memblock

struct memblock {
    bool bottom_up;  /* is bottom up direction? */
    phys_addr_t current_limit;
    struct memblock_type memory;
    struct memblock_type reserved;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
    struct memblock_type physmem;
#endif
};

memblock 内存管理的核心数据结构

bottom_up 内存分配的方向

current_limit 内存分配最大限制值

memblock 的内存分为3类, memory,reserved, 和 physmem

memory 可用的内存的集合

reserved 已分配出去内存的集合

memblock_type

struct memblock_type {
    unsigned long cnt;  /* number of regions */
    unsigned long max;  /* size of the allocated array */
    phys_addr_t total_size; /* size of all regions */
    struct memblock_region *regions;
    char *name;
};

memblock_type 用于描述在当前的memblock中此类型的memory region的数量

memblock_region

struct memblock_region {
    phys_addr_t base;
    phys_addr_t size;
    unsigned long flags;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
    int nid;
#endif
};

memblock_region 用于描述此内存region中的基地址和大小

flags 定义于 include/linux/memblock.h

/* Definition of memblock flags. */
enum {
    MEMBLOCK_NONE       = 0x0,  /* No special request */
    MEMBLOCK_HOTPLUG    = 0x1,  /* hotpluggable region */
    MEMBLOCK_MIRROR     = 0x2,  /* mirrored region */
    MEMBLOCK_NOMAP      = 0x4,  /* don't add to kernel direct mapping */
};

memblock API


在指定的node和范围内寻找可用大小的内存

phys_addr_t memblock_find_in_range_node(phys_addr_t size, phys_addr_t align,
                    phys_addr_t start, phys_addr_t end,
                    int nid, ulong flags);
phys_addr_t memblock_find_in_range(phys_addr_t start, phys_addr_t end,
                   phys_addr_t size, phys_addr_t align);

内存添加和删除type为memory的memblock region

int memblock_add_node(phys_addr_t base, phys_addr_t size, int nid);
int memblock_add(phys_addr_t base, phys_addr_t size);
int memblock_remove(phys_addr_t base, phys_addr_t size);

memblock的内存分配和释放

phys_addr_t memblock_alloc_nid(phys_addr_t size, phys_addr_t align, int nid);
phys_addr_t memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align, int nid);
phys_addr_t memblock_alloc(phys_addr_t size, phys_addr_t align);
int memblock_free(phys_addr_t base, phys_addr_t size);
void memblock_allow_resize(void);
int memblock_reserve(phys_addr_t base, phys_addr_t size);
void memblock_trim_memory(phys_addr_t align);
bool memblock_overlaps_region(struct memblock_type *type,
                  phys_addr_t base, phys_addr_t size);
int memblock_mark_hotplug(phys_addr_t base, phys_addr_t size);
int memblock_clear_hotplug(phys_addr_t base, phys_addr_t size);
int memblock_mark_mirror(phys_addr_t base, phys_addr_t size);
int memblock_mark_nomap(phys_addr_t base, phys_addr_t size);
int memblock_clear_nomap(phys_addr_t base, phys_addr_t size);
ulong choose_memblock_flags(void);

memblock 实现


memblock_init

#define INIT_MEMBLOCK_REGIONS   128
#define INIT_PHYSMEM_REGIONS    4

static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS] __initdata_memblock;
#endif

struct memblock memblock __initdata_memblock = {
    .memory.regions     = memblock_memory_init_regions,
    .memory.cnt     = 1,    /* empty dummy entry */
    .memory.max     = INIT_MEMBLOCK_REGIONS,
    .memory.name        = "memory",

    .reserved.regions   = memblock_reserved_init_regions,
    .reserved.cnt       = 1,    /* empty dummy entry */
    .reserved.max       = INIT_MEMBLOCK_REGIONS,
    .reserved.name      = "reserved",

#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
    .physmem.regions    = memblock_physmem_init_regions,
    .physmem.cnt        = 1,    /* empty dummy entry */
    .physmem.max        = INIT_PHYSMEM_REGIONS,
    .physmem.name       = "physmem",
#endif

    .bottom_up      = false,
    .current_limit      = MEMBLOCK_ALLOC_ANYWHERE,
};

系统会初始化 memory 和 reserved 的 128 个 memblock_region

bottom_up = false 默认的分配方式是从上到下

此时memblock_region 是没有可用内存的, 初次添加可用内存是发生在setup_machine_fdt() 中, 它会解析device tree中的内存物理信息,然后调用函数memblock_add 添加到memblock_region中去.

current_limit 在 kernel的启动过程的 sanity_check_meminfo() 中设定

当kernel启动到此处, 已经可以使用memblock来进行内存分配,从而进行页表的建立.

memblock_add

int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
    phys_addr_t end = base + size - 1;

    memblock_dbg("memblock_add: [%pa-%pa] %pF\n",
             &base, &end, (void *)_RET_IP_);

    return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}

int __init_memblock memblock_add_range(struct memblock_type *type,
                phys_addr_t base, phys_addr_t size,
                int nid, unsigned long flags)
{
    bool insert = false;
    phys_addr_t obase = base;
    phys_addr_t end = base + memblock_cap_size(base, &size);
    int idx, nr_new;
    struct memblock_region *rgn;

    if (!size)
        return 0;

    第一次添加可用内存到memory类型的memblock_region 
    /* special case for empty array */
    if (type->regions[0].size == 0) {
        WARN_ON(type->cnt != 1 || type->total_size);
        type->regions[0].base = base;
        type->regions[0].size = size;
        type->regions[0].flags = flags;
        memblock_set_region_node(&type->regions[0], nid);
        type->total_size = size;
        return 0;
    }
repeat:
    所有要添加的memblock region 都需要跑2次才能最终插入
    第一次是判断是否需要插入,以及是否需要扩充存放memory region的数量大小
    第二次才是真正的添加操作
    base = obase;
    nr_new = 0;

    for_each_memblock_type(type, rgn) {
        phys_addr_t rbase = rgn->base;
        phys_addr_t rend = rbase + rgn->size;

        if (rbase >= end)
            break;
        找到需要输入的节点位置
        if (rend <= base)
            continue;

        将非重叠的部分插入类型为memory的memblock_region
        if (rbase > base) {
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
            WARN_ON(nid != memblock_get_region_node(rgn));
#endif
            WARN_ON(flags != rgn->flags);
            nr_new++;
            if (insert)
                memblock_insert_region(type, idx++, base,
                               rbase - base, nid,
                               flags);
        }
        /* area below @rend is dealt with, forget about it */
        base = min(rend, end);
    }

    插入剩余的部分 或者是 当前要添加的内存区域需要插入到memblock_region的尾部
    if (base < end) {
        nr_new++;
        if (insert)
            memblock_insert_region(type, idx, base, end - base,
                           nid, flags);
    }

    如果第一次执行检查发现要添加的内存已经全部重叠,则直接退出
    if (!nr_new)
        return 0;

    第一次执行检查是否需要调整内存区数组大小,第二次执行合并操作 
    if (!insert) {
        while (type->cnt + nr_new > type->max)
            if (memblock_double_array(type, obase, size) < 0)
                return -ENOMEM;
        insert = true;
        goto repeat;
    } else {
        检查是否需要做合并的动作
        memblock_merge_regions(type);
        return 0;
    }
}

memblock_alloc

static phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
                    phys_addr_t align, phys_addr_t start,
                    phys_addr_t end, int nid)
{
    phys_addr_t found;

    if (!align)
        align = SMP_CACHE_BYTES;

    在类型为memory的memblock_region中寻找合适的分配位置
    found = memblock_find_in_range_node(size, align, start, end, nid);
    将找到的内存保存到类型为reserved的memblock_region中,代表已经分配
    if (found && !memblock_reserve(found, size)) {
        /*
         * The min_count is set to 0 so that memblock allocations are
         * never reported as leaks.
         */
        检查是否发生了memleak
        kmemleak_alloc(__va(found), size, 0, 0);
        return found;
    }
    return 0;
}

memblock_alloc 实际就是调用 memblock_alloc_range_nid 来实现分配

memblock_find_in_range_node

phys_addr_t __init_memblock memblock_find_in_range_node(phys_addr_t size,
                    phys_addr_t align, phys_addr_t start,
                    phys_addr_t end, int nid)
{
    phys_addr_t kernel_end, ret;

    /* pump up @end */
    if (end == MEMBLOCK_ALLOC_ACCESSIBLE)
        end = memblock.current_limit;

    永远都不会分配第一个页面
    start = max_t(phys_addr_t, start, PAGE_SIZE);
    end = max(start, end);
    kernel_end = __pa_symbol(_end);

    判断分配的方向
    if (memblock_bottom_up() && end > kernel_end) {
        phys_addr_t bottom_up_start;

        /* make sure we will allocate above the kernel */
        bottom_up_start = max(start, kernel_end);

        /* ok, try bottom-up allocation first */
        ret = __memblock_find_range_bottom_up(bottom_up_start, end,
                              size, align, nid);
        if (ret)
            return ret;
    }

    默认使用的是从上到下的方式寻找可用的内存
    return __memblock_find_range_top_down(start, end, size, align, nid);
}

memblock_reserve

static int __init_memblock memblock_reserve_region(phys_addr_t base,
                           phys_addr_t size,
                           int nid,
                           unsigned long flags)
{
    struct memblock_type *_rgn = &memblock.reserved;

    memblock_dbg("memblock_reserve: [%#016llx-%#016llx] flags %#02lx %pF\n",
             (unsigned long long)base,
             (unsigned long long)base + size - 1,
             flags, (void *)_RET_IP_);

    return memblock_add_range(_rgn, base, size, nid, flags);
}

memblock_reserve 和 memblock_add 一样也是调用memblock_add_range 来添加,只不过对象从memory变成了memblock.reserved.

<think>我们正在讨论PulseAudio中的`pa_memblock_acquire`函数。这个函数用于获取一个内存块的访问权限,以便读取或修改其中的数据。在PulseAudio中,内存块(`pa_memblock`)是音频数据存储的基本单位,而`pa_memblock_acquire`则是用来安全地获取这些内存块的指针。 ### 关键点: 1. **函数原型**: ```c void *pa_memblock_acquire(pa_memblock* block); ``` 该函数返回一个指向内存块数据的指针。 2. **使用场景**: - 当需要读取或修改内存块中的音频数据时,必须先调用`pa_memblock_acquire`获取指针。 - 使用完毕后,必须调用`pa_memblock_release`释放锁。 3. **线程安全**: - 内部使用引用计数和互斥锁确保多线程安全。 - 每次`acquire`必须对应一次`release`,否则会导致内存泄漏或死锁。 ### 典型代码流程: ```c // 获取内存块指针 void *data = pa_memblock_acquire(chunk->memblock); // 计算有效数据长度(考虑偏移和长度) size_t length = pa_memblock_get_length(chunk->memblock) - chunk->index; if (length > chunk->length) length = chunk->length; // 处理音频数据(例如:单声道转四声道) // ... 这里进行实际的数据处理 ... // 释放内存块 pa_memblock_release(chunk->memblock); ``` ### 注意事项: 1. **内存块生命周期**:在`acquire`和`release`之间,内存块不会被释放。 2. **数据一致性**:多个线程同时`acquire`同一内存块时,修改操作需要外部同步。 3. **性能影响**:频繁的`acquire/release`会带来锁开销,应尽量减少操作次数。 ### 错误处理: - 如果内存块已被销毁,调用`pa_memblock_acquire`将返回`NULL`(但通常由调用者确保内存块有效)。 §§相关问题§§ 1. 在PulseAudio中,`pa_memblock_acquire`和`pa_memblock_release`的调用频率对性能有何影响? 2. 如何确保在多线程环境下安全地操作同一个`pa_memblock`? 3. 除了`pa_memblock_acquire`,还有哪些函数可以访问PulseAudio内存块的数据? 4. 当`pa_memblock_acquire`返回NULL时,可能的原因有哪些? 5. 在修改`pa_memblock`数据时,如何避免音频数据的失真或损坏?</think>在 PulseAudio 中,`pa_memblock_acquire(pchunk->memblock);` 是音频数据处理的关键操作,用于安全访问内存块中的音频数据。以下是核心解析: ### 函数作用与机制 ```c // 函数原型 void *pa_memblock_acquire(pa_memblock *b); ``` 1. **内存锁定**: - 增加内存块引用计数,防止处理期间被意外释放 - 返回指向原始音频数据的指针(`void*` 类型) 2. **使用流程**: ```c // 获取内存块指针 void *data = pa_memblock_acquire(pchunk->memblock); // 计算有效数据范围 size_t length = PA_MIN(pa_memblock_get_length(pchunk->memblock), pchunk->length); uint8_t *audio_data = (uint8_t*)data + pchunk->index; // 处理音频数据 (示例:单声道转四声道) for (size_t i = 0; i < length; i += sizeof(float)) { float sample = *((float*)(audio_data + i)); // 复制单声道样本到四个声道... } // 必须释放内存块 pa_memblock_release(pchunk->memblock); ``` ### 关键注意事项 1. **线程安全**: - 内部使用原子操作管理引用计数 - 允许多线程同时读取,但写入需要外部同步 2. **内存管理**: ```c // 获取内存块实际大小 size_t pa_memblock_get_length(pa_memblock *b); // 释放内存块引用 void pa_memblock_release(pa_memblock *b); ``` 3. **性能优化**: - 避免在音频回调中频繁 acquire/release - 批量处理数据减少锁操作 - 使用 `pa_memblock_is_writable()` 检查是否可修改 ### 典型错误处理 ```c void *data = pa_memblock_acquire(block); if (!data) { pa_log("Failed to acquire memblock: %s", pa_strerror(errno)); return PA_ERR_NO_MEMORY; } // 处理完成后必须释放 pa_memblock_release(block); ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值