外部设备存储空间的地址映射 ioremap
将外设设备上的存储地址反向映射到内核的虚拟地址空间。 设备相关的,下面的 __ioremap 是i386的版本。
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); }
1) 首先是一些sanity check。 2) last_addr = phys_addr + size - 1; if (!size || last_addr < phys_addr) return NULL; 检查映射区间大于0. 3) if (phys_addr >= 0xA0000 && last_addr < 0x100000) return phys_to_virt(phys_addr); VGA卡和BIOS的物理地址始终映射。无需ioremap. 4) if (phys_addr < virt_to_phys(high_memory)) high_memory: 这个变量是在系统初始化的时候,检测到内存条最大的物理地址所对应的虚拟地址。 phys_addr 小于最大的地址,说明phys_addr和系统的内存地址冲突了。 5) if(!PageReserved(page)) 如果地址冲突了,检查是否内核的虚拟地址空间留有空洞。 6) get_vm_area 获取一段内核中的空的虚拟地址。 7) if (remap_area_pages(VMALLOC_VMADDR(addr), phys_addr, size, flags))
get_vm_area 内核获取空闲的虚拟地址空间
struct vm_struct * get_vm_area(unsigned long size, unsigned long flags) { unsigned long addr; struct vm_struct **p, *tmp, *area; area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL); if (!area) return NULL; size += PAGE_SIZE; addr = VMALLOC_START; write_lock(&vmlist_lock); for (p = &vmlist; (tmp = *p) ; p = &tmp->next) { if ((size + addr) < addr) { write_unlock(&vmlist_lock); kfree(area); return NULL; } if (size + addr < (unsigned long) tmp->addr) break; addr = tmp->size + (unsigned long) tmp->addr; if (addr > VMALLOC_END-size) { write_unlock(&vmlist_lock); kfree(area); return NULL; } } area->flags = flags; area->addr = (void *)addr; area->size = size; area->next = *p; *p = area; write_unlock(&vmlist_lock); return area; }
1) struct vm_struct * vmlist; 内核的为自己保留一个虚拟空间地址的队列。 struct vm_struct { unsigned long flags; void * addr; unsigned long size; struct vm_struct * next; }; 2) size += PAGE_SIZE; 每一段虚存空间后面跟一个空洞。 3) addr = VMALLOC_START; 内核需要的虚拟地址空间是从 high_memory-8MB处开始。
remap_area_pages 内核中的页式映射
static int remap_area_pages(unsigned long address, unsigned long phys_addr, unsigned long size, unsigned long flags) { pgd_t * dir; unsigned long end = address + size; phys_addr -= address; dir = pgd_offset(&init_mm, address); flush_cache_all(); if (address >= end) BUG(); do { pmd_t *pmd; pmd = pmd_alloc_kernel(dir, address); if (!pmd) return -ENOMEM; if (remap_area_pmd(pmd, address, end - address, phys_addr + address, flags)) return -ENOMEM; address = (address + PGDIR_SIZE) & PGDIR_MASK; dir++; } while (address && (address < end)); flush_tlb_all(); return 0; }
1) 内核中没有task_struct, 用init_mm描述内核的虚拟地址管理。 2) phys_addr -= address; 在do-while循环中每次都要把物理地址的开始地址传进去。 3) remap_area_pmd 逐级映射pmd, pte