外部裝置存儲空間的位址映射 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