arch/i386/ioremap.c
   

   参考LDD2, ch8, Software-Mapped I/O Memory。
   
   比如isa设备和pci设备,或者是fb,硬件的跳线或者是物理连接方式决定了硬件上
的内存影射到的cpu物理地址。
   在内核访问这些地址必须分配给这段内存以虚拟地址,这正是__ioremap的意义所在
,需要注意的是,物理内存已经"存在",无需alloc page给这段地址了.

   文件中的注释也是比较详尽的,并且只暴露了__ioremap,iounmap两个函数供其他模
块调用,函数remap_area_pte,remap_area_pmd,remap_area_pages只为__ioremap所用.

/*
 * 映射指定的物理地址的一段内存到内核的虚拟地址.
 * 内核如果需要直接访问high address的内存则也需要这个函数先映射一下.
 *
 * NOTE! We need to allow non-page-aligned mappings too: we will obviously
 * have to convert them into an offset in a page-aligned mapping, but the
 * caller shouldn't need to know that small detail.
 */
void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
	void * addr;
	struct vm_struct * area;
	unsigned long offset, last_addr;

        /*先做各种检查*/

	/* Don't allow wraparound or zero size */
	last_addr = phys_addr + size - 1;
	if (!size || last_addr < phys_addr)
		return NULL;

	/*
	 * Don't remap the low PCI/ISA area, it's always mapped..
	 */
	if (phys_addr >= 0xA0000 && last_addr < 0x100000)
		return phys_to_virt(phys_addr);

	/*
	 * Don't allow anybody to remap normal RAM that we're using..
	 */
	if (phys_addr < virt_to_phys(high_memory)) {
                //by the way :virt_to_phys 只能用于HIGH MEM以下
                //如果要映射小于HIMEM的物理地址,绝对要禁止映射普通的ram
		char *t_addr, *t_end;
		struct page *page;

		t_addr = __va(phys_addr);
		t_end = t_addr + (size - 1);
	   
		for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++)
			if(!PageReserved(page))  //只容许映射reserved的页面
				return NULL;
	}

	/*
	 * 然后对映射地址页对齐
	 */
	offset = phys_addr & ~PAGE_MASK;
	phys_addr &= PAGE_MASK;
	size = PAGE_ALIGN(last_addr) - phys_addr;

	/*
	 * 接下来,分配虚拟地址给这段物理内存
	 */
	area = get_vm_area(size, VM_IOREMAP);
	if (!area)
		return NULL;
	addr = area->addr;
        
        /* 最后,遍历设置这段空间所涉及的pgd, pmd, pte
         * 则大功告成.
         */
	if (remap_area_pages(VMALLOC_VMADDR(addr), phys_addr, size, flags)) {
		vfree(addr);
		return NULL;
	}
	return (void *) (offset + (char *)addr);
}   


       就不深入进去细说remap_area_pte,remap_area_pmd,remap_area_pages了。
     
     顺便说说内存的划分和管理. linux 内核在3G(PAGE_OFFSET)以上, 如果全部映射
为普通ram也只能管理1G的物理内存. 所以就保留了vmalloc的空间,最多容许映射
MAX_MEM的物理内存. 参见  arch/i386/kernel/setup.c 的定义:

#define MAXMEM	(unsigned long)(-PAGE_OFFSET-VMALLOC_RESERVE)
#define MAXMEM_PFN	PFN_DOWN(MAXMEM)

     内核使用的缓冲区可以使用vmallc分配,如果需要直接访问某个MAX_MEM地址以上
的物理地址,也可以映射到这段空间,如这里的ioremap.
     
     virt_to_phys只能应用在high_memory以下的虚拟地址上. 变量high_memory是在
系统启动过程中初始化的. 见arch/i386/kernel/setup.c 函数setup_arch 679行
	max_low_pfn = max_pfn;
	if (max_low_pfn > MAXMEM_PFN) { 
		max_low_pfn = MAXMEM_PFN; //最多映射MAXMEM_PFN个物理页面

                 ................
     (注意max_low_pfn这里是局部变量)
   如果系统拥有超过MAXMEM的物理内存,内核则无法直接映射(即virt_to_phys失效).
max_low_pfn如果大于MAXMEM_PFN(即物理地址>MAXMEM,映射不了了)就只映射MAXMEM_PFN
个页面,如果系统中还没到这么多内存,则有多少映射多少.
       max_low_pfn 在setup_arch 712:传递给init_bootmem
 ...............
      bootmap_size = init_bootmem(start_pfn, max_low_pfn);
 ...............

       init_bootmem随后赋值给变量max_low_pfn(bootmem.c的全局变量). 最后,
arch/i386/mm/init.c  函数mem_init, 568,
      ................
      high_memory = (void *) __va(max_low_pfn * PAGE_SIZE);
      ................

      将max_low_pfn 转换成虚拟地址赋予high_memory.


      回到__ioremap 中来看看这段检查.
...................
     /*
      *Don't allow anybody to remap normal RAM that we're using..
      */
	if (phys_addr < virt_to_phys(high_memory)) {
                //by the way :virt_to_phys 只能用于HIGH MEM以下
                //如果要映射小于HIMEM的物理地址,绝对要禁止映射普通的ram

...................

   原作者注释//Don't allow anybody to remap normal RAM that we're using..
   社么是//RAM that we're using..?
   
   根据以上分析,如果phys_addr 小于virt_to_phys(high_memory)意为着正在映
射内核已经映射了的物理页面,除非页面是Reserved属性(保留页,如bios数据区). 
已经映射过了, 内核"随时使用",当然不容许再次映射. 如果phys_addr>
virt_to_phys(high_memory),内核通过vmalloc使用,ioremap也容许映射这些ram
页面,不过有点怪怪的,应该禁止.

    以上通过对HIGHMME的简单分析解释virt_to_phys的使用范围,并解释ioremap
所作检查的意义. 下面回到linux内存的话题上,来注意一个问题:
    虽然virt_to_phys不能处理high_memory以上的虚拟地址,但是那些不能映射
的物理页面的管理结构page,依然在mem_map的数组里. 那些虚拟地址只有通过pte
来获取真正的物理地址.而page的虚拟地址也不适用phys_to_virt,只能通过
page->virtual获得。

    
2.
void iounmap(void *addr)
{
	if (addr > high_memory) //get_vm_area 获取的虚拟地址必然大于 high_memory
		return vfree((void *) (PAGE_MASK & (unsigned long) addr));
}

    其实就是vfree啦.代码参见mm/vmalloc.c,这里不再罗列. 和
remap_area_pages类似,遍历涉及到的pgd,pmd,pte释放相关页面.只需要看看这个
static inline void free_area_pte(pmd_t * pmd, unsigned long address, unsigned long size)
{
	pte_t * pte;
	unsigned long end;

	if (pmd_none(*pmd))
		return;
	if (pmd_bad(*pmd)) {
		pmd_ERROR(*pmd);
		pmd_clear(pmd);
		return;
	}
	pte = pte_offset(pmd, address);
	address &= ~PMD_MASK;
	end = address + size;
	if (end > PMD_SIZE)
		end = PMD_SIZE;
	do {
		pte_t page;
		page = ptep_get_and_clear(pte);
		address += PAGE_SIZE;
		pte++;
		if (pte_none(page))
			continue;
		if (pte_present(page)) {
			struct page *ptpage = pte_page(page);
			if (VALID_PAGE(ptpage) && (!PageReserved(ptpage)))
				//VALID_PAGE 检查此区域是否分配了ram页面,
				//ioremap可以影射vm_area为io内存
				//如果是VALID_PAGE(pagenr<max_mapnr)
				__free_page(ptpage);
			continue;
		}
		printk(KERN_CRIT "Whee.. Swapped out page in kernel page table\n");
	} while (address < end);
}


   只来关心一下其中加了注释的一小段,
.........
  if (VALID_PAGE(ptpage) && (!PageReserved(ptpage)))
        //VALID_PAGE 检查此区域是否分配了ram页面,ioremap可以影射
        //vm_area为io内存
	//如果是VALID_PAGE(pagenr<max_mapnr)
	__free_page(ptpage);
........

   如果对应ioremap的操作可以知道,vmalloc的空间被ioremap"借用",所以这段
虚拟地址上可能出现reseved的页面,也可能出现not valid之页面,也可能出现
ram页面(vmalloc). 所以vfree释放页面的时候需要检查,只释放"真正的RAM"页面
即那些HIGHMEM页面.VALID_PAGE确保页面的mapnr在mem_map数组空间范围之内,
亦即,页面是在所有RAM页面构成的地址范围内.这个范围中可能存在reseved的页
面. 除去这些保留页,就可以放心的free了.
   再注意一下,虽然地址大于virt_to_phy(high_memory)的物理页面无法使用,
phy_to_virt,virt_to_phy互相转换,但是依然在mem_map的管理之下.

    小提一下pmd_bad,个人认为此宏是一个例行check,理由如下:
 1.没有任何内核代码将一个pmd写成非法等着pmd_bad 去检查
 2.出现pmd错误则产生一个log信息,足以证明此猜想.