着手建立内核永久页表

来源:互联网 发布:mac摔了下屏幕不亮 编辑:程序博客网 时间:2024/05/21 11:22

5.2.3 着手建立内核永久页表

得到了总的页面数max_pfn和高端页面数highmem_pages之后,来到setup_arch947行,调用init_memory_mapping()函数来建立系统初始化阶段的临时分页体系,传入的参数意义代表从0~max_low_pfn对应的32位物理地址(低12位全为0,也就是页面对齐),在函数init_memory_mapping函数中先后调用下面的几个函数来设置内存相关数据(因为bootmem此时没有初始化)

find_early_table_space()

kernel_physical_mapping_init()

early_ioremap_page_table_range_init()

load_cr3()

reserve_early()

 

其中,首先find_early_table_space 所实现的功能是相当重要的:

 

  32static void __init find_early_table_space(unsigned long end, int use_pse,

  33                                          int use_gbpages)

  34{

  35        unsigned long puds, pmds, ptes, tables, start;

  36

  37        puds = (end + PUD_SIZE - 1) >> PUD_SHIFT;

  38        tables = roundup(puds * sizeof(pud_t), PAGE_SIZE);

  39

  40        if (use_gbpages) {

  41                unsigned long extra;

  42

  43                extra = end - ((end>>PUD_SHIFT) << PUD_SHIFT);

  44                pmds = (extra + PMD_SIZE - 1) >> PMD_SHIFT;

  45        } else

  46                pmds = (end + PMD_SIZE - 1) >> PMD_SHIFT;

  47

  48        tables += roundup(pmds * sizeof(pmd_t), PAGE_SIZE);

  49

  50        if (use_pse) {

  51                unsigned long extra;

  52

  53                extra = end - ((end>>PMD_SHIFT) << PMD_SHIFT);

  54#ifdef CONFIG_X86_32

  55                extra += PMD_SIZE;

  56#endif

  57                ptes = (extra + PAGE_SIZE - 1) >> PAGE_SHIFT;

  58        } else

  59                ptes = (end + PAGE_SIZE - 1) >> PAGE_SHIFT;

  60

  61        tables += roundup(ptes * sizeof(pte_t), PAGE_SIZE);

  62

  63#ifdef CONFIG_X86_32

  64        /* for fixmap */

  65        tables += roundup(__end_of_fixed_addresses * sizeof(pte_t), PAGE_SIZE);

  66#endif

  67

  68        /*

  69         * RED-PEN putting page tables only on node 0 could

  70         * cause a hotspot and fill up ZONE_DMA. The page tables

  71         * need roughly 0.5KB per GB.

  72         */

  73#ifdef CONFIG_X86_32

  74        start = 0x7000;

  75#else

  76        start = 0x8000;

  77#endif

  78        e820_table_start = find_e820_area(start, max_pfn_mapped<<PAGE_SHIFT,

  79                                        tables, PAGE_SIZE);

  80        if (e820_table_start == -1UL)

  81                panic("Cannot find space for the kernel page tables");

  82

  83        e820_table_start >>= PAGE_SHIFT;

  84        e820_table_end = e820_table_start;

  85        e820_table_top = e820_table_start + (tables >> PAGE_SHIFT);

  86

  87        printk(KERN_DEBUG "kernel direct mapping tables up to %lx @ %lx-%lx/n",

  88                end, e820_table_start << PAGE_SHIFT, e820_table_top << PAGE_SHIFT);

  89}

 

我们看到它确定了PUDPMD以及PTE、固定内存映射等所有的选项所使用的内存空间的大小;之后调用find_e820_area函数从e820.map[]数组中寻找到一块能够容纳所有页表项的内存段:

 

743u64 __init find_e820_area(u64 start, u64 end, u64 size, u64 align)

 744{

 745        int i;

 746

 747        for (i = 0; i < e820.nr_map; i++) {

 748                struct e820entry *ei = &e820.map[i];

 749                u64 addr;

 750                u64 ei_start, ei_last;

 751

 752                if (ei->type != E820_RAM)

 753                        continue;

 754

 755                ei_last = ei->addr + ei->size;

 756                ei_start = ei->addr;

 757                addr = find_early_area(ei_start, ei_last, start, end,

 758                                         size, align);

 759

 760                if (addr != -1ULL)

 761                        return addr;

 762        }

 763        return -1ULL;

 764}

 

find_e820_area中检测e820.map的每一个元素,这个元素代表的内存区必须是E820_RAM,然后调用find_early_area获得tables的首地址:

 

539u64 __init find_early_area(u64 ei_start, u64 ei_last, u64 start, u64 end,

 540                         u64 size, u64 align)

 541{

 542        u64 addr, last;

 543

 544        addr = round_up(ei_start, align);

 545        if (addr < start)

 546                addr = round_up(start, align);

 547        if (addr >= ei_last)

 548                goto out;

 549        while (bad_addr(&addr, size, align) && addr+size <= ei_last)

 550                ;

 551        last = addr + size;

 552        if (last > ei_last)

 553                goto out;

 554        if (last > end)

 555                goto out;

 556

 557        return addr;

 558

 559out:

 560        return -1ULL;

 561}

 

这个find_early_area函数我们要好好说道说道。早系统初始化时,什么内存管理器、内存模型这些东西都没有建立,那么获得内存的最低级函数就是这个find_early_area函数。它接收6个参数:ei_startei_last表示分配范围,我们看到这个范围不能超出某个e820.map[i]元素代表的范围;startend代表期望分配的范围;size为期望分配大小;align表示对齐方式。我们看到,如果addr >= ei_last,或者last > ei_last,或者last > end都会分配失败。这个条件看上去很苛刻,不过e820.map[]数组有那么多元素,总有一个元素会满足的,不然所有的Linux都无法运行了。

 

round_up以及还有一个双胞胎弟弟round_down的定义如下:

#define __round_mask(x, y) ((__typeof__(x))((y)-1))

#define round_up(x, y) ((((x)-1) | __round_mask(x, y))+1)

#define round_down(x, y) ((x) & ~__round_mask(x, y))

 

获得了获得页表tables的首地址之后,find_early_table_space就用它设置e820_table_starte820_table_ende820_table_top的值。这三个全局变量相当的重要,分别表示这个将要容纳所有内核页表的空间的起始、结束和顶部页对齐地址头20位(此时e820_table_starte820_table_end是相等的),在后面的页表初始化阶段将会使用到这三个变量。

 

看了这么多代码,很多同学可能有些迷糊了。为啥我们在arch/x86/kernel/head_32.S中已经初始化了分页环境,建立了一个临时页表了,为啥这里还要建一个呢?其实,这就是ULK-3书中提到的内核临时页表和最终内核页表的概念。我在博客“高端内存映射”http://blog.csdn.net/yunsongice/archive/2010/01/26/5258589.aspx中也提到过这一点。

 

大家好好回忆一下,当时建立的临时页表个数只取决于_end的位置,也就是把解压缩后的内核代码映射出来了。而这里,是要把所有可用的RAM(注意这里是包括了已经映射了的内核代码、页目录)以页为单位分成多个页,每个页一个比特,提供一个初始阶段内存的分配和释放管理平台。

 

目前我的电脑,在arch/x86/kernel/head_32.S里只是映射了大概4Mvmlinux代码。内核尺寸在4M左右(不压缩),一般需要连续映射3个页面表。现在要把所有RAM映射到内核空间。那么内核要根据e820物理内存的布局,也就是RAM的结点布局对多个结点及结点的管理区作初化,最终把除去内核之外所有剩余的页交给页框分配器,同时也完成了页框分配器的初始化。

 

原创粉丝点击