linux内存管理:kmap、vmap、ioremap

目录

散列表也是哈希表

kmap实现

page_address_map

pkmap_count

page_address_slot

哈希函数

kmap函数实现

kmap_init

kmap

kmap_high

page_address

map_new_virtual

set_page_address

kunmap

kunmap_high(struct page *page)函数实现

vmap实现

vmap函数

vunmap函数

ioremap

ioremap函数

__arm_ioremap_pfn_caller函数

pfn_valid(pfn)

后记


 

说明:

  •  kernel版本:5.9.8
  • 调试工具:gdb + qemu + kernel-O0
  • 平台信息:ARM Vexpress ARMv7 4核 SMP
  • 使用工具:vim + cscope + visio

 

  1. vmap()用于相对长时间的映射,可同时映射多个pages,
  2. kmap()和kmap_atomic()则用于相对短时间的映射,只能映射单个page.
  3. 而 ioremap 函数,其功能是将给定的物理地址映射为虚拟地址。

注意此处的物理地址并不是真正内存的物理地址,而是cpu上的io memory。

本篇文章重点从以下方面分析:

  • 1、散列表
  • 2、kmap
  • 3、vmap
  • 4、ioremap

 

散列表也是哈希表


散列表:根据给定的关键字来计算关键字在表中的地址的数据结构,也就说,散列表建立了关键字和存储地址之间的一种直接映射关系。

散列函数:一个把查找表中的关键字映射成该关键字对应的地址的函数,记为Hash(key)=Addr

冲突:散列函数可能会把两个或者两个以上不同关键字映射同一地址,称这种情况为冲突,这些发生碰撞的不同关键字称为同义词

构造散列函数的tips:

  • 1、散列函数的定义域必须包含全部需要存储的关键字,而值域的范围则依赖于散列表的大小或地址范围
  • 2、散列函数计算出来的地址应该能等概率,均分布在整个地址空间,从而减少冲突的发生。
  • 3、散列函数应该尽量简单,能够在较短的时间内就计算出任一关键字对应的散列地址

常用Hash函数的构造方法:

  • 1、直接定址法
  • 2、除留余数法
  • 3、数字分析法
  • 4、平方取中法

 

kmap实现

用于相对短时间的映射,只能映射单个page.


page_address_map

struct page_address_map {                                                                                                         
    struct page *page;
    void *virtual;
    struct list_head list;
};

struct page_address_map:散列表中关键字(key=page)对应的结构,散列表中的结点

  • page:管理一个高端的物理page
  • virtual:page物理页面对应的虚拟地址
  • list:链接到page_address_htable[i]对应lh的头部的结点

 

pkmap_count


#define PTRS_PER_PTE        512
#define LAST_PKMAP      PTRS_PER_PTE 
static int pkmap_count[LAST_PKMAP]
  • 1、pkmap_count[i]=0为空闲的虚拟地址可以直接使用的
  • 2、pkmap_count[i]=1已经被使用过且被释放了但是还在缓存中
  • 3、pkmap_count[i]>=2正在使用中
  • 4、pkmap_count[i]描述的虚拟地址为0xbfe00000+4k*i

来一张图描述pkmap_count虚拟地址之间的对应关系

page_address_slot


static struct page_address_slot {
    struct list_head lh;            /* List of page_address_maps */
    spinlock_t lock;            /* Protect this bucket's list */
} ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];
  • page_address_slot:散列表的描述结构,page_address_htable为散列表
  • lh:链接哈希表中的成员结点,此哈希表通过链表解决冲突问题

 

哈希函数


#define hash_long(val, bits) hash_32(val, bits) 
#define hash_32 hash_32_generic 
#define __hash_32 __hash_32_generic
#define PA_HASH_ORDER   7
#define GOLDEN_RATIO_32 0x61C88647
static inline u32 __hash_32_generic(u32 val)                                                                                      
{
    return val * GOLDEN_RATIO_32;
}
static inline u32 hash_32_generic(u32 val, unsigned int bits)
{
    /* High bits are more random, so use them. */
    return __hash_32(val) >> (32 - bits);                                                                                         
}
static inline u32 hash_ptr(const void *ptr, unsigned int bits)                                                                    
{
    return hash_long((unsigned long)ptr, bits);
}
static struct page_address_slot *page_slot(const struct page *page)
{
    return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
}

哈希函数通过以上的各种转换之后的表达式为:

Hash(page)=page*GOLDEN_RATIO_32>>(32-PA_HASH_ORDER)

哈希函数的选择遵循构造散列函数的tips

再来一张图描述page_address_slotpage_address_map之间的关系

 

kmap函数实现


page如果是ZONE_NORMAL,则直接返回虚拟地址即可。

page如果是ZONE_HIGHMEM,则分以下情况:

  • 1、如果page在page_address_htable中直接返回虚拟地址
  • 2、如果page不在page_address_htable中,则分以下情况:
    •  如果只有pkmap_count[0]为0,则会进行一次flush_all_zero_pkmaps(),然后直接退出,因为pkmap_count[0]为0,则直接返回pkmap_count[0]对应的虚拟地址。
    •  如果有pkmap_count[i]的数组为0,i非0,i的下标最小,则直接拿到pkmap_count[i]对应的虚拟地址。
    •  如果全部非0,则分以下情况
  •  1、有可以的释放的,则通过flush_all_zero_pkmaps()释放,先判断pkmap_count[0]释放释放满足要求,如果不满足要求,然后再循环一次判断,发现有空闲的,则获取对应的虚拟地址。
  •  2、如果没有可以释放的,即都是在使用的,则需要加入等待队列,直到有空闲的释放被唤醒。

 

kmap_init


static void __init kmap_init(void)                                                                                                                                       
{   
#ifdef CONFIG_HIGHMEM
    pkmap_page_table = early_pte_alloc(pmd_off_k(PKMAP_BASE),
        PKMAP_BASE, _PAGE_KERNEL_TABLE);
#endif
}

从上可以看出上面对[KMAP_BASE,PAGE_OFFSET]进行PGD页表的填充,页表的port为_PAGE_KERNEL_TABLE,虚拟地址为FIXADDR_START,early_pte_alloc通过early_alloc函数pte_t*pte = alloc (PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE)申请 512 * 4 + 512 *4其中硬件页表和软件各占一半,512硬件PTE表项,一个PTE表项对应4k,刚好2MB对应[KMAP_BASE,PAGE_OFFSET]大小,初始化完成之后,一级页表PGD表项由KMAP_BASE起始地址的PGD表项(PGD[0],PGD[1])指向对应pte的基地址。此后如果在[KMAP_BASE,PAGE_OFFSET]要映射对应的物理地址,只需要填充PTE表项即可。

kmap


static inline void *kmap(struct page *page)                                                                                                                              
{
    void *addr;
    might_sleep();
    if (!PageHighMem(page))
        addr = page_address(page);
    else
        addr = kmap_high(page);
    kmap_flush_tlb((unsigned long)addr);
    return addr;
}

1、判断page页面是否是ZONE_HIGHMEM,如果不是,则认为是ZONE_NORMAL,因为ZONE_NORMAL为低端ZONE,则为线性映射,直接获取虚拟addr通过lowmem_page_address函数

2、如果给是page指向ZONE_HIGHMEM的物理页面,需要kamp_high做物理地址和虚拟地址的映射

3、获取到映射完成的虚拟地址之后刷TLB后,返回虚拟地址

kmap_high


void *kmap_high(struct page *page)
{                                                                                                                                                                        
    unsigned long vaddr;
    lock_kmap();
    vaddr = (unsigned long)page_address(page);
    if (!vaddr)
        vaddr = map_new_virtual(page);
    pkmap_count[PKMAP_NR(vaddr)]++;
    BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
    unlock_kmap();
    return (void*) vaddr;
}
  • 1、已经确定page是ZONE_HIGHMEM,通过page_address函数的hash表找到对应的虚拟地址,如果虚拟地址返回为NULL,则重新进行映射
  • 2、对pkmap_count[PKMAP_NR(vaddr)]++到2,说明此page对应虚拟地址已经被分配出去使用了
  • 3、返回映射成功的虚拟地址。

接下来重点分析page_address函数

 

page_address


void *page_address(const struct page *page)
{
    unsigned long flags;
    void *ret;
    struct page_address_slot *pas;
    if (!PageHighMem(page))
        return lowmem_page_address(page);
    pas = page_slot(page);
    ret = NULL;
    spin_lock_irqsave(&pas->lock, flags);
    if (!list_empty(&pas->lh)) {
        struct page_address_map *pam;
        list_for_each_entry(pam, &pas->lh, list) {
            if (pam->page == page) {
                ret = pam->virtual;
                goto done;
            }
        }
    }
done:
    spin_unlock_irqrestore(&pas->lock, flags);
    return ret;
}
  • 1、此page已经确定是ZONE_HIGHMEM,则直接通过page_slot(page)函数(哈希函数)获取到此page的储存器地址
  • 2、因为page可能产生冲突,则需要通过lh链表头遍历此page是否在此链表中,如果存在则直接返回虚拟地址即可,说明之前此page已经建立好了映射,虚拟地址直接使用即可,如果未找到,则需要通过map_new_virtual(page)函数重新映射。

 

map_new_virtual

static inline unsigned long map_new_virtual(struct page *page)
{
    unsigned long vaddr;
    int count;
    unsigned int last_pkmap_nr;
    unsigned int color = get_pkmap_color(page);
start:
    count = get_pkmap_entries_count(color);
    /* Find an empty entry */
    for (;;) {
        last_pkmap_nr = get_next_pkmap_nr(color);
        if (no_more_pkmaps(last_pkmap_nr, color)) {
            flush_all_zero_pkmaps();
            count = get_pkmap_entries_count(color);
        }
        if (!pkmap_count[last_pkmap_nr])
            break;  /* Found a usable entry */
        if (--count)
            continue;
        {
            DECLARE_WAITQUEUE(wait, current);
            wait_queue_head_t *pkmap_map_wait = get_pkmap_wait_queue_head(color);
            __set_current_state(TASK_UNINTERRUPTIBLE);
            add_wait_queue(pkmap_map_wait, &wait);
            unlock_kmap();
            schedule();
            remove_wait_queue(pkmap_map_wait, &wait);
            lock_kmap();
            /* Somebody else might have mapped it while we slept */
            if (page_address(page))
                return (unsigned long)page_address(page);
            /* Re-start */
            goto start;
        }
    }
    vaddr = PKMAP_ADDR(last_pkmap_nr);
    set_pte_at(&init_mm, vaddr,
           &(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));
    pkmap_count[last_pkmap_nr] = 1;
    set_page_address(page, (void *)vaddr);                                                                                                                               
    return vaddr;
}

 

static inline int no_more_pkmaps(unsigned int pkmap_nr, unsigned int color)                                                                                              
{       
    return pkmap_nr == 0;
} 
static inline unsigned int get_pkmap_color(struct page *page)                                                                                                      
{
   return 0;
}
static inline int get_pkmap_entries_count(unsigned int color)                                                                                                       
{
   return LAST_PKMAP;
}
static inline unsigned int get_next_pkmap_nr(unsigned int color)                                                                                                         
{           
    static unsigned int last_pkmap_nr;
    last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
    return last_pkmap_nr;
}

get_pkmap_color(page)函数仅在特定体系结构(mips和xtensa)中使用,并且由于未在ARM中使用而返回0

color=0,count=512,last_pkmap_nr=1开始循环遍历pkmap_count [last_pkmap_nr]是否为0,如果为0,说明此PTE还未填充或者PTE无效。现在可以拿来用填充,如果512个表项全为非0,即所有的PTE都映射了page,即已经经过kmap和kumap,或者在kunmap中,但是没有实际的解映射,只是pkmap_count[last_pkmap_nr]--。此page对应的物理地址和对应的虚拟地址通过hashtable存储。

count=512,last_pkmap_nr=1

count=511,last_pkmap_nr=2

…

count=1,last_pkmap_nr=512

然后此时if(no_more_pkmaps(last_pkmap_nr, color))成立

  • 1、先flush_cache_kmaps()
  • 2、循环遍历pkmap_count[i],如果等于1,则说明page已经kumap了,把pkmap_count[i] = 0,然后通过pte_clear清空pte和物理地址的页表的映射关系
  • 3、然后通过set_page_address(page, NULL)函数把page从hashtable删除,同时need_flush置1,然后延时刷TLB

然后再判断pkmap_count[0]是否满足要求,如果不满足再循环检测是否有空闲的pkmap_count[last_pkmap_nr]

count=512 last_pkmap_nr=0;

count=511 last_pkmap_nr=1;

count=510 last_pkmap_nr=2;

count=1 last_pkmap_nr=511

如果count=0,还是无法找到空闲的pkmap_count[last_pkmap_nr]

则把先创建一个等待队列,然后把此任务加入到等待队列中,同时把此任务设置为不可屏蔽中断状态,等待唤醒,如果有其他任务释放了pkmap_count[last_pkmap_nr],即是把pkmap_count[last_pkmap_nr]设置为1了,会wakeup此任务,然后从等待队列中删除此任务,然后先尝试在哈希表中是否能找到对应的page,如果找不到,需要重新start即是上述流程。<为何需要先page_address,有可能同一个page在其他高优先级的进程kmap了也没拿到,但是等有空闲得到时候,高优先级任务可能优先拿到,占了一个pkmap_count,低优先级的任务也要占一个pkmap_count,导致同一个page占了两个pkmap_count>

  • 4、通过last_pkmap_nr找到对应的虚拟地址,然后用通过set_pte_at函数填充PTE页表建立映射。
  • 5、把pkmap_count[last_pkmap_nr] = 1表示此PTE已经被填充
  • 6、通过set_page_address(page, (void *)vaddr)函数把page加入哈希表中

 

set_page_address

void set_page_address(struct page *page, void *virtual)
{
    unsigned long flags;
    struct page_address_slot *pas;
    struct page_address_map *pam;
    BUG_ON(!PageHighMem(page));
    pas = page_slot(page);
    if (virtual) {      /* Add */
        pam = &page_address_maps[PKMAP_NR((unsigned long)virtual)];
        pam->page = page;
        pam->virtual = virtual;
        spin_lock_irqsave(&pas->lock, flags);
        list_add_tail(&pam->list, &pas->lh);
        spin_unlock_irqrestore(&pas->lock, flags);
    } else {        /* Remove */
        spin_lock_irqsave(&pas->lock, flags);
        list_for_each_entry(pam, &pas->lh, list) {
            if (pam->page == page) {
                list_del(&pam->list);
                spin_unlock_irqrestore(&pas->lock, flags);
                goto done;
            }
        }
        spin_unlock_irqrestore(&pas->lock, flags);
    }
done:
    return;
}
  • 1、通过page找到对应存储page_address_map的地址
  • 2、如果虚拟地址非NULL,则说明是add,如果为空则为remove
  • 3、如果是add的话即先通过虚拟地址找到对应的page_address_maps[PKMAP_NR((unsigned long)virtual)]然后更新page_address_map中的page和virtual,通过page_address_map的list添加到page_address_slot中的lh的头部中list_add_tail(&pam->list, &pas->lh);
  • 4、如果是remove的话,找到对应存储地址,然后遍历page_address_slot中的page和当前page是否相等,如果相等就直接list_del

 

kunmap

释放过程

  • 1、page如果是ZONE_NORMAL则直接返回
  • 2、page如果是ZONE_HIGHMEM, pkmap_count[nr],然后判断等待队列是否为空,如果非空,则唤醒等待队列中的任务。
static inline void kunmap(struct page *page)
{
    might_sleep();
    if (!PageHighMem(page))
        return;
    kunmap_high(page);                                                                                                                                                   
}
  • 1、如果是指向ZONE_NORMAL的page,则直接返回
  • 2、如果是指向ZONE_HIGHMEM的page ,则调用 kunmap_high(page)

 

kunmap_high(struct page *page)函数实现

void kunmap_high(struct page *page)
{
    unsigned long vaddr;
    unsigned long nr;                                                                                                                                                    
    unsigned long flags;
    int need_wakeup;
    unsigned int color = get_pkmap_color(page);
    wait_queue_head_t *pkmap_map_wait;
    lock_kmap_any(flags);
    vaddr = (unsigned long)page_address(page);
    BUG_ON(!vaddr);
    nr = PKMAP_NR(vaddr);
    /*
     * A count must never go down to zero
     * without a TLB flush!
     */
    need_wakeup = 0;
    switch (--pkmap_count[nr]) {
    case 0:
        BUG();
    case 1:
        /*
         * Avoid an unnecessary wake_up() function call.
         * The common case is pkmap_count[] == 1, but
         * no waiters.
         * The tasks queued in the wait-queue are guarded
         * by both the lock in the wait-queue-head and by
         * the kmap_lock.  As the kmap_lock is held here,
         * no need for the wait-queue-head's lock.  Simply
         * test if the queue is empty.
         */
        pkmap_map_wait = get_pkmap_wait_queue_head(color);
        need_wakeup = waitqueue_active(pkmap_map_wait);
    }
    unlock_kmap_any(flags);
    /* do wake-up, if needed, race-free outside of the spin lock */                                                                                                      
    if (need_wakeup)
        wake_up(pkmap_map_wait);
}
  • 1、通过get_pkmap_color(page)函数获取color=0,--pkmap_count[nr]表示释放
  • 2、通过page找到vaddr地址,如果释放不存在的page则BUG_ON,然后通过PKMAP_NR(addr)找到数组下标nr
  • 3、通过get_pkmap_wait_queue_head(color)获取等待队列的头,然后查看等待队列中是否有任务需要唤醒,如果有则把need_wakeup稍后去唤醒,如果空,则无需唤醒。

 

vmap实现

vmap()用于相对长时间的映射,可同时映射多个pages,


vmap函数


void *vmap(struct page **pages, unsigned int count,
       unsigned long flags, pgprot_t prot)
{
    struct vm_struct *area;
    unsigned long size;     /* In bytes */
    might_sleep();
    if (count > totalram_pages())
        return NULL;
    size = (unsigned long)count << PAGE_SHIFT;
    area = get_vm_area_caller(size, flags, __builtin_return_address(0));
    if (!area)
        return NULL;
    if (map_kernel_range((unsigned long)area->addr, size, pgprot_nx(prot),
            pages) < 0) {
        vunmap(area->addr);
        return NULL;
    }
    return area->addr;
}

1、先在vmalloc区域获取size大小的虚拟地址

2、找到虚拟地址后通过map_kernel_range函数把虚拟地址和物理地址绑定映射,即是填充PGD和PTE页表

vunmap函数


void vunmap(const void *addr)                                                                                                                                            
{       
    BUG_ON(in_interrupt());
    might_sleep();
    if (addr)
        __vunmap(addr, 0);
}

 

1、如果在中断上下文,直接BUG_ON

2、然后通过__vunmap函数释放,因为第二个参数为0,即不释放对应物理地址对应的物理page

 

ioremap

ioremap 函数,其功能是将给定的物理地址映射为虚拟地址。


ioremap函数

void __iomem *ioremap(resource_size_t res_cookie, size_t size)                                                                                                           
{
    return arch_ioremap_caller(res_cookie, size, MT_DEVICE,
                   __builtin_return_address(0));
}
void __iomem * (*arch_ioremap_caller)(phys_addr_t, size_t,                                                                                                               
                      unsigned int, void *) =
    __arm_ioremap_caller;
void __iomem *__arm_ioremap_caller(phys_addr_t phys_addr, size_t size,                                                                                                   
    unsigned int mtype, void *caller)
{   
    phys_addr_t last_addr;
    unsigned long offset = phys_addr & ~PAGE_MASK;
    unsigned long pfn = __phys_to_pfn(phys_addr);
    /*
     * Don't allow wraparound or zero size
     */
    last_addr = phys_addr + size - 1;
    if (!size || last_addr < phys_addr)
        return NULL;
    return __arm_ioremap_pfn_caller(pfn, offset, size, mtype,
            caller);
}
  • 1、phys_addr找到对应物理地址的页内偏移
  • 2、由phys_addr找到物理页帧号
  • 3、做些简单参数检查size非0及环绕

 

__arm_ioremap_pfn_caller函数

static void __iomem * __arm_ioremap_pfn_caller(unsigned long pfn,
    unsigned long offset, size_t size, unsigned int mtype, void *caller)
{
    const struct mem_type *type;
    int err;
    unsigned long addr;
    struct vm_struct *area;
    phys_addr_t paddr = __pfn_to_phys(pfn);
#ifndef CONFIG_ARM_LPAE
    /*
     * High mappings must be supersection aligned
     */
    if (pfn >= 0x100000 && (paddr & ~SUPERSECTION_MASK))
        return NULL;
#endif
    type = get_mem_type(mtype);
    if (!type)
        return NULL;
    /*
     * Page align the mapping size, taking account of any offset.                                                                                                        
     */
    size = PAGE_ALIGN(offset + size);
    /*
     * Try to reuse one of the static mapping whenever possible.
     */
    if (size && !(sizeof(phys_addr_t) == 4 && pfn >= 0x100000)) {
        struct static_vm *svm;
        svm = find_static_vm_paddr(paddr, size, mtype);
        if (svm) {
            addr = (unsigned long)svm->vm.addr;
            addr += paddr - svm->vm.phys_addr;
            return (void __iomem *) (offset + addr);
        }
    }
    /*
     * Don't allow RAM to be mapped with mismatched attributes - this
     * causes problems with ARMv6+
     */
    if (WARN_ON(pfn_valid(pfn) && mtype != MT_MEMORY_RW))
        return NULL;
    area = get_vm_area_caller(size, VM_IOREMAP, caller);
    if (!area)
        return NULL;
    addr = (unsigned long)area->addr;
    area->phys_addr = paddr;
      err = ioremap_page_range(addr, addr + size, paddr,
                     __pgprot(type->prot_pte));
    if (err) {
        vunmap((void *)addr);
        return NULL;
    }
    flush_cache_vmap(addr, addr + size);
    return (void __iomem *) (offset + addr);
}
  • 1、由pfn获取物理地址
  • 2、如果物理地址大于4G则需要16MB对齐
  • 3、由get_mem_type(mtype)函数获取内存类型,设置页表权限

    [MT_DEVICE] = {       /* Strongly ordered / ARMv6 shareddevice */
        .prot_pte   = PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED |
                  L_PTE_SHARED,
        .prot_l1    = PMD_TYPE_TABLE,
        .prot_sect  = PROT_SECT_DEVICE | PMD_SECT_S,
        .domain     = DOMAIN_IO,
    },
  • 4、如果页内偏移为0xff0,size为2,则只需要4k虚拟地址即可,如果size为0x10,则需要两4k大小的虚拟地址映射。
  • 5、无static mapping暂不分析
  • 6、mtype !=MT_MEMORY_RW成立,pfn_valid函数稍后重点分析
  • 7、通过get_vm_area_caller函数获取虚拟地址
  • 8、通过虚拟地址和物理地址及页表属性建立映射,填充PGD PTE页表。
  • 9、flush_cache_vmap刷cachevmap 

 

pfn_valid(pfn)

int pfn_valid(unsigned long pfn)
{
    phys_addr_t addr = __pfn_to_phys(pfn);
    if (__phys_to_pfn(addr) != pfn)                                                                                                                                      
        return 0;
    return memblock_is_map_memory(addr);
}
bool __init_memblock memblock_is_map_memory(phys_addr_t addr)                                                                                                            
{       
    int i = memblock_search(&memblock.memory, addr);
    if (i == -1)
        return false;
    return !memblock_is_nomap(&memblock.memory.regions[i]);
}
static int __init_memblock memblock_search(struct memblock_type *type, phys_addr_t addr)                                                                                 
{
    unsigned int left = 0, right = type->cnt;
    do {
        unsigned int mid = (right + left) / 2;
        if (addr < type->regions[mid].base)
            right = mid;
        else if (addr >= (type->regions[mid].base +
                  type->regions[mid].size))
            left = mid + 1;
        else
            return mid;
    } while (left < right);
    return -1;
}   
static inline bool memblock_is_nomap(struct memblock_region *m)                                                                                                          
{       
    return m->flags & MEMBLOCK_NOMAP;
}
  • 1、由pfn找到物理地址
  • 2、memblock_is_map_memory由此函数判断memory是否是map区域

如果在memblock.memory区域找到了,说明是memblock.memory区域,否则不是memory区域返回false=0,即WARN_ON不执行异常,如果是memblock.memory区域则需要判断此memblock是否是MEMBLOCK_NOMAP属性,如果是WARN_ON不执行异常,如果不是则触发异常警告。


void (*arch_iounmap)(volatile void __iomem *) = __iounmap;


void iounmap(volatile void __iomem *cookie)                                                                                                                              
{
    arch_iounmap(cookie);
}
void __iounmap(volatile void __iomem *io_addr)
{
    void *addr = (void *)(PAGE_MASK & (unsigned long)io_addr);
    struct static_vm *svm;

    /* If this is a static mapping, we must leave it alone */
    svm = find_static_vm_vaddr(addr);
    if (svm)
        return;

    vunmap(addr);
}
  • 1、无static_vm直接vunmap(addr)
  • 2、如果在中断上下文,直接BUG_ON
  • 3、然后通过__vunmap函数释放,因为第二个参数为0,即不释放对应物理地址对应的物理page

 

后记


本文重点分析了kmap vmap ioreamp的实现,其中vmap,ioremap中的部分函数在vmalloc的实现中已经分析过。对于kmap vmap ioremap的差异对比和使用场景这里暂未仔细做分析,因为如果你知道它的实现细节了,它的使用注意事项也就了然于胸了。从对内存管理的刚开始的懵懂到现在有种面朝大海春暖花开的感觉。

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值