物理内存信息从固件或者设备树中进行解析并且存储在struct loongarchlist_mem_map
结构中,供后续物理内存管理等操作。
struct loongarchlist_mem_map {
u8 map_count;
struct loongson_mem_map {
u32 mem_type;
u64 mem_start;
u64 mem_size;
} __packed map[LOONGARCH_BOOT_MEM_MAP_MAX];
} __packed;
和大多数操作系统类似,kernel-travel 将内存分割为一个个页,页作为内存管理的最小单位。由于 loongarch 2k1000 开发板的物理内存是不连续的块,因此我们对这些内存块使用 DISCONTIGMEM 非连续内存模型分别进行管理。为了 kernel-travel 后期的扩展与优化(DMA等),我们采用 NUMA 体系架构,鉴于当前CPU核数限制,管理使用的是一个伪NUMA节点,一个单独的节点管理所有的内存。
pglist_data
结构中记录了一个 NUMA 点中的信息。
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
// struct zonelist node_zonelists[MAX_ZONELISTS];
int nr_zones;
struct page *node_mem_map;
unsigned long node_start_pfn;
unsigned long node_present_pages;
unsigned long node_spanned_pages;
int node_id;
struct task_struct *kswapd;
int kswapd_order;
enum zone_type kswapd_highest_zoneidx;
int kswapd_failures;
unsigned long totalreserve_pages;
unsigned long flags;
} pg_data_t;
使用全局变量 node_data 来管理所有的 NUMA 节点
struct pglist_data node_data[MAX_NUMNODES];
在每一个 NUMA 节点下我们使用 zone 管理不同类型的物理内存。
struct zone {
unsigned long _watermark[NR_WMARK];
unsigned long watermark_boost;
long low_reserved_mem[MAX_NR_ZONES];
int node;
struct pglist_data *zone_pgdat;
unsigned long zone_start_pfn;
atomic_long_t managed_pages;
unsigned long managed_pages; // for tmp use
unsigned long spanned_pages;
unsigned long present_pages;
const char *name;
struct bitmap *pageblock_flags;
spinlock_t lock;
// 伙伴系统的核心数据结构
struct free_area free_area[MAX_ORDER + 1];
bool initialized;
};
我们将 zone 分为三种类型:
- ZONE_DMA 用于 DMA 的物理内存分配
- ZONE_NORMAL 用于普通物理页的分配
- ZONE_MOVABLE 用于减少内存碎片,提高系统可用性的优化机制,将内存碎片合并为连续的大物理内存
每一个 zone 中使用伙伴系统来实现每个 zone 下的物理页的内存管理。
初始化 zone 结构体:
//初始化 zone 结构体的初始状态
static void __meminit zone_init_internals(struct zone *zone, enum zone_type idx, int nid, unsigned long remaining_pages);
对于 zone 的处理,提供了以下接口:
//计算并返回指定 NUMA 节点中一个内存区域的页数
static unsigned long __init zone_spanned_pages_in_node(int nid,unsigned long zone_type, unsigned long node_start_pfn, unsigned long node_end_pfn, unsigned long *zone_start_pfn,unsigned long *zone_end_pfn)
//计算在指定 NUMA 节点中某个范围内缺失的页面数量
unsigned long __init zone_absent_pages_in_node(int nid, unsigned long range_start_pfn, unsigned long range_end_pfn)
free_area 结构用于管理内存区域中的空闲页面,它通过链表组织不同迁移类型的空闲页面,并维护一个计数器来记录总的空闲页数。
struct free_area {
struct list_head free_list[MIGRATE_TYPES]; // 用于存储不同迁移类型的空闲页面链表。
unsigned long nr_free; // 记录该区域的自由页的总数。用于追踪该区域内总共有多少页处于空闲状态。
};
对于页面的类型我们将其分为以下三种:
- MIGRATE_UNMOVABLE:不可移动的内存页。不能被迁移到其他节点或区域
- MIGRATE_MOVABLE:可移动的内存页,可以在内存回收或迁移时移动到其他节点或区域
- MIGRATE_RECLAIMABLE:可回收的内存页,低内存条件下可以被回收
为了避免内存碎片化的问题,我们采取伙伴系统来减少页与页之间的内存碎片化。
伙伴系统核心数据结构:
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
当前结构的核心初始化函数
static void __init free_area_init_core(struct pglist_data *pg_data)
{
int nid = pg_data->node_id;
pgdat_init_internals(pg_data);
for (enum zone_type j = 0; j < MAX_NR_ZONES; j++) {
struct zone *zone = pg_data->node_zones + j;
unsigned long size = zone->spanned_pages;
unsigned long present_size = zone->present_pages;
unsigned long memmap_pages_size = calc_memmap_pages_size(size);
if(present_size > memmap_pages_size) {
present_size -= memmap_pages_size;
if(memmap_pages_size)
printk("[%s zone]: %lu pages used for memmap\n",
zone_names[j], memmap_pages_size);
else
printk("[%s zone]: %lu memmap pages exceeds freesize %lu\n",
zone_names[j], memmap_pages_size, present_size);
}
// 这里需要 DMA 的话还要减去为 DMA 保留的页
// ...
nr_kernel_pages += present_size;
nr_all_pages += present_size;
zone_init_internals(zone, j, nid, present_size);
if (!size)
continue;
init_currently_empty_zone(zone, zone->zone_start_pfn, size);
}
return;
}
给 free_list 中添加,删除页的函数实现:
static inline void del_page_from_free_list(struct page *page, struct zone *zone,
unsigned int order)
{
list_del(&page->buddy_list);
set_page_private(page, 0);
zone->free_area[order].nr_free--;
}
static inline void add_to_free_list_tail(struct page *page, struct zone *zone,
unsigned int order, int migratetype)
{
struct free_area *area = &zone->free_area[order];
list_add_tail(&page->buddy_list, &area->free_list[migratetype]);
area->nr_free++;
}
static inline void add_to_free_list(struct page *page, struct zone *zone,
unsigned int order, int migratetype)
{
struct free_area *area = &zone->free_area[order];
list_add(&page->buddy_list, &area->free_list[migratetype]);
area->nr_free++;
}
内核会为 NUMA 节点中的每个物理内存区域 zone 分配一个伙伴系统用于管理该物理内存区域 zone 里的空闲内存页,而伙伴系统的核心数据结构就封装在 struct zone 里。
伙伴系统所分配的物理内存页全部都是物理上连续的,并且只能分配 2 的整数幂个页,这里的整数幂在内核中称之为分配阶 order。
在我们调用物理内存分配接口时,均需要指定这个分配阶 order,意思是从伙伴系统申请多少个物理内存页,假设我们指定分配阶为 order,那么就会从伙伴系统中申请 2 的 order 次幂个物理内存页。
伙伴系统会将物理内存区域中的空闲内存根据分配阶 order 划分出不同尺寸的内存块,并将这些不同尺寸的内存块分别用一个双向链表组织起来。
比如:分配阶 order 为 0 时,对应的内存块就是一个 page。分配阶 order 为 1 时,对应的内存块就是 2 个 pages。依次类推,当分配阶 order 为 n 时,对应的内存块就是 2 的 order 次幂个 pages。
当请求的内存块大于当前可用的最大块时,伙伴系统会将大块分裂成两个较小的块,直到能满足请求大小为止。具体步骤如下:
- 查找合适的块:
从与请求大小最接近的较大块开始查找可用的内存块。
- 分裂操作:
如果没有找到合适大小的块,则从更大的块中分裂出两个较小的块,直到找到合适大小的块。每次分裂都会将较大的块分成两个相等的小块,称为“伙伴”。更新自由链表:将分裂出的块重新插入到对应大小的自由链表中。
- 分配内存:
将找到的合适块分配给请求方,并从自由链表中移除。
示例:
请求分配3页内存,而当前最大可用块为8页。将8页块分裂为两个4页块。将其中一个4页块分裂为两个2页块。再将其中一个2页块与剩下的1页块合并成3页块,满足请求。
从伙伴系统获取页的核心函数:
struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,
const struct alloc_context *ac)
{
struct zone * zone;
int i;
retry:
for_pglist_data_each_zone(zone, node_data) {
if (zone->name == zone_names[1]) {
struct page *page;
unsigned long mark;
mark = wmark_pages(zone, alloc_flags & ALLOC_WMARK_MASK);
if (!zone_watermark_fast(zone, order, mark,
ac->highest_zoneidx, alloc_flags,
gfp_mask)) {
// 如果不能快速分配,直接返回 NULL
return NULL;
}
try_this_node:
// 尝试从伙伴系统获取
// page = rmqueue(ac->preferred_zoneref->zone, zone, order,
// gfp_mask, alloc_flags, ac->migratetype);
page = rmqueue(NULL, zone, order,
gfp_mask, alloc_flags, MIGRATE_UNMOVABLE);
if(page) {
/*初始化当前 struct page*/
return page;
} else {
/*分配失败重试*/
goto retry;
}
}
}
return NULL;
}
struct page *__alloc_pages(gfp_t gfp, unsigned int order, int preferred_nid)
{
struct page *page;
unsigned int alloc_flags = ALLOC_WMARK_LOW;
gfp_t alloc_gfp; /* The gfp_t that was actually used for allocation */
struct alloc_context ac = { };
if (order > MAX_PAGE_ORDER)
return NULL;
/* gfp 逻辑处理*/
/*预先 alloc page*/
// if (!prepare_alloc_pages(gfp, order, preferred_nid, &alloc_gfp))
// return NULL;
/*第一次尝试分配*/
page = get_page_from_freelist(alloc_gfp, order, alloc_flags, &ac);
if (likely(page))
goto out;
/*slow path alloc*/
out:
return page;
}
查找伙伴页面中指定大小页面分配操作的核心函数:
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
int migratetype)
{
unsigned int current_order;
struct free_area *area;
struct page *page;
/* 从当前分配阶 order 开始在伙伴系统对应的 free_area[order] 里查找合适尺寸的内存块*/
for (current_order = order; current_order <= MAX_ORDER; ++current_order) {
area = &(zone->free_area[current_order]);
page = get_page_from_free_area(area, migratetype);
if (!page)
// 没有空闲页,继续向上一级寻找
continue;
// 从当前的 free_list 的链表中删除
del_page_from_free_list(page, zone,current_order);
// 更新到更低的 free_list 中
expand(zone, page, order, current_order, migratetype);
// 设置页面的迁移类型
page->index = migratetype;
return page;
}
// 内存分配失败返回 null
return NULL;
}
当释放内存块时,伙伴系统会尝试将释放的块与其“伙伴”块合并,形成更大的块。具体步骤如下:
- 查找伙伴块:
根据释放块的地址计算出其伙伴块的地址。检查伙伴块是否也在自由链表中。
- 合并操作:
如果伙伴块也在自由链表中,将两个块合并成一个更大的块。合并后的块继续尝试与其新的伙伴块合并。继续此过程,直到无法合并为止。
- 更新自由链表: 将合并后的块插入到对应大小的自由链表中。
示例:
释放一个2页块,其伙伴块也为2页且在自由链表中。将两个2页块合并成一个4页块。再检查4页块的伙伴块,继续尝试合并。
释放页面到伙伴系统中的核心函数,原理同上:
void __free_pages_ok(struct page *page, unsigned int order,
bool fpi_flags)
{
int migratetype;
unsigned long pfn = page_to_pfn(page);
struct zone *zone = page_zone(page);
free_one_page(zone, page, pfn, order, MIGRATE_UNMOVABLE, fpi_flags);
}
void free_one_page(struct zone *zone,
struct page *page, unsigned long pfn,
unsigned int order,
int migratetype, bool fpi_flags)
{
struct page *buddy;
unsigned long buddy_pfn = 0;
unsigned long combined_pfn;
bool to_tail;
ASSERT(zone_is_initialized(zone) == true);
while (order < MAX_PAGE_ORDER) {
buddy = find_buddy_page_pfn(page, pfn, order, &buddy_pfn);
if (!buddy)
// 不需要合并
goto done_merging;
del_page_from_free_list(buddy, zone, order);
combined_pfn = buddy_pfn & pfn;
page = page + (combined_pfn - pfn);
pfn = combined_pfn;
order++;
}
done_merging:
set_buddy_order(page, order);
if(fpi_flags == FPI_TO_TAIL)
to_tail = true;
else
to_tail = buddy_merge_likely(pfn, buddy_pfn, page, order);
if (to_tail)
add_to_free_list_tail(page, zone, order, migratetype);
else
add_to_free_list(page, zone, order, migratetype);
}
static inline unsigned long
__find_buddy_pfn(unsigned long page_pfn, unsigned int order)
{
return page_pfn ^ (1 << order);
}
static inline unsigned int
buddy_order(struct page *page)
{
return page->private;
}
static inline bool
page_is_buddy(struct page *page, struct page *buddy,
unsigned int order)
{
if (buddy_order(buddy) != order)
return false;
/*这里只管理 Normal,忽略*/
// if (page_zone_id(page) != page_zone_id(buddy))
// return false;
return true;
}