📄 004_arch_i386_mm_init_c.html
字号:
<html lang="zh-CN" xmlns:gdoc=""> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"> <style type="text/css">/* default css */table { font-size: 1em; line-height: inherit;}div, address, ol, ul, li, option, select { margin-top: 0px; margin-bottom: 0px;}p { margin: 0px;}body { margin: 0px; padding: 0px; font-family: Verdana, sans-serif; font-size: 10pt; background-color: #ffffff;}h6 { font-size: 10pt }h5 { font-size: 11pt }h4 { font-size: 12pt }h3 { font-size: 13pt }h2 { font-size: 14pt }h1 { font-size: 16pt }blockquote {padding: 10px; border: 1px #DDD dashed }a img {border: 0}div.google_header, div.google_footer { position: relative; margin-top: 1em; margin-bottom: 1em;}/* end default css */ /* default print css */ @media print { body { padding: 0; margin: 0; } div.google_header, div.google_footer { display: block; min-height: 0; border: none; } div.google_header { flow: static(header); } /* used to insert page numbers */ div.google_header::before, div.google_footer::before { position: absolute; top: 0; } div.google_footer { flow: static(footer); } /* always consider this element at the start of the doc */ div#google_footer { flow: static(footer, start); } span.google_pagenumber { content: counter(page); } span.google_pagecount { content: counter(pages); } } @page { @top { content: flow(header); } @bottom { content: flow(footer); } } /* end default print css */ /* custom css *//* end custom css */ /* ui edited css */ body { font-family: Verdana; font-size: 10.0pt; line-height: normal; background-color: #ffffff; } .documentBG { background-color: #ffffff; } /* end ui edited css */</style> </head> <body revision="dcbsxfpf_45jb24hqd3:7"> <table align=center cellpadding=0 cellspacing=0 height=5716 width=768>
<tbody>
<tr>
<td height=5716 valign=top width=100%>
<pre>2005-10-24<br>arch/i386/mm/init.c <br> pmd_bad ....<br> <br> <br> 文件的开始的一段注释中,介绍了BAD_PAGE, ZERO PAGE的作用.应该好好读<br>读.<br> 前面已经说起过pmd_bad, 这只是一个例行检查. 但是BAD_PAGE则有另外的<br>作用.再内核内存耗尽的时候,现在的处理策略是将进程相应的pte写成指向此<br>BAD_PAGE. 注释中说,used for page faults,其实内存耗尽了,如何设置pte?<br>无论是不是在page fault的处理过程中,需要pte的时候内存耗尽,分配不出页面<br>就使用此BAD_PAGE. bad pmd 作用是一样的.注释提到,以前,内核内存耗尽就杀<br>掉此进程.<br> 下面通过一个函数,分析以上种种....<br>pte_t *get_pte_slow(pmd_t *pmd, unsigned long offset)<br>{<br> unsigned long pte;<br><br> pte = (unsigned long) __get_free_page(GFP_KERNEL);<br> //可能睡醒之后已经被其他流程设置好了,(注意我们使用的是pmd的指针)<br> if (pmd_none(*pmd)) {//还没有就我们来设置<br> if (pte) {<br> clear_page((void *)pte);<br> set_pmd(pmd, __pmd(_PAGE_TABLE + __pa(pte)));<br> return (pte_t *)pte + offset;<br> }<br> //这里就是内核内存耗尽了,那就就设置成BAD_PAGE<br> set_pmd(pmd, __pmd(_PAGE_TABLE + __pa(get_bad_pte_table())));<br> return NULL; //然后返回内存分配失败<br> }<br> free_page(pte);<br> //别人已经代劳了,我们就释放掉刚获取的页面<br> if (pmd_bad(*pmd)) {//做例行检查,保证页面属性可读写等<br> //检查没有通过就奇怪了!也恢复成BAD_PAGE<br> __handle_bad_pmd(pmd);<br> return NULL;<br> }<br> //一切正常,返回已经设置的pte<br> return (pte_t *) pmd_page(*pmd) + offset; <br>}<br><br> 此函数中已经祥加注释了,希望有疑问的仔细阅读.<br> 首先要注意,内存分配失败后,返回的是NULL,注意get_bad_pte_table却为<br>页表提供了指向BAD_PAGE的pte.希望你阅读get_bad_pte_table的时候没有困难<br>然后注意,对于其他流程代劳的页面做了检查,pmd_bad,如果检查通不过返回也<br>是NULL,并且也是用BAD_PAGE填充pte,见__handle_bad_pmd,同样希望__handle_<br>bad_pmd你读起来轻松自如.<br> 通过bad_pmd的检查保证页面可读写,拥有DIRTY,ACESS属性,这在最大程度<br>上保证了这个页面"似乎"一切正常可以认为存在.我们怀疑一切,做bad pmd检测<br>但是其实并"没有"bad pmd,起码,我们没有主动破坏过,良好的内核中应该永远没<br>有bad pmd. 在__handle_bad_pmd的时候输出了一个log信息<br>void __handle_bad_pmd(pmd_t *pmd)<br>{<br> pmd_ERROR(*pmd);<br> set_pmd(pmd, __pmd(_PAGE_TABLE + __pa(get_bad_pte_table())));<br>}<br><br> 通过这一点,可以知道,如果有bad pmd就一定是有什么不对了!!!(fix me)<br> <br> 到这里,此文件的前面一部分,关于BAD_PTE相关的部分就不再介绍了,看看<br>cpu quick list的一些东西:<br>int do_check_pgt_cache(int low, int high)<br>{<br> int freed = 0;<br> if(pgtable_cache_size > high) {<br> do {<br> if(pgd_quicklist)<br> free_pgd_slow(get_pgd_fast()), freed++;<br> if(pmd_quicklist)<br> free_pmd_slow(get_pmd_fast()), freed++;<br> if(pte_quicklist)<br> free_pte_slow(get_pte_fast()), freed++;<br> } while(pgtable_cache_size > low);<br> }<br> return freed;<br>}<br> 此函数在cpu idle 的时候从per cpu 的页面缓存链表上取下页面返还给内存<br>管理系统. slow,fast的区别也在与此.(其他情况也有调用此函数释放内存的).<br><br> <br> 下面的一个函数和HIGHMEM有关.在此之前,借此机会可以梳理一下内核对虚<br>拟地址的使用布局. 参考include/asm-i386/fixmap.h<br><br> 内核在使用虚拟空间之前,先留了8k的空洞,从FIXADDR_TOP(0xffffe000UL)<br>开始使用.从高地址到低地址排列如下:<br>+------------------------------------------------------------------ <br>| <span style=COLOR:#3333ff>8K空洞</span><br>+------------------------------------------------------------------<br>| <span style=COLOR:#ff0000>FIXADDR_TOP</span>(0xffffe000UL) (include/asm-i386/fixmap.h)<br>| <span style=COLOR:#ff0000>fixed map</span>(每项4k虚存,见FIXADDR_SIZE) <br>| { //fix map 内容 (enum fixed_addresses)<br>| FIX_APIC_BASE, <br>| FIX_IO_APIC_BASE_0,<br>| FIX_IO_APIC_BASE_END = FIX_IO_APIC_BASE_0 + MAX_IO_APICS-1<br>| <br>| FIX_CO_CPU, /* Cobalt timer */<br>| FIX_CO_APIC, /* Cobalt APIC Redirection Table */ <br>| FIX_LI_PCIA, /* Lithium PCI Bridge A */<br>| FIX_LI_PCIB, /* Lithium PCI Bridge B */<br>+--------------<br>#ifdef CONFIG_HIGHMEM /*为fix KMAP预留每cpu 8k的虚存,读写各4k*/<br>| <span style=COLOR:#ff0000>FIX_KMAP_BEGIN</span>, /* 主要用于kmap_atomic*/<br>| FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,<br>#endif<br>+--------------<br>| __end_of_fixed_addresses<br>| }<br>| FIXADDR_START (FIXADDR_TOP - FIXADDR_SIZE)<br>+--------------------------------------------------------------------<br>| <span style=COLOR:#ff0000>VMALLOC_END</span> (FIXADDR_START) (include/asm-i386/pgtable.h)<br>| +------------------<br>| | xxxxx: kmap 和 vmalloc 相互重叠,2.6已经修正<br>| | kmap 使用的4M虚存 (asm/highmem.h,LAST_PKMAP)<br>| | PKMAP_BASE (0xfe000000UL) (距离4G 32M)<br>| +------------------<br>| vmalloc 映射区<br>| <span style=COLOR:#ff0000>VMALLOC_START</span> (((unsigned long) high_memory + 2*VMALLOC_OFFSET-1)<br>| & \~(VMALLOC_OFFSET-1)) /*down align 8M */<br>+--------------------------------------------------------------------<br>| 约 8M 空洞<br>+--------------------------------------------------------------------<br>| high_memory (见003___arch_i386_mm_ioremap.c 对此的分析) <br>| 内核已经映射了的物理页面 MAX 896M<br>| 3G<br>+--------------------------------------------------------------------<br>| resoved for app 0-3G<br>+--------------------------------------------------------------------<br> <br> 003___arch_i386_mm_ioremap.c 对high_memory进行分析的时候提到<br>VMALLOC_RESERVE (ULONG)(128<<20) arch/i386/kernel/setup.c. 从今天的<br>分析可知,这128M中一部分给了fix map,一部分是kmap,vmallc,还有一部分被<br>空洞占用. 注意2.4中kmap使用的虚存和vmalloc的顶端有重叠,2.6已经修正.<br> 总结一下:<br> 系统初始化的时候预留128M虚存,896M用于"直接"映射物理内存.直接映射<br>就是virt_to_phy可以工作,只差一个常量.见宏MAXMEM,函数setup_arch.这128<br>M内存一部分用于fix map(enum fixed_addresses),一部分用于kmap(PKMAP_BASE)<br>,然后用于vmalloc. kmap_atomic使用了fix map项FIX_KMAP_BEGIN提供可以在<br>中断环境使用的"kmap". HIGHMEM由kmap和kmap_atomic构成,但是vmalloc机制<br>也尽可能的使用HIGHMEM,只是内核要直接访问highmem的物理地址时可以使用<br>kmap,kmap_atomic.<br> 003___arch_i386_mm_ioremap.c 分析__ioremap时提到<br>/*<br> * 映射指定的物理地址的一段内存到内核的虚拟地址.<br> * 内核如果需要直接访问high address的内存则也需要这个函数先映射一下.<br> *<br> 有些不够准确,通过这次分析,概念上更清楚些了.宏vmlloc就是分配的<br>HIGHMEM,如果非得使用__ioremap,也是未尝不可....<br><br> 003___arch_i386_mm_ioremap.c中还提到了page->virtual,这里借机补充<br>说明一下.在初始化page的mem_map时候,free_area_init_core对非ZONE_HIGHMEM<br>的page->virtual进行了初始化.而ZONE_HIGHMEM则在需要的时候用kmap来映射<br>页面并初始化page->virtual,或者使用vmalloc. 所以,在任何时候page->virtual<br>都可代表页面在内核的虚拟地址(只要非0). <br> 以后分析mm/highmem.c的时候可以轻松多啦.<br> <br> 回到正题, init.c看函数 <br>#if CONFIG_HIGHMEM<br>pte_t *kmap_pte;<br>pgprot_t kmap_prot;<br>#define kmap_get_fixmap_pte(vaddr) \<br> pte_offset(pmd_offset(pgd_offset_k(vaddr), (vaddr)), (vaddr))<br>void __init kmap_init(void)<br>{ /*为HIGHMEM kmap_automic 使用的两个变量求值,最多NR_CPU*2项pte*/<br> unsigned long kmap_vstart;<br><br> /* cache the first kmap pte */<br> kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN);<br> /*Fix KAMP 起始虚拟地址*/<br> kmap_pte = kmap_get_fixmap_pte(kmap_vstart);<br> /*顺着页表找到起始pte*/<br> <br> kmap_prot = PAGE_KERNEL;<br>}<br>#endif /* CONFIG_HIGHMEM */ <br> 其实调用此函数之前已经使用了fixrange_init对三级页表进行了部分初始<br>化. 所谓部分,即,只有pte未设置,也就是说虚拟地址落实到物理地址的最后一<br>步还没有进行. fixrange_init也在此文件中,分析到这里阅读这个没有"逻辑"的<br>函数当然应该没有问题了.不再赘述.<br> set_pte_phys 逻辑亦是简单,注意__flush_tlb_one在cpu支持invlpg指令的<br>时候只刷新一个页面的映射.<br><br> pagetable_init 初始化内核页表,之后就可以废弃head.s中的临时表了.同时<br>部分初始化fix map, 并为HIGEMEM初始化Permanent kmap使用的4M虚存(1024项<br>pte,刚好一个页面大小).<br><br>static void __init pagetable_init (void){<br>.............<br>#if CONFIG_HIGHMEM<br>/*<br> * Permanent kmaps: (即,kmap 使用的虚存,2.4中和vmallc使用的虚存有重叠)<br> */<br> vaddr = PKMAP_BASE;<br> fixrange_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);<br><br> pgd = swapper_pg_dir + __pgd_offset(vaddr);<br> pmd = pmd_offset(pgd, vaddr);<br> pte = pte_offset(pmd, vaddr);<br> pkmap_page_table = pte;<br>#endif<br>#if CONFIG_X86_PAE<br> /*<br> * Add low memory identity-mappings - SMP needs it when<br> * starting up on an AP from real-mode. In the non-PAE<br> * case we already have these mappings through head.S.<br> * All user-space mappings are explicitly cleared after<br> * SMP startup.<br> */<br> /*0-4M , 3G-4M的恒同映射,smp完成后用zap_low_mappings 清除*/<br> pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];<br>#endif<br><br>} <br> <br> 接下来是paging_init,调用上面的pagetable_init,初始化页表然后重新加<br>载cr3,flush tlb,然后使用kmap_init为kmap_automic使用的变量求值,.....<br>不算复杂,重要的是free_area_init,初始化了zone-buddy内存管理系统(设置<br>所有页面都是PG_reserved(free_all_bootmem,负责重置)).<br> <br> test_wp_bit -->检查cpu是否支持写保护位,不支持就提示用户重新编译内<br>核.<br><br> 接下来的重要函数就是mem_init:<br> 1. 初始化变量high_memory, 参见以前说明.<br> 2. 调用free_all_bootmem, 清除ram页的PG_reserved位,并"释放"页面到<br> buddy 系统.<br> 3. boot mem 无法处理HIGHMEM,对highmem,mem_init自己清除PG_reserved位,<br> 并"释放"页面到buddy 系统.<br> 4. 打印一些统计信息,如果不是SMP,就清楚0-8M,3G-8M的恒同映射.(所有用<br> 户空间pgd)<br> <br> 最后只说一下free_initmem . 释放.data.init段占用的内存. 顺便请注意<br>free_initrd_mem, 看到了initrd,^_^.<br><br> 在整个系统启动过程中,关于内存的初始化如下:<br> <br> setup.S->asmlinkage void __init start_kernel(void) (init/main.c)<br> |<br> +-->setup_arch ---> 处理e820内存报告<br> --> 关于内存的提示信息<br> ---> 初始化bootmem (init_bootmem)<br> ---> paging_init--+<br> +-------+<br> | +--> pagetable_init(含fix map,vmalloc init)<br> \ / +--> load cr3<br> . +--> kmap_init <br> . +--> free_area_init(zone-buddy初始化)<br> . --->smp,apic,roms等处理<br> +--> idt gate modules,kmem_cache_init<br> |<br> +--> mem_init -->free_all_bootmem buddy 得到页面控制权<br> +<br> +--> proc_root_init,fork_init, ipc,inode<br> +--> smp_init<br> +<br> +--> 创建kernel thread, init (init/main.c->函数init)<br> +--->do_basic_setup<br> ---->init pci,mtrr,sysctl,mca....<br> ---->filesystem_setup<br> ---->mount_root (关注...)<br> ---->......<br> +---> free_initmem<br> +---> 打开console<br> +--->execve("/sbin/init",argv_init,envp_init);<br> +--->execve("/etc/init",argv_init,envp_init);<br> +--->execve("/bin/init",argv_init,envp_init);<br> +--->execve("/bin/sh",argv_init,envp_init);<br><br><br> 收工.<br> <br> <br></pre>
</td>
</tr>
</tbody>
</table></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -