arch/i386/mm/init.c
pmd_bad ....
文件的开始的一段注释中,介绍了BAD_PAGE, ZERO PAGE的作用.应该好好读
读.
前面已经说起过pmd_bad, 这只是一个例行检查. 但是BAD_PAGE则有另外的
作用.再内核内存耗尽的时候,现在的处理策略是将进程相应的pte写成指向此
BAD_PAGE. 注释中说,used for page faults,其实内存耗尽了,如何设置pte?
无论是不是在page fault的处理过程中,需要pte的时候内存耗尽,分配不出页面
就使用此BAD_PAGE. bad pmd 作用是一样的.注释提到,以前,内核内存耗尽就杀
掉此进程.
下面通过一个函数,分析以上种种....
pte_t *get_pte_slow(pmd_t *pmd, unsigned long offset)
{
unsigned long pte;
pte = (unsigned long) __get_free_page(GFP_KERNEL); if (pmd_none(*pmd)) { if (pte) {
clear_page((void *)pte);
set_pmd(pmd, __pmd(_PAGE_TABLE + __pa(pte)));
return (pte_t *)pte + offset;
} set_pmd(pmd, __pmd(_PAGE_TABLE + __pa(get_bad_pte_table())));
return NULL; }
free_page(pte); if (pmd_bad(*pmd)) { __handle_bad_pmd(pmd);
return NULL;
} return (pte_t *) pmd_page(*pmd) + offset;
}
此函数中已经祥加注释了,希望有疑问的仔细阅读.
首先要注意,内存分配失败后,返回的是NULL,注意get_bad_pte_table却为
页表提供了指向BAD_PAGE的pte.希望你阅读get_bad_pte_table的时候没有困难
然后注意,对于其他流程代劳的页面做了检查,pmd_bad,如果检查通不过返回也
是NULL,并且也是用BAD_PAGE填充pte,见__handle_bad_pmd,同样希望__handle_
bad_pmd你读起来轻松自如.
通过bad_pmd的检查保证页面可读写,拥有DIRTY,ACESS属性,这在最大程度
上保证了这个页面"似乎"一切正常可以认为存在.我们怀疑一切,做bad pmd检测
但是其实并"没有"bad pmd,起码,我们没有主动破坏过,良好的内核中应该永远没
有bad pmd. 在__handle_bad_pmd的时候输出了一个log信息
void __handle_bad_pmd(pmd_t *pmd)
{
pmd_ERROR(*pmd);
set_pmd(pmd, __pmd(_PAGE_TABLE + __pa(get_bad_pte_table())));
}
通过这一点,可以知道,如果有bad pmd就一定是有什么不对了!!!(fix me)
到这里,此文件的前面一部分,关于BAD_PTE相关的部分就不再介绍了,看看
cpu quick list的一些东西:
int do_check_pgt_cache(int low, int high)
{
int freed = 0;
if(pgtable_cache_size > high) {
do {
if(pgd_quicklist)
free_pgd_slow(get_pgd_fast()), freed++;
if(pmd_quicklist)
free_pmd_slow(get_pmd_fast()), freed++;
if(pte_quicklist)
free_pte_slow(get_pte_fast()), freed++;
} while(pgtable_cache_size > low);
}
return freed;
}
此函数在cpu idle 的时候从per cpu 的页面缓存链表上取下页面返还给内存
管理系统. slow,fast的区别也在与此.(其他情况也有调用此函数释放内存的).
下面的一个函数和HIGHMEM有关.在此之前,借此机会可以梳理一下内核对虚
拟地址的使用布局. 参考include/asm-i386/fixmap.h
内核在使用虚拟空间之前,先留了8k的空洞,从FIXADDR_TOP(0xffffe000UL)
开始使用.从高地址到低地址排列如下:
+------------------------------------------------------------------
| 8K空洞
+------------------------------------------------------------------
| FIXADDR_TOP(0xffffe000UL) (include/asm-i386/fixmap.h)
| fixed map(每项4k虚存,见FIXADDR_SIZE)
| {| FIX_APIC_BASE,
| FIX_IO_APIC_BASE_0,
| FIX_IO_APIC_BASE_END = FIX_IO_APIC_BASE_0 + MAX_IO_APICS-1
|
| FIX_CO_CPU,
| FIX_CO_APIC,
| FIX_LI_PCIA,
| FIX_LI_PCIB,
+--------------
#ifdef CONFIG_HIGHMEM /*为fix KMAP预留每cpu 8k的虚存,读写各4k*/
| FIX_KMAP_BEGIN,
| FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,
#endif
+--------------
| __end_of_fixed_addresses
| }
| FIXADDR_START (FIXADDR_TOP - FIXADDR_SIZE)
+--------------------------------------------------------------------
| VMALLOC_END (FIXADDR_START) (include/asm-i386/pgtable.h)
| +------------------
| | xxxxx: kmap 和 vmalloc 相互重叠,2.6已经修正
| | kmap 使用的4M虚存 (asm/highmem.h,LAST_PKMAP)
| | PKMAP_BASE (0xfe000000UL) (距离4G 32M)
| +------------------
| vmalloc 映射区
| VMALLOC_START (((unsigned long) high_memory + 2*VMALLOC_OFFSET-1)
| & \~(VMALLOC_OFFSET-1))
+--------------------------------------------------------------------
| 约 8M 空洞
+--------------------------------------------------------------------
| high_memory (见003___arch_i386_mm_ioremap.c 对此的分析)
| 内核已经映射了的物理页面 MAX 896M
| 3G
+--------------------------------------------------------------------
| resoved for app 0-3G
+--------------------------------------------------------------------
003___arch_i386_mm_ioremap.c 对high_memory进行分析的时候提到
VMALLOC_RESERVE (ULONG)(128<<20) arch/i386/kernel/setup.c. 从今天的
分析可知,这128M中一部分给了fix map,一部分是kmap,vmallc,还有一部分被
空洞占用. 注意2.4中kmap使用的虚存和vmalloc的顶端有重叠,2.6已经修正.
总结一下:
系统初始化的时候预留128M虚存,896M用于"直接"映射物理内存.直接映射
就是virt_to_phy可以工作,只差一个常量.见宏MAXMEM,函数setup_arch.这128
M内存一部分用于fix map(enum fixed_addresses),一部分用于kmap(PKMAP_BASE)
,然后用于vmalloc. kmap_atomic使用了fix map项FIX_KMAP_BEGIN提供可以在
中断环境使用的"kmap". HIGHMEM由kmap和kmap_atomic构成,但是vmalloc机制
也尽可能的使用HIGHMEM,只是内核要直接访问highmem的物理地址时可以使用
kmap,kmap_atomic.
003___arch_i386_mm_ioremap.c 分析__ioremap时提到
unsigned long kmap_vstart;
kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN);
kmap_pte = kmap_get_fixmap_pte(kmap_vstart);
kmap_prot = PAGE_KERNEL;
}
#endif /* CONFIG_HIGHMEM */
其实调用此函数之前已经使用了fixrange_init对三级页表进行了部分初始
化. 所谓部分,即,只有pte未设置,也就是说虚拟地址落实到物理地址的最后一
步还没有进行. fixrange_init也在此文件中,分析到这里阅读这个没有"逻辑"的
函数当然应该没有问题了.不再赘述.
set_pte_phys 逻辑亦是简单,注意__flush_tlb_one在cpu支持invlpg指令的
时候只刷新一个页面的映射.
pagetable_init 初始化内核页表,之后就可以废弃head.s中的临时表了.同时
部分初始化fix map, 并为HIGEMEM初始化Permanent kmap使用的4M虚存(1024项
pte,刚好一个页面大小).
static void __init pagetable_init (void){
.............
#if CONFIG_HIGHMEM
vaddr = PKMAP_BASE;
fixrange_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);
pgd = swapper_pg_dir + __pgd_offset(vaddr);
pmd = pmd_offset(pgd, vaddr);
pte = pte_offset(pmd, vaddr);
pkmap_page_table = pte;
#endif
#if CONFIG_X86_PAE
pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];
#endif
}
接下来是paging_init,调用上面的pagetable_init,初始化页表然后重新加
载cr3,flush tlb,然后使用kmap_init为kmap_automic使用的变量求值,.....
不算复杂,重要的是free_area_init,初始化了zone-buddy内存管理系统(设置
所有页面都是PG_reserved(free_all_bootmem,负责重置)).
test_wp_bit -->检查cpu是否支持写保护位,不支持就提示用户重新编译内
核.
接下来的重要函数就是mem_init:
1. 初始化变量high_memory, 参见以前说明.
2. 调用free_all_bootmem, 清除ram页的PG_reserved位,并"释放"页面到
buddy 系统.
3. boot mem 无法处理HIGHMEM,对highmem,mem_init自己清除PG_reserved位,
并"释放"页面到buddy 系统.
4. 打印一些统计信息,如果不是SMP,就清楚0-8M,3G-8M的恒同映射.(所有用
户空间pgd)
最后只说一下free_initmem . 释放.data.init段占用的内存. 顺便请注意
free_initrd_mem, 看到了initrd,^_^.
在整个系统启动过程中,关于内存的初始化如下:
setup.S->asmlinkage void __init start_kernel(void) (init/main.c)
|
+-->setup_arch ---> 处理e820内存报告
--> 关于内存的提示信息
---> 初始化bootmem (init_bootmem)
---> paging_init--+
+-------+
| +--> pagetable_init(含fix map,vmalloc init)
\ / +--> load cr3
. +--> kmap_init
. +--> free_area_init(zone-buddy初始化)
. --->smp,apic,roms等处理
+--> idt gate modules,kmem_cache_init
|
+--> mem_init -->free_all_bootmem buddy 得到页面控制权
+
+--> proc_root_init,fork_init, ipc,inode
+--> smp_init
+
+--> 创建kernel thread, init (init/main.c->函数init)
+--->do_basic_setup
---->init pci,mtrr,sysctl,mca....
---->filesystem_setup
---->mount_root (关注...)
---->......
+---> free_initmem
+---> 打开console
+--->execve("/sbin/init",argv_init,envp_init);
+--->execve("/etc/init",argv_init,envp_init);
+--->execve("/bin/init",argv_init,envp_init);
+--->execve("/bin/sh",argv_init,envp_init);
收工.