📄 003_arch_i386_mm_iorema.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_44ggw5kphc:6"> <table align=center cellpadding=0 cellspacing=0 height=5716 width=768>
<tbody>
<tr>
<td height=5716 valign=top width=802>
<pre>2005-10-24 <br>arch/i386/ioremap.c<br> <br><br> 参考LDD2, ch8, Software-Mapped I/O Memory。<br> <br> 比如isa设备和pci设备,或者是fb,硬件的跳线或者是物理连接方式决定了硬件上<br>的内存影射到的cpu物理地址。<br> 在内核访问这些地址必须分配给这段内存以虚拟地址,这正是__ioremap的意义所在<br>,需要注意的是,物理内存已经"存在"了,无需alloc page给这段地址了.<br><br> 文件中的注释也是比较详尽的,并且只暴露了__ioremap,iounmap两个函数供其他模<br>块调用,函数remap_area_pte,remap_area_pmd,remap_area_pages只为__ioremap所用.<br><br>/*<br> * 映射指定的物理地址的一段内存到内核的虚拟地址.<br> * 内核如果需要直接访问high address的内存则也需要这个函数先映射一下.<br> *<br> * NOTE! We need to allow non-page-aligned mappings too: we will obviously<br> * have to convert them into an offset in a page-aligned mapping, but the<br> * caller shouldn't need to know that small detail.<br> */<br>void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)<br>{<br> void * addr;<br> struct vm_struct * area;<br> unsigned long offset, last_addr;<br><br> /*先做各种检查*/<br><br> /* Don't allow wraparound or zero size */<br> last_addr = phys_addr + size - 1;<br> if (!size || last_addr < phys_addr)<br> return NULL;<br><br> /*<br> * Don't remap the low PCI/ISA area, it's always mapped..<br> */<br> if (phys_addr >= 0xA0000 && last_addr < 0x100000)<br> return phys_to_virt(phys_addr);<br><br> /*<br> * Don't allow anybody to remap normal RAM that we're using..<br> */<br> if (phys_addr < virt_to_phys(high_memory)) {<br> //by the way :virt_to_phys 只能用于HIGH MEM以下<br> //如果要映射小于HIMEM的物理地址,绝对要禁止映射普通的ram<br> char *t_addr, *t_end;<br> struct page *page;<br><br> t_addr = __va(phys_addr);<br> t_end = t_addr + (size - 1);<br> <br> for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++)<br> if(!PageReserved(page)) //只容许映射reserved的页面<br> return NULL;<br> }<br><br> /*<br> * 然后对映射地址页对齐<br> */<br> offset = phys_addr & ~PAGE_MASK;<br> phys_addr &= PAGE_MASK;<br> size = PAGE_ALIGN(last_addr) - phys_addr;<br><br> /*<br> * 接下来,分配虚拟地址给这段物理内存<br> */<br> area = get_vm_area(size, VM_IOREMAP);<br> if (!area)<br> return NULL;<br> addr = area->addr;<br> <br> /* 最后,遍历设置这段空间所涉及的pgd, pmd, pte<br> * 则大功告成.<br> */<br> if (remap_area_pages(VMALLOC_VMADDR(addr), phys_addr, size, flags)) {<br> vfree(addr);<br> return NULL;<br> }<br> return (void *) (offset + (char *)addr);<br>} <br><br><br> 就不深入进去细说remap_area_pte,remap_area_pmd,remap_area_pages了。<br> <br> 顺便说说内存的划分和管理. linux 内核在3G(PAGE_OFFSET)以上, 如果全部映射<br>为普通ram也只能管理1G的物理内存. 所以就保留了vmalloc的空间,最多容许映射<br>MAX_MEM的物理内存. 参见 arch/i386/kernel/setup.c 的定义:<br><br>#define MAXMEM (unsigned long)(-PAGE_OFFSET-VMALLOC_RESERVE)<br>#define MAXMEM_PFN PFN_DOWN(MAXMEM)<br><br> 内核使用的缓冲区可以使用vmallc分配,如果需要直接访问某个MAX_MEM地址以上<br>的物理地址,也可以映射到这段空间,如这里的ioremap.<br> <br> virt_to_phys只能应用在high_memory以下的虚拟地址上. 变量high_memory是在<br>系统启动过程中初始化的. 见arch/i386/kernel/setup.c 函数setup_arch 679行<br> max_low_pfn = max_pfn;<br> if (max_low_pfn > MAXMEM_PFN) { <br> max_low_pfn = MAXMEM_PFN; //最多映射MAXMEM_PFN个物理页面<br><br> ................<br> (注意max_low_pfn这里是局部变量)<br> 如果系统拥有超过MAXMEM的物理内存,内核则无法直接映射(即virt_to_phys失效).<br>max_low_pfn如果大于MAXMEM_PFN(即物理地址>MAXMEM,映射不了了)就只映射MAXMEM_PFN<br>个页面,如果系统中还没到这么多内存,则有多少映射多少.<br> max_low_pfn 在setup_arch 712行:传递给init_bootmem<br> ...............<br> bootmap_size = init_bootmem(start_pfn, max_low_pfn);<br> ...............<br><br> init_bootmem随后赋值给变量max_low_pfn(bootmem.c的全局变量). 最后,<br>arch/i386/mm/init.c 函数mem_init, 568行,<br> ................<br> high_memory = (void *) __va(max_low_pfn * PAGE_SIZE);<br> ................<br><br> 将max_low_pfn 转换成虚拟地址赋予high_memory.<br><br><br> 回到__ioremap 中来看看这段检查.<br>...................<br> /*<br> *Don't allow anybody to remap normal RAM that we're using..<br> */<br> if (phys_addr < virt_to_phys(high_memory)) {<br> //by the way :virt_to_phys 只能用于HIGH MEM以下<br> //如果要映射小于HIMEM的物理地址,绝对要禁止映射普通的ram<br><br>...................<br><br> 原作者注释Don't allow anybody to remap normal RAM that we're using..<br> 社么是RAM that we're using..?<br> <br> 根据以上分析,如果phys_addr 小于virt_to_phys(high_memory)意为着正在映<br>射内核已经映射了的物理页面,除非页面是Reserved属性(保留页,如bios数据区). <br>已经映射过了, 内核"随时使用",当然不容许再次映射. 如果phys_addr><br>virt_to_phys(high_memory),内核通过vmalloc使用,ioremap也容许映射这些ram<br>页面,不过有点怪怪的,应该禁止.<br><br> 以上通过对HIGHMME的简单分析解释virt_to_phys的使用范围,并解释ioremap<br>所作检查的意义. 下面回到linux内存的话题上,来注意一个问题:<br> 虽然virt_to_phys不能处理high_memory以上的虚拟地址,但是那些不能映射<br>的物理页面的管理结构page,依然在mem_map的数组里. 那些虚拟地址只有通过pte<br>来获取真正的物理地址.而page的虚拟地址也不适用phys_to_virt,只能通过<br>page->virtual获得。<br><br> <br>2.<br>void iounmap(void *addr)<br>{<br> if (addr > high_memory) //get_vm_area 获取的虚拟地址必然大于 high_memory<br> return vfree((void *) (PAGE_MASK & (unsigned long) addr));<br>}<br><br> 其实就是vfree啦.代码参见mm/vmalloc.c,这里不再罗列. 和<br>remap_area_pages类似,遍历涉及到的pgd,pmd,pte释放相关页面.只需要看看这个<br>static inline void free_area_pte(pmd_t * pmd, unsigned long address, unsigned long size)<br>{<br> pte_t * pte;<br> unsigned long end;<br><br> if (pmd_none(*pmd))<br> return;<br> if (pmd_bad(*pmd)) {<br> pmd_ERROR(*pmd);<br> pmd_clear(pmd);<br> return;<br> }<br> pte = pte_offset(pmd, address);<br> address &= ~PMD_MASK;<br> end = address + size;<br> if (end > PMD_SIZE)<br> end = PMD_SIZE;<br> do {<br> pte_t page;<br> page = ptep_get_and_clear(pte);<br> address += PAGE_SIZE;<br> pte++;<br> if (pte_none(page))<br> continue;<br> if (pte_present(page)) {<br> struct page *ptpage = pte_page(page);<br> if (VALID_PAGE(ptpage) && (!PageReserved(ptpage)))<br> //VALID_PAGE 检查此区域是否分配了ram页面,<br> //ioremap可以影射vm_area为io内存<br> //如果是VALID_PAGE(pagenr<max_mapnr)<br> __free_page(ptpage);<br> continue;<br> }<br> printk(KERN_CRIT "Whee.. Swapped out page in kernel page table\n");<br> } while (address < end);<br>}<br><br><br> 只来关心一下其中加了注释的一小段,<br>.........<br> if (VALID_PAGE(ptpage) && (!PageReserved(ptpage)))<br> //VALID_PAGE 检查此区域是否分配了ram页面,ioremap可以影射<br> //vm_area为io内存<br> //如果是VALID_PAGE(pagenr<max_mapnr)<br> __free_page(ptpage);<br>........<br><br> 如果对应ioremap的操作可以知道,vmalloc的空间被ioremap"借用",所以这段<br>虚拟地址上可能出现reseved的页面,也可能出现not valid之页面,也可能出现<br>ram页面(vmalloc). 所以vfree释放页面的时候需要检查,只释放"真正的RAM"页面<br>即那些HIGHMEM页面.VALID_PAGE确保页面的mapnr在mem_map数组空间范围之内,<br>亦即,页面是在所有RAM页面构成的地址范围内.这个范围中可能存在reseved的页<br>面. 除去这些保留页,就可以放心的free了.<br> 再注意一下,虽然地址大于virt_to_phy(high_memory)的物理页面无法使用,<br>phy_to_virt,virt_to_phy互相转换,但是依然在mem_map的管理之下.<br><br> 小提一下pmd_bad,个人认为此宏是一个例行check,理由如下:<br> 1.没有任何内核代码将一个pmd写成非法等着pmd_bad 去检查<br> 2.出现pmd错误则产生一个log信息,足以证明此猜想.<br> <br> <br><br> <br><br></pre>
</td>
</tr>
</tbody>
</table></body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -