# 内存管理的不同粒度

在内核中这个管理的核心单位是 **页(page)**，所以对内存的管理可以从大方向上分成两个步骤：

* 准确的划分出页
* 高效得使用好页

在前几章的内容中，我们已经一步步完成了第一个步骤的任务。尤其是在[寻找页结构体的位置](/kernel-exploring/nei-cun-guan-li/00-memory_a_bottom_up_view/00_page_allocator/03-sparsemem.md)一章我们已经看到了当前常用的sparsemem是如何关联一个物理地址(pfn)和一个页面(page struct)的。

然而我们如果仔细观察，在整个内存管理中除了页，为了高效管理内存还有不少其他粒度的管理单位。这次我们从粒度的角度来探究一下内存管理的层次，也顺便补充一下几个鲜为人知的内存管理单元。

按照层次，不考虑node和zone的概念，整个系统上的内存从下到上按照粒度划分可以有这么几个层次。

```
              +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 slub         | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
              +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                                            
 page         |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |                                            
              +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
 page_block   |       |       |       |       |       |       |       |       |
              +-------+-------+-------+-------+-------+-------+-------+-------+
 mem_section  |               |               |               |               |
              +---------------+---------------+---------------+---------------+
 memory_block |                               |                               |
              +-------------------------------+-------------------------------+
 memblock     |                                                               |
              +---------------------------------------------------------------+
 e820         |                                                               |
              +---------------------------------------------------------------+
```

从这张图中我们可以看到，为了管理好内存，内核开发者将整个内存分成了不同的层次不同的粒度来管理。其中page和slub已经有很多文章来解读，而e820, memblock和mem\_section在前面的章节中已有描述，本章就不在赘述。这里我们着重看看通常被忽略的两个粒度：

* memory\_block
* pageblock

究竟这两个粒度在系统中起到什么作用呢？让我们来一探究竟。

## memory\_block

这个东西呢说有用也有用，说没用呢也没用。因为大多数人平时也用不到。就我现在所知，它啊在内存热插拔的时候才会用到。但是呢他们代表了系统上所有的内存设备。

在系统上我们其实能看到这个东东，因为他们都在 /sys/devices/system/memory/ 这个目录下。

### 初始化流程

对于系统上所有的内存，我们都有对应的memory\_block设备。那这些设备是怎么生成的呢？我们来看一下初始化的代码吧。

```
    memory_dev_init(), called kernel_init_freeable->do_basic_setup->driver_init()
        block_sz = memory_block_size_bytes()
        sections_per_block = block_sz / MIN_MEMORY_BLOCK_SIZE
        subsys_system_register(&memory_subsys, memory_root_attr_groups)
        add_memory_block(nr), nr equals the start section number of memory_block
            init_memory_block(mem, base_memory_block_id(nr), MEM_ONLINE)
                mem->start_section_nr = block_id * sections_per_block
                mem->state = MEM_ONLINE
                mem->nid = NUMA_NO_NODE
                register_memory(mem), mem->dev.bus = &memory_subsys
```

怎么样，其实很简单吧。就是每block\_sz大小就会对应有一个memory\_block的设备。但是这个block\_sz的大小不是固定的，**最小的大小是一个section**。

嗯，貌似讲完了，但是实际上还没有。上面这段代码是在初始化时做的操作，但是在内存热插拔的时候还会有不同的操作。

### 热插拔何时创建设备

既然对所有的内存都会有这个一个设备，那么热插拔的时候也会生成这么一个设备。所以我们来看热插时的这个流程。

```
      add_memory()
          add_memory_resource()
              create_memory_block_devices()
                  init_memory_block()
```

是不是也挺简单的。别急，我再告诉你个东东 -- online。

内存热插进来后其实还不能被内核使用，准确的说是不能被buddy使用。此时还差一步，那就是online了。 （嗯。。。这个名字其实不是很好理解。）

那这个过程是如何触发的呢？ 有两种情况

* 默认直接上线
* 手动上线

默认直接上线的我们就不看了，来看这个手动上线的。 还记得刚才我们是在系统的那个目录下看到memory\_block设备的么？

对了，在 /sys/devices/system/memory/ 下。然后你找一个具体的设备，再进去就能看到一个文件**online**.

这个文件对应的内核操作是什么呢？还记得注册的subsys的变量么？对了就是 memory\_subsys。

```
static struct bus_type memory_subsys = {
	.name = MEMORY_CLASS_NAME,
	.dev_name = MEMORY_CLASS_NAME,
	.online = memory_subsys_online,
	.offline = memory_subsys_offline,
};
```

我把它老人家放在这里了，我想你能才到上线时触发到了谁吧～

## pageblock

对pageblock的使用小编暂时还不是特别理解，主要的用途之一和内存迁移相关。这里我们先从原理上理解pageblock的作用，至于用途等到小编后续学习了之后再来讲解。

### pageblock的位置

pageblock这个概念其实有点尴尬，因为它不像其他的内存粒度有一个非常明确的结构体来表达。对pageblock的描述只是一个挂在mem\_section结构体上的位图(bitmap)。

多说无益，来张图看看：

```
     mem_section
     +-----------------------------+
     |section_mem_map              |
     |   (unsigned long)           |
     |                             |
     +-----------------------------+
     |usage                        |
     |   (mem_section_usage *)     |
     |   +-------------------------+
     |   |subsection_map           |
     |   |    (bitmap)             |
     |   |                         |
     |   |                         |
     |   |                         |
     |   |pageblock_flags[0]       |    [(1UL << (PFN_SECTION_SHIFT - pageblock_order))]
     |   |    (unsigned long)   ---|--->+----+----+----+---...---+----+----+
     +---+-------------------------+    |    |    |    |         |    |    |
                                        |    |    |    |         |    |    |
                                        +----+----+----+---...---+----+----+
```

从上图中可以看到，在每个mem\_section结构体上用一个pageblock\_flags的位图，来表示对应物理内存的属性。

为了节省空间（小编猜的），并不是给每一个页分配对应的属性，而是每pageblock\_order个页共用一个pageblock属性。这个pageblock\_order有几种不同的可能性，其中一种就是buddy分配器上的最大页面值。

```
#define pageblock_order		(MAX_ORDER-1)
```

从这个角度看，pageblock的粒度就是 MAX\_ORDER个页。

### pageblock的定义

上面我们看到了pageblock是一个位图，那这个位图上究竟定义的是什么，每一位代表什么意义？

待小编画个草图给看官瞧一瞧：

```
    3     2     1     0      total NR_PAGEBLOCK_BITS = 4
   +-----+-----+-----+-----+
   |     |     |     |     |
   |     |     |     |     |
   +-----+-----+-----+-----+
      |     |     |     |
      |     |     |     +---
      |     |     |          \
      |     |     +---------   = enum migratetype
      |     |                /
      |     +---------------
      |
      +---------------------  migrate_skip
```

也就是每个pageblock\_order页面都有4bit的位图来表示其属性。其中最高位有点特殊，表示一个特殊含义。而后三位则是一个枚举类型migratetype。其可能的值定义为：

```
    enum migratetype {
    	MIGRATE_UNMOVABLE,
    	MIGRATE_MOVABLE,
    	MIGRATE_RECLAIMABLE,
    	MIGRATE_PCPTYPES,	/* the number of types on the pcp lists */
    	MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
    #ifdef CONFIG_CMA
    	MIGRATE_CMA,
    #endif
    #ifdef CONFIG_MEMORY_ISOLATION
    	MIGRATE_ISOLATE,	/* can't allocate from here */
    #endif
    	MIGRATE_TYPES
    };
```

嗯，是啥意思咱先不管了，至少看出来是怎么定义的。

在继续阅读代码之前，小编先出道题考一考大家。代码中有这么一段定义，大家能理解为什么要这么写么？

```
BUILD_BUG_ON(MIGRATE_TYPES > (1 << PB_migratetype_bits));
```

### pageblock的使用

说了这么一堆，定义了这么个位图，那究竟是怎么使用呢？嗯，其中的奥妙就要联系到buddy分配器了。在这里小编只讲解与pageblock相关的部分，其他的细节还请读者们自行网上搜索相关资料～

这其中的奥秘啊还要从free\_area说起，小编将free\_area中的相关部分摘取出来。

```
      +------------------------------+
      |free_area[MAX_ORDER]  0...11  |
      |   (struct free_area)         |
      |   +--------------------------+
      |   |free_list[MIGRATE_TYPES]  |
      |   |(struct list_head)        |
      +---+--------------------------+
```

所以我们之前看到的free\_area->free\_list其实不止一个链表，而是每个migrate\_type一个链表。当我们去分配内存时，不仅要看大小上的匹配，还要看migrate\_type的匹配。

当然，为了减少内存碎片，当我们找不到内存时，还可以在不同的migrate\_type上去搜索。只是这个搜索还是有一定规则的，也不是阿猫阿狗都能用不同类型的空闲内存。

小编在这里只给出一个方向，具体的代码就留给各位想要进一步学习的读者自行探索啦～

* find\_suitable\_fallback()
* fallbacks\[MIGRATE\_TYPES]


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://richardweiyang-2.gitbook.io/kernel-exploring/nei-cun-guan-li/00-memory_a_bottom_up_view/13-physical-layer-partition.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
