Linux内核页表的建立

本文详细介绍了Linux内核在X86平台上页表的建立过程,包括临时映射和最终映射阶段。在实模式下,通过startup_32()函数初始化临时页表,然后启用分页机制。最终,Linux内核利用硬件分页机制,如常规分页、扩展分页(PSE)和物理地址扩展(PAE),进行线性地址到物理地址的映射。此外,文章还探讨了Linux中四级分页模型的实现,以及如何根据硬件特性适配不同的分页模式。
摘要由CSDN通过智能技术生成

内核页表建立的两个阶段

对于理解这节需要先熟悉线性地址到物理地址的映射过程及X86平台的分页机制,可以参考《深入理解Linux内核》第二章(内存寻址)。
内核页表的建立有两个阶段:

  1. 第一阶段:内核映像刚装入内存后,CPU运行于实模式,此时分页功能没有开启。在开启分页功能之前,内核需要创建一个有限的地址空间,包括内核的代码段和数据段、初始页表和用于存放动态数据结构的共128KB大小的空间。我们把这个阶段成为临时内核页表。
  2. 第二阶段:内核充分利用剩余的RAM并适当的建立页表。

临时映射

由于Linux由BIOS加载后,起始阶段其实是运行在实模式,此时并没有开启分页机制。那Linux在开启分页机制之前需要先做哪些准备工作以支持分页机制?答案是页目录项及页表项,可以根据x86的硬件分页机制知道,此时的两级映射。

初始阶段内存的使用情况

一般来说,Linux内核安装在RAM中从物理地址0x0010 0000开始的地方,也就是说,从第二个MB开始。
为什么内核没有安装在RAM第一个MB开始的地方?因为PC体系结构有几个独特的地方必须考虑到。例如:

  • 页框0由BIOS使用,存放加电自检(Power-On Self-Test, POST)期间检查到的系统硬件配置。
  • 物理地址从0x000a 0000到0x000f ffff的范围通常留给BIOS例程,并且映ISA图形卡上的内部内存。
  • 第一个MB内的其它页框可能由特定计算机模型保留

对于内存初始阶段的前3MB的内存布局大致如下图所示,这个图我们可以通过查看System.map文件得到:
在这里插入图片描述

临时内核页表建立

临时页表由startup_32()函数(定义于arch/i386/kernel/head.S)初始化的,由于在这个阶段没有激活PAE,所以只是两级映射:页全局目录和页表。页全局目录放在swapper_pg_dir变量中,页表放在pg0变量及之后的内存中。所以建立的页表需要满足的映射内存大小为:最后一个页表的地址+128K。
临时内核页表需要映射的线性地址有哪些?由于此阶段需要使得实模式和保护模式都能对物理地址0x0000 0000到 "最后一个页表位置 + 128k"都寻址到。所以映射关系如下:
线性地址(0x0000 0000 到 “最后一个页表位置 + 128k”)=> 物理地址(0x0000 0000 到 “最后一个页表位置 + 128k”)
线性地址(0xc000 0000 到 0xc000 0000 +“最后一个页表位置 + 128k” )=> 物理地址(0x0000 0000 到 “最后一个页表位置 + 128k”)。

页目录项及页表填充过程:

/* page_pde_offset/4 = 0x300,正好为页目录的0x300项(0xc000 0000的前10位) 
 * 0x0000 0000 与 0xC000 0000在页目录项正好偏移0x300项 */
page_pde_offset = (__PAGE_OFFSET >> 20);
  /* 页表是放在pg0开始的内存处 */
	movl $(pg0 - __PAGE_OFFSET), %edi
	movl $(swapper_pg_dir - __PAGE_OFFSET), %edx
	movl $0x007, %eax			/* 0x007 = PRESENT+RW+USER */
10:
	leal 0x007(%edi),%ecx			/* Create PDE entry */
  /* 填充线性地址0x0000 0000开始的目录项 */
	movl %ecx,(%edx)			/* Store identity PDE entry */
	/* 填充线性地址0xc000 0000开始的目录项 */
	movl %ecx,page_pde_offset(%edx)		/* Store kernel PDE entry */
	addl $4,%edx
	/* 一个页表包含1024项 */
	movl $1024, %ecx
11:
  /* 填充页表项,一个页表项映射4k物理内存,所以每次加0x1000 */
	stosl
	addl $0x1000,%eax
	loop 11b
	/* End condition: we must map up to and including INIT_MAP_BEYOND_END */
	/* bytes beyond the end of our own page tables; the +0x007 is the attribute bits */
  /* INIT_MAP_BEYOND_END=128*1024, 确定建立的页表是否能够映射“最后一个页表地址” + 128K的内存
   * 如果不行继续建立映射关系 */
	leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp
	cmpl %ebp,%eax
	jb 10b

启用分页单元

/*
 * Enable paging
 */
	movl $swapper_pg_dir-__PAGE_OFFSET,%eax
	movl %eax,%cr3		/* set the page table pointer.. */
	movl %cr0,%eax
	orl $0x80000000,%eax
	movl %eax,%cr0		/* ..and set paging (PG) bit */

Linux内核页表的最终映射

建立完临时映射大概8M的内存空间后,Linux内核就运行在保护模式了,此时就可以开始进行一些内核的基本初始化工作了。

x86的硬件分页

如果要理清Linux里面是怎么建立页表的映射,我们需要首先清楚硬件的分页机制是怎样的,因为我们建立的页目录和页表等最终是需要MMU来使用的。在这之前我们需要知道三个基本模式:

  1. 常规分页:
    在常规分页中,32位的线性地址被分成3个域:
    Directory(目录): 最高10位
    Table(页表): 中间10位
    Offset(偏移量): 最低12位
    在这里插入图片描述

  2. 扩展分页(PSE):
    在扩展分页模式下,页框的大小不再是常规分页下的4KB,而是4MB. 在该模式下就可以把大段连续的线性地址转换成相应的物理地址,并且映射过程不需要页表,从而节省了建立页表所占用的内存,并且TLB也更容易命中。
    在这种模式下,32位线性地址分成两个字段进行物理地址的转换:
    Directory: 最高10位
    Offset: 其余22位

  3. 物理地址扩展(PAE)分页:
    PAE的推出是因为由于物理内存(RAM)需求增加导致的,之前的物理内存受限于地址总线只有32根,支持的最大物理内存大小为4G,如果处理器支持PAE机制,地址总线就可以扩展到36根,从而支持的最大物理内存为64G. 但是线性地址仍然为32位,最大为4G.
    在这种模式下又可以分为大尺寸页(2M)和常规尺寸页(4KB)两种模式:

    • 大尺寸页:
      32位线性地址按如下方式解释:
      PDPT: 由位30-31进行索引
      页目录: 由位21-29进行索引
      页框中的偏移(2M):由位0-20进行索引
    • 常规尺寸页:
      32位线性地址按如下方式解释:
      PDPT: 由位30-31进行索引
      页目录: 由位21-29进行索引
      页框中的偏移(2M):由位0-20进行索引

Linux中的分页

由于Linux需要支持多平台的可移植性,所以Linux中定义了四级分页模型,四种页表分别为:

  • 页全局目录(pgd)
  • 页上级目录(pud)
  • 页中间目录(pmd)
  • 页表(pte)

虽然Linux定义四级分页模型,但是最终针对不同平台及模式时,还是需要根据硬件的分页模式去适配的:

  • 常规分页模式:采用的是两级分页,用了pgd, pte
  • 扩展分页模式:采用的是一级映射,只用了pgd
  • 物理地址扩展分页模式:
    • 大页模式(2M):采用的是两级分页,用了pgd, pmd
    • 常规页模式(4KB):采用的是三级分页,用了pgd,pmd, pte

在这里我们不需要太纠结于Linux中页表之间的层次关系,比如觉得pgd一定要索引到pud,其实pud,pmd及pte只是Linux中对页表的一个抽象,只是一个命名而已,应该关注的是里面的内容,比如说常规分页模式下,pgd的目录项是初始化为pte的基地址及一些标志位,虽然没有pud,但是能找到下一级页表就行。下面我们从代码实现中就能看到:
函数的调用关系为:pagetable_init -> kernel_physical_mapping_init
pagtable_init():

static void __init pagetable_init (void)
{
	unsigned long vaddr;
	pgd_t *pgd_base = swapper_pg_dir;

#ifdef CONFIG_X86_PAE
	int i;
	/* Init entries of the first-level page table to the zero page */
	/* 在扩展模式下,pgd的前三项映射线性地址空间为0-3G,这为用户空间访问的地址范围,所以用一个空页对它进行初始化 */
	for (i = 0; i < PTRS_PER_PGD; i++)
		set_pgd(pgd_base + i, __pgd(__pa(empty_zero_page) | _PAGE_PRESENT));
#endif

	/* Enable PSE if available */
	if (cpu_has_pse) {
		set_in_cr4(X86_CR4_PSE);
	}

	/* Enable PGE if available */
	if (cpu_has_pge) {
		set_in_cr4(X86_CR4_PGE);
		__PAGE_KERNEL |= _PAGE_GLOBAL;
		__PAGE_KERNEL_EXEC |= _PAGE_GLOBAL;
	}

	kernel_physical_mapping_init(pgd_base);
	remap_numa_kva();

	/*
	 * Fixed mappings, only the page table structure has to be
	 * created - mappings will be set by set_fixmap():
	 */
	vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
	page_table_range_init(vaddr, 0, pgd_base);

	permanent_kmaps_init(pgd_base);

#ifdef CONFIG_X86_PAE
	/*
	 * Add low memory identity-mappings - SMP needs it when
	 * starting up on an AP from real-mode. In the non-PAE
	 * case we already have these mappings through head.S.
	 * All user-space mappings are explicitly cleared after
	 * SMP startup.
	 */
	pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];
#endif
}

页面的建立是在函数kernel_physical_mapping_init()中:

/*
 * 常规分页模式:采用的是两级分页,用了pgd, pte
 * 扩展分页模式:采用的是一级映射,只用了pgd
 * 物理地址扩展分页模式:
 *    大页模式(2M):采用的是两级分页,用了pgd, pmd
 *    常规页模式(4KB):采用的是三级分页,用了pgd,pmd, pte */
static void __init kernel_physical_mapping_init(pgd_t *pgd_base)
{
	unsigned long pfn;
	pgd_t *pgd;
	pmd_t *pmd;
	pte_t *pte;
	int pgd_idx, pmd_idx, pte_ofs;

	pgd_idx = pgd_index(PAGE_OFFSET);
	pgd = pgd_base + pgd_idx;
	pfn = 0;

	for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {
    /* 1, 如果PAE没有打开,pmd=pgd 
     * 2, 如果PAE打开,就会分配内存给到pmd,并把pmd的地址放到pgd的表项中 */
		pmd = one_md_table_init(pgd);
		if (pfn >= max_low_pfn)
			continue;
		for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++, pmd_idx++) {
			unsigned int address = pfn * PAGE_SIZE + PAGE_OFFSET;

			/* Map with big pages if possible, otherwise create normal page tables. */
      /* 1,当处于扩展分页(PSE)时就不会再有下一级的页表,而是直接把页框基地址放到pmd页表项中
       *     1.1 如果不是物理地址扩展分页机制(PAE)模式,那PTRS_PER_PTE为1024,所以一页的大小为1024*4K = 4M 
       *     1.2 如果是PAE模式,那PTRS_PER_PTE为512,所以一页的大小为512*4K = 2M 
       * 2, 当不处于扩展分页(PSE)时,就需要多建立一级页表pte */
			if (cpu_has_pse) {
				unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1;

				if (is_kernel_text(address) || is_kernel_text(address2))
					set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));
				else
					set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE));
				pfn += PTRS_PER_PTE;
			} else {
				pte = one_page_table_init(pmd);

				for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte++, pfn++, pte_ofs++) {
						if (is_kernel_text(address))
							set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));
						else
							set_pte(pte, pfn_pte(pfn, PAGE_KERNEL));
				}
			}
		}
	}
}

当建立完内核最终的线性映射之后,线性地址0xc0000000 - 0xc0000000+896M, 对应的物理地址就为0x00000000-0x00000000+896M,这就是所谓的线性映射。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值