页面分配器测试:分配不同阶数的连续物理页

探索Linux内核中物理内存管理的奥秘

引言

在Linux内核中,页面分配器(Page Allocator)是内存管理的核心组件之一。它负责分配和释放物理内存页,支持从单个页面到多个连续页面的分配。本实验将深入探讨如何分配不同阶数(order)的连续物理页,并通过read操作查看分配的信息。

通过这个实验,您将学习:

页面分配器概述

Linux内核的页面分配器使用伙伴系统(Buddy System)来管理物理内存。伙伴系统将内存分成多个块,每个块的大小是2的阶数次幂。阶数(order)决定了分配块的大小:

阶数 (Order) 页面数量 大小 (字节, 4KB页)
014096
128192
2416384
3832768
41665536
532131072
664262144
7128524288
82561048576
95122097152
1010244194304

分配连续物理页时,高阶数的分配可能失败,因为系统可能没有足够的连续空闲内存。

驱动设计思路

我们将创建一个字符设备驱动,实现以下功能:

  1. 在模块初始化时,分配一系列不同阶数的连续物理页
  2. 通过read操作,向用户空间返回分配页面的信息,包括:
    • 阶数
    • 分配的页面数量
    • 物理地址
    • 虚拟地址
    • 分配状态(成功/失败)
  3. 在模块退出时,释放所有分配的页面
驱动架构图 用户空间 内核空间 硬件 系统调用 页面分配

关键代码实现

数据结构定义

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/mm.h>

#define MAX_ORDER 10
#define DEVICE_NAME "page_alloc_test"

struct page_allocation {
    unsigned int order;
    struct page *page;
    void *virt_addr;
    phys_addr_t phys_addr;
    bool allocated;
};

static struct page_allocation allocations[MAX_ORDER + 1];

分配不同阶数的页面

static int allocate_pages(void)
{
    int order;
    gfp_t gfp_mask = GFP_KERNEL | __GFP_ZERO;
    
    for (order = 0; order <= MAX_ORDER; order++) {
        allocations[order].order = order;
        allocations[order].page = alloc_pages(gfp_mask, order);
        
        if (!allocations[order].page) {
            pr_err("Failed to allocate order %d pages\n", order);
            allocations[order].allocated = false;
            continue;
        }
        
        allocations[order].allocated = true;
        allocations[order].virt_addr = page_address(allocations[order].page);
        allocations[order].phys_addr = page_to_phys(allocations[order].page);
        
        pr_info("Allocated order %d: %lu pages, virt %p, phys %pap\n",
                order, (1UL << order), 
                allocations[order].virt_addr, 
                &allocations[order].phys_addr);
    }
    
    return 0;
}

实现read操作

static ssize_t dev_read(struct file *filp, char __user *buf, 
                       size_t count, loff_t *f_pos)
{
    char output[1024];
    int len = 0;
    int order;
    
    if (*f_pos > 0)
        return 0;
    
    len += snprintf(output + len, sizeof(output) - len, 
                   "Order | Pages | Physical Address | Virtual Address | Status\n");
    len += snprintf(output + len, sizeof(output) - len, 
                   "------|-------|------------------|-----------------|--------\n");
    
    for (order = 0; order <= MAX_ORDER; order++) {
        if (allocations[order].allocated) {
            len += snprintf(output + len, sizeof(output) - len, 
                           "%-5d | %-5lu | %-16pap | %-15p | Success\n",
                           order, (1UL << order), 
                           &allocations[order].phys_addr,
                           allocations[order].virt_addr);
        } else {
            len += snprintf(output + len, sizeof(output) - len, 
                           "%-5d | %-5lu | %-16s | %-15s | Failed\n",
                           order, (1UL << order), "N/A", "N/A");
        }
    }
    
    if (copy_to_user(buf, output, len))
        return -EFAULT;
    
    *f_pos = len;
    return len;
}

文件操作结构体

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = dev_read,
};

测试方法

  1. 编译并加载内核模块
  2. 使用mknod创建字符设备文件
  3. 通过cat命令读取设备文件,查看分配信息:
    cat /dev/page_alloc_test
  4. 观察输出,验证不同阶数的页面分配情况

注意:在QEMU环境中测试时,可以通过调整虚拟机内存大小来观察不同内存压力下的分配行为。

预期输出示例

Order | Pages | Physical Address | Virtual Address | Status
------|-------|------------------|-----------------|--------
0     | 1     | 0x0000000012345000 | 0xffff888012345000 | Success
1     | 2     | 0x0000000012346000 | 0xffff888012346000 | Success
2     | 4     | 0x0000000012348000 | 0xffff888012348000 | Success
3     | 8     | 0x000000001234c000 | 0xffff88801234c000 | Success
4     | 16    | 0x0000000012354000 | 0xffff888012354000 | Success
5     | 32    | 0x0000000012364000 | 0xffff888012364000 | Success
6     | 64    | 0x0000000012384000 | 0xffff888012384000 | Success
7     | 128   | 0x00000000123c4000 | 0xffff8880123c4000 | Success
8     | 256   | 0x0000000012444000 | 0xffff888012444000 | Success
9     | 512   | 0x0000000012544000 | 0xffff888012544000 | Success
10    | 1024  | N/A               | N/A              | Failed

总结与扩展

通过本实验,我们实现了:

扩展思考: