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所用.
void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
void * addr;
struct vm_struct * area;
unsigned long offset, last_addr;
last_addr = phys_addr + size - 1;
if (!size || last_addr < phys_addr)
return NULL;
if (phys_addr >= 0xA0000 && last_addr < 0x100000)
return phys_to_virt(phys_addr);
if (phys_addr < virt_to_phys(high_memory)) { 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)) 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;
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;
................
(注意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 中来看看这段检查.
...................
if (phys_addr < virt_to_phys(high_memory)) {
...................
原作者注释 社么是
根据以上分析,如果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) 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))) __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))) __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信息,足以证明此猜想.