⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 003_arch_i386_mm_iorema.html

📁 重读linux 2.4.2o所写的笔记
💻 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 &lt; 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 &gt;= 0xA0000 &amp;&amp; last_addr &lt; 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 &lt; 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 &lt;= virt_to_page(t_end); page++)<br>			if(!PageReserved(page))  //只容许映射reserved的页面<br>				return NULL;<br>	}<br><br>	/*<br>	 * 然后对映射地址页对齐<br>	 */<br>	offset = phys_addr &amp; ~PAGE_MASK;<br>	phys_addr &amp;= 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-&gt;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 &gt; 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(即物理地址&gt;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 &lt; 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&gt;<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-&gt;virtual获得。<br><br>    <br>2.<br>void iounmap(void *addr)<br>{<br>	if (addr &gt; high_memory) //get_vm_area 获取的虚拟地址必然大于 high_memory<br>		return vfree((void *) (PAGE_MASK &amp; (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 &amp;= ~PMD_MASK;<br>	end = address + size;<br>	if (end &gt; 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) &amp;&amp; (!PageReserved(ptpage)))<br>				//VALID_PAGE 检查此区域是否分配了ram页面,<br>				//ioremap可以影射vm_area为io内存<br>				//如果是VALID_PAGE(pagenr&lt;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 &lt; end);<br>}<br><br><br>   只来关心一下其中加了注释的一小段,<br>.........<br>  if (VALID_PAGE(ptpage) &amp;&amp; (!PageReserved(ptpage)))<br>        //VALID_PAGE 检查此区域是否分配了ram页面,ioremap可以影射<br>        //vm_area为io内存<br>	//如果是VALID_PAGE(pagenr&lt;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 + -