在上一节中,我们可以看到,在kernel/head.S中建立了临时页表swapper_pg_dir。
背景
由内核页表所提供的最终映射必须把0xc0000000开始的线性地址转化为从0开始的物理地址。
设计
需求:把线性地址0xc0000000-0xc37FFFFF映射到物理地址0x0-0x37FFFFF。
分析:
现在页目录项有1024项,第0、1、768和769项是指向真实页表。把线性地址0xc0000000-0xc37FFFFF映射到物理地址0x0-0x37FFFFF,也就是把页目录项中第768项到第991项,分配页表内容,并且把物理地址加权限写进页表中。
步骤:
a.找到页目录项,然后分配页表,把页目录项指向对应页表。
b.把物理地址和权限写进页表项中。
代码实现
a.1 找到对应页目录项地址
pgd_idx = pgd_index(PAGE_OFFSET);
pgd = pgd_base + pgd_idx;
pmd = one_md_table_init(pgd);
PAGE_OFFSET是0xc0000000,则pgd_index(PAGE_OFFSET)是0x300,pgd_base=swapper_pg_dir,所以pgd是swapper_pg_dir+0x300,采用两级页表,没有上次目录和中间目录,则pmd=pgd=swapper_pg_dir+0x300。
a.2 分配页表,把页表的物理地址写进页目录项中,并返回页表的虚拟地址
因为页表有2^10=1024项,每项4个字节,则 1024×4=PAGE_SIZE大小的内存。
static pte_t * __init one_page_table_init(pmd_t *pmd)
{
if (pmd_none(*pmd)) {
pte_t *page_table = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);
set_pmd(pmd, __pmd(__pa(page_table) | _PAGE_TABLE));
if (page_table != pte_offset_kernel(pmd, 0))
BUG();
return page_table;
}
return pte_offset_kernel(pmd, 0);
}
b 把物理地址和权限写进页表项中.
pte = one_page_table_init(pmd);
set_pte(pte, pfn_pte(pfn, PAGE_KERNEL));
从one_page_table返回页表的虚拟地址,通过set_pte(pte, pfn_pte(pfn, PAGE_KERNEL)),用相应的物理地址和权限填充页表的第1项(就是返回的页表的虚拟地址),通过循环填充页表中其他项。
建立低端内存的kernel_physical_mapping_init代码如下:
/*
* This maps the physical memory to kernel virtual address space, a total
* of max_low_pfn pages, by creating page tables starting from address
* PAGE_OFFSET.
*/
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++) {
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. */
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));
}
}
}
}
}
内存映射示意图
说明:
1)内核空间的线性地址0xc0000000-0xc37FFFFF映射到物理地址0x0-0x37FFFFF。
2)用户空间的线性地址0x0-0x3ff000映射到物理地址0x0-0x3ff000.
PSE的打开
在arch/i386/kernel/cpu/common.c中
264 cpuid(0x00000001, &tfms, &junk, &excap, &capability);
265 c->x86_capability[0] = capability;
140 static inline void cpuid(int op, int *eax, int *ebx, int *ecx, int *edx)
141 {
142 __asm__("cpuid"
143 : "=a" (*eax),
144 "=b" (*ebx),
145 "=c" (*ecx),
146 "=d" (*edx)
147 : "0" (op), "c"(0));
148 }
就是通过调用,cpuid汇编,然后把capabilty的值保存到edx中,然后c->x86_capability[0] 等于edx,里面有了包含CPU是否支持PSE。
PSE的打开与关闭
CR4中的PSE位默认是0.
打开PSE功能。
- 置位PDE中的PS
- 置位CR4的PSE
置位PDE中的PS位的动作在arch/i386/mm/init.c中
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;
}
set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE))可以看到置位PDE中的PS位。
置位CR4的PSE在pagetable_init中
342 if (cpu_has_pge) {
343 set_in_cr4(X86_CR4_PGE);
344 __PAGE_KERNEL |= _PAGE_GLOBAL;
345 __PAGE_KERNEL_EXEC |= _PAGE_GLOBAL;
346 }
PSE的关闭
1.
可以通过disable_pse=1,然后
在arch/i386/kernel/cpu/common.c的early_cpu_init中添加下面语句:
529 void __init early_cpu_init(void)
530 {
...................
540 early_cpu_detect();
541
542 if(disable_pse)
543 clear_bit(X86_FEATURE_PSE, boot_cpu_data.x86_capability);
544
545 #ifdef CONFIG_DEBUG_PAGEALLOC
546 /* pse is not compatible with on-the-fly unmapping,
547 * disable it even if the cpus claim to support it.
548 */
549 clear_bit(X86_FEATURE_PSE, boot_cpu_data.x86_capability);
550 disable_pse = 1;
551 #endif
.....
}
2.或者打开kernel_hacking----------->Page alloc debugging
这样就关闭了PSE
if (disable_pse)
clear_bit(X86_FEATURE_PSE, c->x86_capability);
这样清空了x86_capability中的86_FEATURE_PSE。CR4中的PSE默认是0,PDE也没置位PS。
MMU单元是按大页(4M)或者页(4K)转换线性地址,主要根据:
1)PDE中的PS
2)CR4的PSE是否打开
关于地址的一些说明
设置好CR3,然后硬件MMU单元会把线性地址转化为物理地址。
1)开启分页后,汇编还是c用的地址都是线性地址,都会经过MMU单元转化为物理地址。
2)CR3寄存器里,页目录,和页表中存放的都是物理地址,因为MMU会用到CR3页表,页必须都是物理地址。
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3 /* set the page table pointer.. */
所以:
pte_t *page_table = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);
set_pmd(pmd, __pmd(__pa(page_table) | _PAGE_TABLE));
注意:set_pmd中,pmd是虚拟地址,__pa(page_table)是物理地址。
问题:
根据框图,用户空间和内核空间的地址空间是多少?
内核的页表项,是768和769项?
这他们能寻址:
1100 0000 00 00 0000 0000 00000 0000 0000—1100 0000 00 11 1111 1111 1111 1111 1111
也就是0xC00 0000-0xC03 fffff
1100 0000 01 00 0000 0000 00000 0000 0000—1100 0000 01 11 1111 1111 1111 1111 1111
也就是0xC04 0000-0xC07f ffff
也就是:内核的地址0xC00 0000- 0xC03 ffff,映射到物理地址0-0x7f ffff,也是能寻址8M