内核代码阅读(11) - ioremap

简介: 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
相关文章
|
6月前
|
安全 调度
软件体系结构 - 宏内核
【4月更文挑战第19天】软件体系结构 - 宏内核
91 0
|
3月前
|
存储 缓存 Java
hyengine编译实现问题之复用 quickjs 原代码如何解决
hyengine编译实现问题之复用 quickjs 原代码如何解决
|
3月前
|
测试技术 数据安全/隐私保护 Python
探索Python中的装饰器:简化代码,增强功能深入理解操作系统:从用户空间到内核空间的旅程
【8月更文挑战第29天】本文将引导你深入理解Python装饰器的核心概念、应用场景及其对代码的优化作用。我们将从基础使用到高级应用逐步展开,通过实例展示如何利用装饰器提升代码的可读性和复用性,同时避免常见的陷阱。
|
6月前
|
存储 算法
C标准库函数的工作细节
C标准库函数的工作细节
一个Linux驱动工程师必知的内核编译机制
一个Linux驱动工程师必知的内核编译机制
|
存储 API
驱动开发:内核文件读写系列函数
在应用层下的文件操作只需要调用微软应用层下的`API`函数及`C库`标准函数即可,而如果在内核中读写文件则应用层的API显然是无法被使用的,内核层需要使用内核专有API,某些应用层下的API只需要增加Zw开头即可在内核中使用,例如本章要讲解的文件与目录操作相关函数,多数ARK反内核工具都具有对文件的管理功能,实现对文件或目录的基本操作功能也是非常有必要的。
276 0
|
安全 开发工具 Windows
驱动开发:内核实现进程汇编与反汇编
在笔者上一篇文章`《驱动开发:内核MDL读写进程内存》`简单介绍了如何通过MDL映射的方式实现进程读写操作,本章将通过如上案例实现远程进程反汇编功能,此类功能也是ARK工具中最常见的功能之一,通常此类功能的实现分为两部分,内核部分只负责读写字节集,应用层部分则配合反汇编引擎对字节集进行解码,此处我们将运用`capstone`引擎实现这个功能。
277 0
|
机器学习/深度学习 C语言