着手建立内核永久页表

原创 2011年01月11日 23:07:00

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的结点布局对多个结点及结点的管理区作初化,最终把除去内核之外所有剩余的页交给页框分配器,同时也完成了页框分配器的初始化。

 

相关文章推荐

用Source Insight打开linux内核源代码

用Source Insight打开linux内核源代码 2008-01-09 19:06 Linux的内核源代码可以从很多途径得到。一般来讲,在安装的linux系统下,/usr/src/linux...

构建WDK驱动出现fatal error U1087: cannot have : and :: dependents for same target

原因:WDK在编译驱动时,是不允许源文件所在的路径(全路径)中包含空格的,如果你包含了空格,就会出现上述错误。         解决方法:把源文件放在一个没有空格的路径下。...

linux-3.2.36内核启动3-setup_arch中的内存初始化2(arm平台 分析建立页表)

再介绍pageing_init之前,我们了解几个定义 pte_t 页表项 pmd_t 页中间目录项 pud_t 页上级目录 pgd_t 页全局目录项 我的arm平台 #define PMD_SHIFT...

linux进程创建与守护;exec 进程内核页表建立

linux进程创建与守护;exec 进程内核页表建立

Linux0.11内核--启动代码分析setup.s 建立页目录和页表

 head.s开始pg_dir.................org 0x1000pg0: .org 0x2000pg1: .org 0x3000pg2: .org 0x4000pg3: .org ...

学习日志:内核临时页表的建立

背景:为什么要建立内核临时页表:当内核被解压到线性地址0x100000后,为了继续启动内核,即启动内核的第一进程即swapper进程,内核需要建立一张临时页表供其使用。   当内核从16位的实模式...
  • qwdzq
  • qwdzq
  • 2012年01月10日 16:32
  • 559

ARM架构内核启动分析-head.S(1.3、stext分析之内存临时页表建立)

1.2.4、创建临时页表: 对于创建临时页表,使用的是arm的L1主页表,L1主页表也称为段页表(section page table,说白了就是采用段式管理而不是页式管理),它将4GB的地址空间分成...

arm-linux内存管理学习笔记(2)-内核临时页表的建立

需要搞明白的是,linux系统上执行./a.out运行,其实并不是运行的a.out这个elf文件。内核对于要运行的elf文件会调用load_elf_binary进行解析,首先是根据elf文件的head...

arm的2级页表在Linux内核创建过程解析

系统DDR的基地址为0x0,内存为1GB,所以TTB的基地址为0x4000。下面要创建虚拟地址0xfe700000到物理地址0xffff0000之间的映射,映射大小为64KB,即16页。由于物理地址不...

内核页表图示

可能有错,更新中。。。 如果是 arm 256MB sdram at 3000 0000-3FFF FFFF 1.内核线性地址需要覆盖整个物理内存,这样才能达到他管理的目的,如某个进程页表映射...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:着手建立内核永久页表
举报原因:
原因补充:

(最多只允许输入30个字)