📄 007_mm_highmem_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_48f3d5crhb:9"> <table align=center cellpadding=0 cellspacing=0 height=5716 width=768>
<tbody>
<tr>
<td height=5716 valign=top width=100%>
<pre>2005-11-16 <br>HIGHMEM Completed Understanding<br> mm/highmem.c include/asm-i386/highmem.h(not mm/highmem.h)<br> <br> HighMem 已经不止一次的被提到了.或许本节所述已存于前,还是希望这里有完<br>整的表述.<br> 内核空间从3G开始,如果全部采用"线性映射"(物理地址和逻辑地址只差一个常<br>量 PAGE_OFFSET ),最多管理1G物理内存.现在我使用的电脑就有1G内存(AMD64),<br>我朋友的机器有2G,据说玩游戏巨爽(AMD64300+).显然如果线性映射我的朋友就会<br>浪费1G内存.为了使内核能够访问这些"高端内存",内核使用HighMem.做法是不将<br>内核1G的虚拟地址空间全部映射成物理内存,而是预留一部分给高端内存做临时映<br>射使用.<br> 其实内核不仅仅预留了highmem的地址空间,还给fixmap,vmalloc预留了虚存空<br>间.实际上,系统初始化的时候预留128M虚存,896M用于"直接"映射物理内存.分析<br>arch/i386/mm/init.c的时候已经对此有过详细说明.这里还是将相关部分罗列出<br>来,以便有一个完整的印象.<br> 首先是内核虚存的划分管理图:<br>+------------------------------------------------------------------ <br>| 8K空洞<br>+------------------------------------------------------------------<br>| FIXADDR_TOP(0xffffe000UL) (include/asm-i386/fixmap.h)<br>| fixed map(每项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>| FIX_KMAP_BEGIN, /* 主要用于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>| VMALLOC_END (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>| VMALLOC_START (((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> 首先是highmem所占用的虚拟内存分成两个部分,一个是FixMap部分为每个cpu<br>都预留了8k的虚存用于kmap_atomic,另外一部分从PKMAP_BASE (0xfe000000UL)开<br>始,预留了4M的虚存.总共能映射1024+2个高端内存页面.<br> 然后看看如何获取页面的虚拟地址:<br> 对于物理地址小于high_memory的页面,采取的是线性映射phys_to_vir有效<br>同样,对于虚拟地址小于virt_to_phys(high_memory)则可以使用宏virt_to_phys.<br> 另外一个获取页面虚拟地址的方式是page->virtual变量.在初始化page的mem_map<br>数组的时候,free_area_init_core对非ZONE_HIGHMEM的page->virtual进行了初始<br>化.而ZONE_HIGHMEM则在需要的时候用kmap来映射页面并初始化page->virtual,或<br>者使用vmalloc. 所以,在任何时候page->virtual都可代表页面在内核的虚拟地址<br>(只要非0).<br> 其实还有一个 fix_to_virt 用于转换fix map页面.<br> 从虚拟地址向物理地址转换,virt_to_phys适用于虚拟地址小于virt_to_phys<br>(high_memory)的范围,前面刚刚说过.还有一个是从pte中获取页面nr,从nr转换成<br>物理地址. 从pte获取页面物理地址的方式是任何时候都不会出错的.所以在处理缺<br>页的时候,"都是"从pte转换. 因为内核尽量给用户分配highmem.所以virt_to_phys<br>不能工作. 这里的差别应该仔细体会.<br> <br> I)include/asm-i386/highmem.h 的几个重要的全局变量和宏定义:<br> <br>/* ref. setup_arch line 720*/<br>/* highstart_pfn:高端内存起始页面的page fram num<br> * highend_pfn :高端内存终止页面的page fram num<br> */<br>extern unsigned long highstart_pfn, highend_pfn;<br><br>/*<br> * kmap_pte: ref kmap_init,为kmap_atomic预留的两个fixmap页面的起始pte<br> * kmap_prot: i386下初始化为PAGE_KERNEL<br> */<br>extern pte_t *kmap_pte;<br>extern pgprot_t kmap_prot;<br>/* <br> * pkmap_page_table: 内核为kmap预留了4M虚存,正好一整页的pte(one page <br> * table) pgd,pmd,page table在内核初始化的时候都分配好了,只差将pte置<br> * 为某个page pfn<br> */<br>extern pte_t *pkmap_page_table;<br><br> 参考一下对arch/i386/mm/init.c的分析,fixmap和pkmap对page dir,pmd,<br>包括page table都分配了页面,只有page table中的pte未设置.<br><br>#define PKMAP_BASE (0xfe000000UL)<br> /*<br> * kmap 使用 从PKMAP_BASE (0xfe000000UL)<br> * 开始的4M 空间,不幸的是和VMALLOC_START-VMALLOC_END<br> * 有重叠 linux2.6 已经修正了这个错误<br> * kmap_atomic 使用 FIX_KMAP_BEGIN的一段虚存,见 fixmap.h<br> */<br>#ifdef CONFIG_X86_PAE<br>#define LAST_PKMAP 512<br>#else<br>#define LAST_PKMAP 1024 /*4M虚存最多映射1k页面,叫max kmaped pages更合适*/<br>#endif<br>#define LAST_PKMAP_MASK (LAST_PKMAP-1)<br><br>/*将virt转换成其所位于的4M kmap虚存页的序号*/<br>#define PKMAP_NR(virt) ((virt-PKMAP_BASE) >> PAGE_SHIFT) <br>/*pkmap nr 到虚拟地址*/<br>#define PKMAP_ADDR(nr) (PKMAP_BASE + ((nr) << PAGE_SHIFT))<br> <br> <br> II) mm/memory.c 中相关的两个全局变量<br> <br>/*从虚拟地址 high_memory 开始不再是线性映射区<br> *如果全部可以线性映射其值为物理内存顶端之虚拟地址<br> */<br>void * high_memory; <br>struct page *highmem_start_page; /*高端物理内存的起始 page(struct page)*/<br><br> III)mm/highmem.c的全局量<br> /*<br> * Virtual_count is not a pure "count".<br> * 0 means that it is not mapped, and has not been mapped<br> * since a TLB flush - it is usable.<br> * 1 means that there are no users, but it has been mapped<br> * since the last TLB flush - so we can't use it.<br> * n means that there are (n-1) current users of it.<br> */<br>static int pkmap_count[LAST_PKMAP];<br>static unsigned int last_pkmap_nr; /*上一次分配出去的虚存页面的kmapnr*/<br> <br> 数组pkmap_count管理为kmap准备的4M虚存,LAST_PKMAP为1K,正好管理1k个<br>虚存页面.last_pkmap_nr记录上次分配出去的虚存页面之nr.<br><br> IV)highmem的接口函数<br>highmem.h(include/asm-i386)<br>static inline void *kmap(struct page *page)<br>{<br> if (in_interrupt())<br> BUG();<br> if (page < highmem_start_page) /*如果企图映射非highmem页面,直接返回其*/<br> return page_address(page); /*线性映射地址*/<br> return kmap_high(page); /*否则寻找一个未用虚存页,设置pte,完成映射*/<br>} <br>kmap_high检查page->virtual如果非0证明已经映射过,否则用map_new_virtual建立<br>真正的映射,如果没有发现可用虚存则等待,kumap的时候会唤醒等待进程.<br> 相关代码很是直白,有问题再讨论吧.<br> <br> kmap_atomic使用fixmap中保留的两个虚存页面,提供了可以在irq环境中使用的<br>highmem访问接口.<br> 另外注意__GFP_HIGH不是要分配highmem的内存,__GFP_HIGHMEM才是,看看<br>struct buffer_head * create_bounce(int rw, struct buffer_head * bh_orig)<br>不要把这两个东西搞混了.create_bounce为处于highmem的bh分配一个非highmem的<br>内存页面,并继承bh其他的所有东西.在io完成之前后负责在highmem和这个跳板页<br>面直接复制数据. 或许是dma不能处理himem故需要这个跳板(should be this).<br> 看看什么情况下对HigMemPage进行io:<br> 内核尽量给用户分配highmem页面,在某种情况下需要将数据写到这些页面.比如用<br>户将文件mmap到内存,然后内核为这些页面分配了Highmem Page,现在需要读入数据.<br>直接看block_read_full_page,<br>............<br> if (!buffer_mapped(bh)) {<br> memset(kmap(page) + i*blocksize, 0, blocksize);<br> flush_dcache_page(page);<br> kunmap(page);<br> set_bit(BH_Uptodate, &bh->b_state);<br> continue;<br> }<br>...............<br> 可以看到kmap,映射高端内存的操作.证明这个流程需要处理Highmem Page.看<br>submit_bh->generic_make_request->q->make_request_fn这个函数度于ide就是<br>__make_request(见函数blk_init_queue):<br>#if CONFIG_HIGHMEM<br> bh = create_bounce(rw, bh);<br>#endif<br> 利用了highmem提供的这个跳板.<br> <br> 所有其他函数这里不再讨论了.<br> <br> <br><br> <br> <br> <br></pre>
</td>
</tr>
</tbody>
</table></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -