一. 总体说明
1.1 页表的初始化都是在paging_init中
paging_initsetup_arch
--> paging_init
{
a.调用pagetable_init,建立页目录表(pgd=swapper_pg_dir=0xc0101000)
a.1 将小于896M的页目录表与物理地址一一映射
a.2 预留出最后32项用于高端地址映射.
注:最后的32项中间有两项被用了,现在还不知道为什么
b.将cr3设为刚刚建立的页目录表基地址
c.刷新tlb使页目录表生效
d.kmap_init
e.free_area_init_core中在物理地址16M=0xC1000000处,建立了mem_map用page去管理内存
e.1 刚开始时设置page_count=0
e.2 设置flag=reserved
e.3 初始化zone结构体,使mem_map中virtual都指向相应的虚拟地址
f.free_all_bootmem_core
}
二.代码分析
2.1 初始化页表
在arch/i386/mm/init.c中-->start_kernel-->setup_arch-->paging_init
-
void __init paging_init(void)
-
{
-
pagetable_init(); //2.1设置页目录表
-
//将swapper_pg_dir=0xc0101000,赋到cr3中,告诉cpu这个是页目录表的基地址
-
__asm__( "movl %%ecx,%%cr3\n" ::"c"(__pa(swapper_pg_dir)));
-
-
#if CONFIG_X86_PAE
-
/*
-
* We will bail out later - printk doesnt work right now so
-
* the user would just see a hanging kernel.
-
*/
-
if (cpu_has_pae)
-
set_in_cr4(X86_CR4_PAE);
-
#endif
-
//flush一下使修改后的页目录表生效
-
__flush_tlb_all();
-
-
#ifdef CONFIG_HIGHMEM
-
kmap_init(); //初始化几个变量kmap_vstart=0xffff5000,kmap_pte=0xc0003fd4,kmap_prot=0x163
-
#endif
-
//初始化三个管理区的size
-
{
-
unsigned long zones_size[MAX_NR_ZONES] = {0, 0, 0}; //MAX_NR_ZONES=3,DMA,NORMAL,HIGH
-
unsigned int max_dma, high, low;
-
-
max_dma = virt_to_phys((char *)MAX_DMA_ADDRESS) >> PAGE_SHIFT; //MAX_DMA_ADDRESS=16M,取物理地址的页帧=0x1000
-
low = max_low_pfn; //low=896M的页帧=0x38000
-
high = highend_pfn; //high=实际内存的页帧=我这儿给qemu了1G内存=0x3fffe
-
-
if (low < max_dma)
-
zones_size[ZONE_DMA] = low;
-
else {
-
zones_size[ZONE_DMA] = max_dma;
-
zones_size[ZONE_NORMAL] = low - max_dma;
-
#ifdef CONFIG_HIGHMEM
-
zones_size[ZONE_HIGHMEM] = high - low; //zones_size={0x1000, 0x37000, 0x7ffe},都是用size表示的
-
#endif
-
}
-
free_area_init(zones_size); //初始化各个管理区
-
}
-
return;
-
}
2.1.1 pagetable_init
linux-2.4.18/arch/i386/mm/init.c-->paging_init-->pagetable_init
-
static void __init pagetable_init (void)
-
{
-
unsigned long vaddr, end;
-
pgd_t *pgd, *pgd_base;
-
int i, j, k;
-
pmd_t *pmd;
-
pte_t *pte, *pte_base;
-
-
//max_low_pfn=0x38000,低端内存的最高地址(最大896M)的页帧
-
//max_low_pfn*PAGE_SIZE=0x38000000 转为虚地址end=0xf8000000
-
//看出实地址转虚地址__va=实地址+0xC0000000
-
end = (unsigned long)__va(max_low_pfn*PAGE_SIZE);
-
-
pgd_base = swapper_pg_dir; -->swapper_pg_dir=0xc0101000就是在kernel/head.S中的页目录表,当时只有4项
-
#if CONFIG_X86_PAE
-
for (i = 0; i < PTRS_PER_PGD; i++)
-
set_pgd(pgd_base + i, __pgd(1 + __pa(empty_zero_page)));
-
#endif
-
//查看虚地址0xC0000000在页目录表中处于第几项,算法:0xC0000000>>22=768
-
i = __pgd_offset(PAGE_OFFSET);
-
pgd = pgd_base + i; -->获取内核页目录表项的地址=0xc0101000+0xc00=0xc0101c00
-
//将ZONE_NORMAL部分的页目录表修改一下
-
for (; i < PTRS_PER_PGD; pgd++, i++) { //PTRS_PER_PGD=1024
-
vaddr = i*PGDIR_SIZE;
-
if (end && (vaddr >= end))
-
break;
-
#if CONFIG_X86_PAE
-
pmd = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE);
-
set_pgd(pgd, __pgd(__pa(pmd) + 0x1));
-
#else
-
pmd = (pmd_t *)pgd;
-
#endif
-
if (pmd != pmd_offset(pgd, 0))
-
BUG();
-
//我这儿cpu_has_pse=true,所以会将页目录表项768项-992项写为0x*****1e3,一个页目录表项就映射了4M的物理地址,没有页表项了
-
for (j = 0; j < PTRS_PER_PMD; pmd++, j++) {
-
vaddr = i*PGDIR_SIZE + j*PMD_SIZE;
-
if (end && (vaddr >= end))
-
break;
-
if (cpu_has_pse) {
-
unsigned long __pe;
-
set_in_cr4(X86_CR4_PSE);
-
boot_cpu_data.wp_works_ok = 1;
-
__pe = _KERNPG_TABLE + _PAGE_PSE + __pa(vaddr);
-
/* Make it "global" too if supported */
-
if (cpu_has_pge) {
-
set_in_cr4(X86_CR4_PGE);
-
__pe += _PAGE_GLOBAL;
-
}
-
set_pmd(pmd, __pmd(__pe)); //写页目录表的相应项
-
continue;
-
}
-
//下面这儿根本没有执行
-
pte_base = pte = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);
-
-
for (k = 0; k < PTRS_PER_PTE; pte++, k++) {
-
vaddr = i*PGDIR_SIZE + j*PMD_SIZE + k*PAGE_SIZE;
-
if (end && (vaddr >= end))
-
break;
-
*pte = mk_pte_phys(__pa(vaddr), PAGE_KERNEL);
-
}
-
set_pmd(pmd, __pmd(_KERNPG_TABLE + __pa(pte_base)));
-
if (pte_base != pte_offset(pmd, 0))
-
BUG();
-
-
}
-
}
-
-
//将ZONE_HIGHMEM部分的页目录表修改一下
-
//执行后vaddr=0xffc00000(物理地址1020M)(虚拟地址4092M)
-
//0xc0101ff0 : 0x00000000 0x00000000 0x00000000 0x00003063
-
vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
-
fixrange_init(vaddr, 0, pgd_base); -->0xc0101ffc处写入0x00003063
-
-
#if CONFIG_HIGHMEM
-
//执行后vaddr=0xfe000000(物理地址992M),end=996M 虚地址(4064M-4068M之间)
-
//执行后0xc0101fe0 : 0x00004063 0x00000000 0x00000000 0x00000000
-
vaddr = PKMAP_BASE;
-
fixrange_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base); -->0xc0101fe0处写入0x00004063
-
-
pgd = swapper_pg_dir + __pgd_offset(vaddr); //pgd=0xc0101fe0
-
pmd = pmd_offset(pgd, vaddr); //pmd=0xc0101fe0
-
pte = pte_offset(pmd, vaddr); //pte=0xc0004000
-
pkmap_page_table = pte; //pkmap_pate_talbe=0xc0004000
-
#endif
-
-
#if 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
-
}
2.1.1说明 ZONE_NORMAL执行完后的页目录表项如下所示
-
(gdb) x /1024wx 0xc0101000
-
0xc0101000 <swapper_pg_dir>: 0x00102027 0x00103007 0x00000000 0x00000000
-
0xc0101010 <swapper_pg_dir+16>: 0x00000000 0x00000000 0x00000000 0x00000000
-
...
-
0xc0101bf0 <swapper_pg_dir+3056>: 0x00000000 0x00000000 0x00000000 0x00000000 -->到此,在以上都是用户空间的页目录表
-
0xc0101c00 <swapper_pg_dir+3072>: 0x000001e3 0x004001e3 0x008001e3 0x00c001e3
-
....
-
0xc0101f70 <swapper_pg_dir+3952>: 0x370001e3 0x374001e3 0x378001e3 0x37c001e3 -->到此,在以上都是内核空间小于896M的页目录表
-
0xc0101f80 <swapper_pg_dir+3968>: 0x00000000 0x00000000 0x00000000 0x00000000
-
0xc0101f90 <swapper_pg_dir+3984>: 0x00000000 0x00000000 0x00000000 0x00000000
-
0xc0101fa0 <swapper_pg_dir+4000>: 0x00000000 0x00000000 0x00000000 0x00000000
-
0xc0101fb0 <swapper_pg_dir+4016>: 0x00000000 0x00000000 0x00000000 0x00000000
-
0xc0101fc0 <swapper_pg_dir+4032>: 0x00000000 0x00000000 0x00000000 0x00000000
-
0xc0101fd0 <swapper_pg_dir+4048>: 0x00000000 0x00000000 0x00000000 0x00000000
-
0xc0101fe0 <swapper_pg_dir+4064>: 0x00004063 0x00000000 0x00000000 0x00000000
-
0xc0101ff0 <swapper_pg_dir+4080>: 0x00000000 0x00000000 0x00000000 0x00003063 -->到此,留下了4*8=32项,32*4M=128M可能跟highmem有关,以后再补充
-
因为打开了ps位,所以cpu是按照一级映射来的,所以页目录表中存的直接就是物理地址,因为4M=0x400000,所以都是按4M累加的0x370001e3-->0x374001e3
-
其中0x00003063 是__end_of_fixed_addresses
-
0x00004063 是PKMAP_BASE的映射
用户空间的PTE个数=768项(0xc0101000 --0xc0101c00)-->768项PTE, 每个PTE映射4M内存,则768项PTE可以映射768*4M=3072M=3G内存
内核空间的PTE个数=224项(0xc0101c00 -- 0xc0101f80)-->
224项PTE, 每个PTE映射4M内存,则224项PTE可以映射224*4M=896M的内存
PTE=0x1E3= 111100011 = 1 1110 0011 --> PS=1 -->如果PS=1,这个页目录表项就指向4MB的物理页面,而不是像原来那样指向一个页表
高端地址=剩下256-224=32个PTE,32个PTE可以映射128M内存
2.1.2 高端地址页目录表的修改
//start=0xffc00000(物理地址1020M), end=0x0, pgd_base=0xc0101000
-
static void __init fixrange_init (unsigned long start, unsigned long end, pgd_t *pgd_base)
-
{
-
pgd_t *pgd;
-
pmd_t *pmd;
-
pte_t *pte;
-
int i, j;
-
unsigned long vaddr;
-
-
vaddr = start;
-
i = __pgd_offset(vaddr); //start在页目录表中的第几项,vaddr=0xffc00000,执行后i=0x3ff
-
j = __pmd_offset(vaddr); //这个有数据,但是假的,下面没有用
-
pgd = pgd_base + i; //start在页目录表中的偏移。执行后pgd=0xc0101ffc=0xc0101000+0x3ff*4
-
-
for ( ; (i < PTRS_PER_PGD) && (vaddr != end); pgd++, i++) {
-
#if CONFIG_X86_PAE
-
if (pgd_none(*pgd)) {
-
pmd = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE);
-
set_pgd(pgd, __pgd(__pa(pmd) + 0x1));
-
if (pmd != pmd_offset(pgd, 0))
-
printk("PAE BUG #02!\n");
-
}
-
pmd = pmd_offset(pgd, vaddr);
-
#else
-
pmd = (pmd_t *)pgd; //直接将pmd=pgd了,pmd不影响真正的页目录表的内容,只是逻辑上有用
-
#endif
-
for (; (j < PTRS_PER_PMD) && (vaddr != end); pmd++, j++) {
-
if (pmd_none(*pmd)) {
-
pte = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE); //内存管理区中是0x7-->0xF,对应的物理地址=0x00003000
-
set_pmd(pmd, __pmd(_KERNPG_TABLE + __pa(pte))); //将start在页目录表项中的内容改为0x00003063
-
if (pte != pte_offset(pmd, 0))
-
BUG();
-
}
-
vaddr += PMD_SIZE;
-
}
-
j = 0;
-
}
-
}
0x00003063 -->页表的物理地址0x3000 -->4(DIRTY) 2(ACCESSED) 2(RW) 1(PRESENT)
附录1.关于PMD
《深入理解Linux虚拟内存管理》
a.
include/asm/pgtable-2level.h中
-
static inline pmd_t * pmd_offset(pgd_t * dir, unsigned long address)
-
{
-
return (pmd_t *) dir;
-
}
从这里可以看出"PMD页面帧偏移量“根本就不存在,而不是0
b. 在include/asm/pgtable-3level.h中
-
#define pmd_offset(dir, address) ((pmd_t *) pgd_page(*(dir)) + __pmd_offset(address))
-
-
#define pgd_page(pgd) ((unsigned long) __va(pgd_val(pgd) & PAGE_MASK))
-
-
#define __pmd_offset(address) (((address) >> PMD_SHIFT) & (PTRS_PER_PMD-1))
kernel/head.S中只留了一个页目录表
虚拟地址0xC0000000在页目录表的第768项上,一个页目录表1024项
1024-768=256
256*4M=1024M -->所以内核用的虚地址只有1G -->0xC0000000-0xFFFFFFF
上图出自《64-ia-32-architectures-software-developer-manual.pdf》Vol3_ch4 Paging
阅读(1260) | 评论(0) | 转发(0) |