内核代码阅读(11) - ioremap

外部设备存储空间的地址映射 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
上一篇:内核代码阅读(15) - 中断请求队列的初始化


下一篇:内核代码阅读(9) - 内核缓冲区的管理slab上