linux 内核 内存管理 bootmem alloctor 的初始化

来源:互联网 发布:常微分方程 知乎 编辑:程序博客网 时间:2024/04/30 04:09

       首先说说bootmem alloctor存在的意义。在内核刚刚开启分页机制时,只是创建了很少的几页的映射(内核+堆栈+bitmap),并没有实现内存的管理模块,也就是此时的内核还不能比较随意的申请/释放内存,所以内核的功能受到了很大的限制。为了尽快改变这种状况,内核创建了一个临时的内存管理器 -- bootmem alloctor,但这个内存管理模块的功能十分有限,内核随后会建立更强大的内存管理机制。那么为什么不直接建立哪个所谓的完善的内存管理机制呢?因为bootmem alloctor简单!完善的内存管理器本身就需要内存空间,也就是在内存管理器之前就需要一个有效的内存管理机制,使内核在创建完善的内存管理器时就能非常容易的申请页面。

       bootmem allotor 使用位图(bitmap)管理内存,位图中的一位对应物理页面的一页,0代表空闲,1代表被占用。有一点很重要,在分页机制开启之前,bitmap所需要占用的内存空间已经申请好了,所以创建bitmap不需要另外进行内存页的操作。

PFN概念:

在阅读bootmem alloctor源码的过程中,经常遇到pfn,pfn是page frame number的缩写,内核把一个物理页看做一个page frame,一个物理页就是一个page frame,所以从0x0地址开始,给每个page frame一个编号,这个编号就是pfn,pfn从0开始。

// 页对齐
#define PFN_ALIGN(x)    (((unsigned long)(x) + (PAGE_SIZE - 1)) & PAGE_MASK)
// 获取x所代表的物理地址的后一个PFN值,即x如果属于page 0, 则返回1.
#define PFN_UP(x)   (((x) + PAGE_SIZE-1) >> PAGE_SHIFT)
// 获取x所代表的物理地址的前一个PFN值,即x如果属于page 1, 则返回0.
#define PFN_DOWN(x) ((x) >> PAGE_SHIFT)
// 返回PFN的值为x时,所对应物理页的起始地址。
#define PFN_PHYS(x) ((x) << PAGE_SHIFT)

以上四个是和PFN相关的宏定义,内核使用这4个宏完成PFN到物理地址之间的映射。

位操作:

位操作函数的头文件的位置:include/asm-x86/bitops_32.h。

设置某一位,并返回该位原来的值。nr为要设置的位,addr为位图的起始地址,该函数是原子操作。
static inline int test_and_set_bit(int nr, volatile unsigned long * addr)
{
    int oldbit;
    __asm__ __volatile__( LOCK_PREFIX
        "btsl %2,%1\n\tsbbl %0,%0"
        :"=r" (oldbit),"+m" (ADDR)
        :"Ir" (nr) : "memory");
    return oldbit;

}

清除某一位,并返回该位原来的值。nr为要清除的位,addr是位图的起始地址,该函数是原子操作。

static inline int test_and_clear_bit(int nr, volatile unsigned long * addr)
{
    int oldbit;
    __asm__ __volatile__( LOCK_PREFIX
        "btrl %2,%1\n\tsbbl %0,%0"
        :"=r" (oldbit),"+m" (ADDR)
        :"Ir" (nr) : "memory");
    return oldbit;

}
根据gcc对于内嵌汇编的解释,test_and_clear_bit可以基本解释为如下两句汇编:
btrl nr addr; 含义是把以addr为基址的第nr个bit设置为1,并把该位原来的值保存到eflags的carry位。
sbbl oldbit oldbit; 含义是oldbit <-- oldbit - (oldbit+carry),如果carry为1,则oldbit=0xffffffff,否则为0x0;
test_and_set_bit只是把btrl改成了btsl。

低端内存&&高端内存:

高端内存是指物理地址大于896M的内存。

初始化bootmem alloctor:

初始化bootmem alloctor是在start_kernel() --> setup_arch() --> setup_memory()中进行的,源文件为:arch/x86/kernel/setup_32.c。源码如下:(蓝色:注释。红色:源码。黑色:x86默认配置下不会被编译)
static unsigned long __init setup_memory(void)
{

   
/*
     * partially used pages are not usable - thus
     * we are rounding upwards:
     * init_pg_tables_end指向最后一个页表项,其后的位置没有数据,可用
     */

    min_low_pfn = PFN_UP(init_pg_tables_end);
    /*
     * 设置max_pfn,根据e820数组,找出最大的页面号,保存在max_pfn中;
     */

    find_max_pfn();
    /*
     * 找到低内存(<896M)中最大的页面号,所谓低内存就是低于896M的内存。
     */

    max_low_pfn = find_max_low_pfn();
#ifdef CONFIG_HIGHMEM

    /*
     * 如果定义了高端内存,highstart_pfn和highend_pfn都需要被初始化
     * 一下把这两个变量进行初始化.
     */

    highstart_pfn = highend_pfn = max_pfn;
    if (max_pfn > max_low_pfn) {
        highstart_pfn = max_low_pfn;    }
    printk(KERN_NOTICE "%ldMB HIGHMEM available.\n",
        pages_to_mb(highend_pfn - highstart_pfn));

    /* 一共有多少个物理页 */
    num_physpages = highend_pfn;

    /* 设置高内存起始地址(虚拟地址) */
    high_memory = (void *) __va(highstart_pfn * PAGE_SIZE - 1) + 1;
#else
    num_physpages = max_low_pfn;
    high_memory = (void *) __va(max_low_pfn * PAGE_SIZE - 1) + 1;
#endif
#ifdef CONFIG_FLATMEM
    max_mapnr = num_physpages;
#endif
    printk(KERN_NOTICE "%ldMB LOWMEM available.\n",
            pages_to_mb(max_low_pfn));
    setup_bootmem_allocator();
    return max_low_pfn;
}

setup_memory()调用setup_bootmem_allocator()函数之前,初始化了以下这些变量,这些变量在初始化bootmem alloctor时会用到。
min_low_pfn最小的可用pfnmax_low_pfn低内存中最大的pfnmax_pfn最大的可用pfnhighstart高端内存开始的pf高端内存开始的pf高端内存结束的pfn高端内存结束的pfn物理页框的数量(包括hole)物理页框的数量(包括hole)高端内存的起始地址(物理地址)

setup_bootmem_allocator()在arch/x86/kernel/setup_32.c文件中定义,源码如下:(蓝色:注释。红色:源码。黑色:x86默认配置下不会被编译)

void __init setup_bootmem_allocator(void)
{
    unsigned long bootmap_size;


   
/*
     * 初始化低内存中的可用的内存页,所有可用的页在bitmap中都置为1.
     * 返回:共置了bootmap_size(Byte)个字节为0;
     */

    bootmap_size = init_bootmem(min_low_pfn, max_low_pfn);

    /* 清除低内存所对应的bit  */
    register_bootmem_low_pages(max_low_pfn);

    /*
     * 内核占用内存+页表+bootmap占用内存设置为不可用
     * min_low_pfn代表内核+页表占用的内存页数
     */

    reserve_bootmem(__pa_symbol(_text), (PFN_PHYS(min_low_pfn) +
             bootmap_size + PAGE_SIZE-1) - __pa_symbol(_text));

    /*第一个页设置为不可用*/
    reserve_bootmem(0, PAGE_SIZE);

    /* reserve EBDA region, it's a 4K region */
    reserve_ebda_region();

    /* could be an AMD 768MPX chipset. Reserve a page  before VGA to prevent
       PCI prefetch into it (errata #56). Usually the page is reserved anyways,
       unless you have no PS/2 mouse plugged in. */

    if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD &&
        boot_cpu_data.x86 == 6)
         reserve_bootmem(0xa0000 - 4096, 4096);

#ifdef CONFIG_SMP
    /*
     * But first pinch a few for the stack/trampoline stuff
     * FIXME: Don't need the extra page at 4K, but need to fix
     * trampoline before removing it. (see the GDT stuff)
     */
    reserve_bootmem(PAGE_SIZE, PAGE_SIZE);
#endif
#ifdef CONFIG_ACPI_SLEEP
    /*
     * Reserve low memory region for sleep support.
     */

    acpi_reserve_bootmem();
#endif
#ifdef CONFIG_X86_FIND_SMP_CONFIG
    /*
     * Find and reserve possible boot-time SMP configuration:
     */
    find_smp_config();
#endif
    numa_kva_reserve();
#ifdef CONFIG_BLK_DEV_INITRD
    if (boot_params.hdr.type_of_loader && boot_params.hdr.ramdisk_image) {
        unsigned long ramdisk_image = boot_params.hdr.ramdisk_image;
        unsigned long ramdisk_size  = boot_params.hdr.ramdisk_size;
        unsigned long ramdisk_end   = ramdisk_image + ramdisk_size;
        unsigned long end_of_lowmem = max_low_pfn << PAGE_SHIFT;


        if (ramdisk_end <= end_of_lowmem) {
            reserve_bootmem(ramdisk_image, ramdisk_size);
            initrd_start = ramdisk_image + PAGE_OFFSET;
            initrd_end = initrd_start+ramdisk_size;
        } else {
            printk(KERN_ERR "initrd extends beyond end of memory "
                   "(0x%08lx > 0x%08lx)\ndisabling initrd\n",
                   ramdisk_end, end_of_lowmem);
            initrd_start = 0;
        }
    }
#endif
    reserve_crashkernel();
}

init_bootmem()中先确定bitmap存放的位置,bitmap从低端内存中第一个可用pfn所对应的页面的首地址作为bitmap的首地址,并把bitmap中所有的位都置为1(占用),这其中包括那些没有内存的地址空间。register_bootmem_low_pages()函数根据e820中保存的数据,把所有可用的低端内存页所对应bitmap中的值都置为0(空闲),而此时memory hole所对应的位还是1(占用)。
随后两次调用reserve_bootmem()函数,把内核+页表+bitmap所占用的内存置为1,又把第一页内存置为1。
然后调用reserve_xxx()函数,把相应的bitmap置为1(占用)。
至此,bootmem allocator已初始化完毕,内核可以通过alloc_bootmem_low_pages()或者bootmem提供的其他接口申请内存页,bootmem提供的接口在include/linux/bootmem.h中。




原创粉丝点击