Linux内存管理17(基于6.1内核)

Linux内存管理17(基于6.1内核)---kmap持久内核映射

 

一、高端内存与内核映射


尽管vmalloc函数族可用于从高端内存域向内核映射页帧(这些在内核空间中通常是无法直接看到的), 但这并不是这些函数的实际用途。

重要的是强调以下事实:内核提供了其他函数用于将ZONE_HIGHMEM页帧显式映射到内核空间, 这些函数与vmalloc机制无关. 因此, 这就造成了混乱。

而在高端内存的页不能永久地映射到内核地址空间。因此, 通过alloc_pages()函数以__GFP_HIGHMEM标志获得的内存页就不可能有逻辑地址。

在arm体系结构总, 高于896MB的所有物理内存的范围大都是高端内存, 它并不会永久地或自动映射到内核地址空间, 尽管arm处理器能够寻址物理RAM的范围达到4GB(启用PAE可以寻址64GB), 一旦这些页被分配, 就必须映射到内核的逻辑地址空间上. 在arm上, 高端地址的页被映射到内核地址空间(即虚拟地址空间的3GB~4GB)。

内核地址空间的最后128 MiB用于何种用途呢?

该部分有3个用途。

  1. 虚拟内存中连续、但物理内存中不连续的内存区,可以在vmalloc区域分配. 该机制通常用于用户过程, 内核自身会试图尽力避免非连续的物理地址。内核通常会成功,因为大部分大的内存块都在启动时分配给内核,那时内存的碎片尚不严重。但在已经运行了很长时间的系统上, 在内核需要物理内存时, 就可能出现可用空间不连续的情况. 此类情况, 主要出现在动态加载模块时。

  2. 持久映射用于将高端内存域中的非持久页映射到内核中。

  3. 固定映射是与物理地址空间中的固定页关联的虚拟地址空间项,但具体关联的页帧可以自由选择. 它与通过固定公式与物理内存关联的直接映射页相反,虚拟固定映射地址与物理内存位置之间的关联可以自行定义,关联建立后内核总是会注意到的。

 

在这里有两个预处理器符号很重要 __VMALLOC_RESERVE设置了vmalloc区域的长度, 而MAXMEM则表示内核可以直接寻址的物理内存的最大可能数量。

内核中, 将内存划分为各个区域是通过上图的各个常数控制的。根据内核和系统配置, 这些常数可能有不同的值。直接映射的边界由high_memory指定。

  1. 直接映射区
    线性空间中从3G开始最大896M的区间, 为直接内存映射区,该区域的线性地址和物理地址存在线性转换关系:线性地址=3G+物理地址。

  2. 动态内存映射区
    该区域由内核函数vmalloc来分配, 特点是 : 线性空间连续, 但是对应的物理空间不一定连续. vmalloc分配的线性地址所对应的物理页可能处于低端内存, 也可能处于高端内存。

  3. 永久内存映射区
    该区域可访问高端内存. 访问方法是使用alloc_page(_GFP_HIGHMEM)分配高端内存页或者使用kmap函数将分配到的高端内存映射到该区域。

  4. 固定映射区
    该区域和4G的顶端只有4k的隔离带,其每个地址项都服务于特定的用途,如ACPI_BASE等。

即内核对于低端内存, 不需要特殊的映射机制, 使用直接映射即可以访问普通内存区域, 而对于高端内存区域, 内核可以采用三种不同的机制将页框映射到高端内存:分别叫做永久内核映射临时内核映射以及非连续内存分配。

二、持久内核映射


如果需要将高端页帧长期映射(作为持久映射)到内核地址空间中, 必须使用kmap函数。 需要映射的页用指向page的指针指定,作为该函数的参数。该函数在有必要时创建一个映射(即,如果该页确实是高端页), 并返回数据的地址。

如果没有启用高端支持, 该函数的任务就比较简单。在这种情况下, 所有页都可以直接访问, 因此只需要返回页的地址, 无需显式创建一个映射.。

如果确实存在高端页, 情况会比较复杂。类似于vmalloc, 内核首先必须建立高端页和所映射到的地址之间的关联。 还必须在虚拟地址空间中分配一个区域以映射页帧, 最后, 内核必须记录该虚拟区域的哪些部分在使用中, 哪些仍然是空闲的

2.1 数据结构


vmalloc区域之后分配了一个区域, 从PKMAP_BASEFIXADDR_START. 该区域用于持久映射。 不同体系结构使用的方案是类似的。

永久内核映射允许内核建立高端页框到内核地址空间的长期映射。 他们使用着内核页表中一个专门的页表,其地址存放在变量pkmap_page_table中, 页表中的表项LAST_PKMAP。因此,内核一次最多访问2MB或4MB的高端内存。

 arch/arc/include/asm/highmem.h

#define PKMAP_BASE              (PAGE_OFFSET - PMD_SIZE)

页表映射的线性地址从PKMAP_BASE开始。 pkmap_count数组包含LAST_PKMAP个计数器,pkmap_page_table页表中的每一项都有一个。

 arch/arc/mm/highmem.c

extern pte_t * pkmap_page_table;

static noinline pte_t * __init alloc_kmap_pgtable(unsigned long kvaddr)
{
	pmd_t *pmd_k = pmd_off_k(kvaddr);
	pte_t *pte_k;

	pte_k = (pte_t *)memblock_alloc_low(PAGE_SIZE, PAGE_SIZE);
	if (!pte_k)
		panic("%s: Failed to allocate %lu bytes align=0x%lx\n",
		      __func__, PAGE_SIZE, PAGE_SIZE);

	pmd_populate_kernel(&init_mm, pmd_k, pte_k);
	return pte_k;
}

void __init kmap_init(void)
{
	/* Due to recursive include hell, we can't do this in processor.h */
	BUILD_BUG_ON(PAGE_OFFSET < (VMALLOC_END + FIXMAP_SIZE + PKMAP_SIZE));
	BUILD_BUG_ON(LAST_PKMAP > PTRS_PER_PTE);
	BUILD_BUG_ON(FIX_KMAP_SLOTS > PTRS_PER_PTE);

	pkmap_page_table = alloc_kmap_pgtable(PKMAP_BASE);
	alloc_kmap_pgtable(FIXMAP_BASE);
}

高端映射区逻辑页面的分配结构用分配表(pkmap_count)来描述,它有1024项,对应于映射区内不同的逻辑页面。当分配项的值等于0时为自由项,等于1时为缓冲项,大于1时为映射项。映射页面的分配基于分配表的扫描,当所有的自由项都用完时,系统将清除所有的缓冲项,如果连缓冲项都用完时,系统将进入等待状态。

 mm/highmem.c

/* 
高端映射区逻辑页面的分配结构用分配表(pkmap_count)来描述,它有1024项, 
对应于映射区内不同的逻辑页面。当分配项的值等于零时为自由项,等于1时为 
缓冲项,大于1时为映射项。映射页面的分配基于分配表的扫描,当所有的自由 
项都用完时,系统将清除所有的缓冲项,如果连缓冲项都用完时,系 
统将进入等待状态。 
*/  
static int pkmap_count[LAST_PKMAP];

pkmap_count是一容量为LAST_PKMAP的整数数组, 其中每个元素都对应于一个持久映射页。它实际上是被映射页的一个使用计数器,语义不太常见。

内核可以通过get_next_pkmap_nr获取到pkmap_count数组中元素的个数, 该函数定义mm/highmem.c

/*
 * Get next index for mapping inside PKMAP region for page with given color.
 */
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;
}

该计数器计算了内核使用该页的次数加1. 如果计数器值为2, 则内核中只有一处使用该映射页。 计数器值为5表示有4处使用。一般地说,计数器值为n代表内核中有n-1处使用该页。和通常的使用计数器一样, 0意味着相关的页没有使用.计数器值1有特殊语义。 这表示该位置关联的页已经映射, 但由于CPU的TLB没有更新而无法使用, 此时访问该页, 或者失败, 或者会访问到一个不正确的地址。

为了记录高端内存页框与永久内核映射包含的线性地址之间的联系,内核使用了page_address_htable散列表。 该表包含一个page_address_map数据结构,用于为高端内存中的每一个页框进行当前映射。而该数据结构还包含一个指向页描述符的指针和分配给该页框的线性地址。

内核利用page_address_map数据结构, 来建立物理内存页的page实例与其在虚似内存区中位置之间的关联。

 mm/highmem.c

/*
 * Describes one page->virtual association
 */
struct page_address_map
{
    struct page *page;
    void *virtual;
    struct list_head list;
};

该结构用于建立page-->virtual的映射(该结构由此得名).

字段描述
page是一个指向全局mem_map数组中的page实例的指针
virtual指定了该页在内核虚拟地址空间中分配的位置

为便于组织, 映射保存在散列表中, 结构中的链表元素用于建立溢出链表,以处理散列碰撞. 该散列表通过page_address_htable数组实现, mm/highmem.c

/*
 * Hash table bucket
 */
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_slot函数, mm/highmem.c

static struct page_address_slot *page_slot(const struct page *page)
{
    return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
}

2.2 page_address函数


page_address是一个前端函数, 使用上述数据结构确定给定page实例的线性地址, 该函数定义mm/highmem.c

/**
 * page_address - get the mapped virtual address of a page
 * @page: &struct page to get the virtual address of
 *
 * Returns the page's virtual 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;
				break;
			}
		}
	}

	spin_unlock_irqrestore(&pas->lock, flags);
	return ret;
}
EXPORT_SYMBOL(page_address);

page_address首先检查传递进来的page实例在普通内存还是在高端内存。

  • 如果是前者(普通内存区域), 页地址可以根据pagemem_map数组中的位置计算. 这个工作可以通过lowmem_page_address调用page_to_virt完成。

  • 对于后者, 可通过上述散列表查找虚拟地址。

2.3 kmap创建映射


1 kmap函数


为通过page指针建立映射, 必须使用kmap函数。

不同体系结构的定义可能不同, 但是大多数体系结构的定义都如下所示include/linux/highmem-internal.h

/*高端内存映射,运用数组进行操作分配情况 
分配好后需要加入哈希表中;*/  
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;
}

kmap函数只是一个page_address的前端,用于确认指定的页是否确实在高端内存域中. 否则, 结果返回page_address得到的地址. 如果确实在高端内存中, 则内核将工作委托给kmap_high

kmap_high的实现在mm/highmem.c定义如下

2 kmap_high函数


/**
 * kmap_high - map a highmem page into memory
 * @page: &struct page to map
 *
 * Returns the page's virtual memory address.
 *
 * We cannot call this from interrupts, as it may block.
 */
void *kmap_high(struct page *page)
{
    unsigned long vaddr;

    /*
     * For highmem pages, we can't trust "virtual" until
     * after we have the lock.
     */
    lock_kmap();    /*保护页表免受多处理器系统上的并发访问*/  

    /*检查是否已经被映射*/
    vaddr = (unsigned long)page_address(page);
    if (!vaddr) )/*  如果没有被映射  */    
        /*把页框的物理地址插入到pkmap_page_table的 
        一个项中并在page_address_htable散列表中加入一个 
        元素*/  
        vaddr = map_new_virtual(page);
    /*分配计数加一,此时流程都正确应该是2了*/  
    pkmap_count[PKMAP_NR(vaddr)]++;
    BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
    unlock_kmap();
    return (void*) vaddr;   ;/*返回地址*/ 
}

EXPORT_SYMBOL(kmap_high);

3 map_new_virtual函数


上文讨论的page_address函数首先检查该页是否已经映射. 如果它不对应到有效地址, 则必须使用map_new_virtual映射该页。

该函数定义mm/highmem.c

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);   /*加1,防止越界*/  
        /* 接下来判断什么时候last_pkmap_nr等于0,等于0就表示1023(LAST_PKMAP(1024)-1)个页表项已经被分配了 
        ,这时候就需要调用flush_all_zero_pkmaps()函数,把所有pkmap_count[] 计数为1的页表项在TLB里面的entry给flush掉 
        ,并重置为0,这就表示该页表项又可以用了,可能会有疑惑为什么不在把pkmap_count置为1的时候也 
        就是解除映射的同时把TLB也flush呢? 
        个人感觉有可能是为了效率的问题吧,毕竟等到不够的时候再刷新,效率要好点吧。*/  
        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;

        /*
         * Sleep for somebody else to unmap their entries
         */
        {
            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.*/  
    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,1不是表示不可用吗, 
    既然映射已经建立好了,应该赋值为2呀,其实这个操作 
    是在他的上层函数kmap_high里面完成的(pkmap_count[PKMAP_NR(vaddr)]++).*/  
    pkmap_count[last_pkmap_nr] = 1;
    /*到此为止,整个映射就完成了,再把page和对应的线性地址 
    加入到page_address_htable哈希链表里面就可以了*/  
    set_page_address(page, (void *)vaddr);

    return vaddr;
}
  1. 从最后使用的位置(保存在全局变量last_pkmap_nr中)开始,反向扫描pkmap_count数组, 直至找到一个空闲位置。 如果没有空闲位置,该函数进入睡眠状态,直至内核的另一部分执行解除映射操作腾出空位。 在到达pkmap_count的最大索引值时, 搜索从位置0开始。 在这种情况下, 还调用flush_all_zero_pkmaps函数刷出CPU高速缓存(读者稍后会看到这一点)。

  2. 修改内核的页表,将该页映射在指定位置。但尚未更新TLB。

  3. 新位置的使用计数器设置为1。如上所述,这意味着该页已分配但无法使用,因为TLB项未更新。

  4. set_page_address将该页添加到持久内核映射的数据结构。
    该函数返回新映射页的虚拟地址. 在不需要高端内存页的体系结构上(或没有设置CONFIG_HIGHMEM),则使用通用版本的kmap返回页的地址,且不修改虚拟内存。

2.4 kunmap解除映射


kmap映射的页, 如果不再需要, 必须用kunmap解除映射. 照例, 该函数首先检查相关的页(由page实例标识)是否确实在高端内存中. 倘若如此, 则实际工作委托给mm/highmem.c中的kunmap_high, 该函数的主要任务是将pkmap_count数组中对应位置在计数器减1。

该机制永远不能将计数器值降低到小于1. 这意味着相关的页没有释放。因为对使用计数器进行了额外的加1操作, 正如前文的讨论, 这是为确保CPU高速缓存的正确处理。

也在上文提到的flush_all_zero_pkmaps是最终释放映射的关键. 在map_new_virtual从头开始搜索空闲位置时, 总是调用该函数。

它负责以下3个操作:

  1. flush_cache_kmaps在内核映射上执行刷出(在需要显式刷出的大多数体系结构上,将使用flush_cache_all刷出CPU的全部的高速缓存), 因为内核的全局页表已经修改。

  2. 扫描整个pkmap_count数组. 计数器值为1的项设置为0,从页表删除相关的项, 最后删除该映射。

  3. 最后, 使用flush_tlb_kernel_range函数刷出所有与PKMAP区域相关的TLB项。

1 kunmap函数


同kmap类似, 每个体系结构都应该实现自己的kmap函数, 大多数体系结构的定义都如下所示,include/linux/highmem-internal.h

static inline void kunmap(struct page *page)
{
	might_sleep();
	if (!PageHighMem(page))
		return;
	kunmap_high(page);
}

内核首先检查待释放内存区域是不是在高端内存区域:

  • 如果内存区域在普通内存区, 则内核并没有通过kmap_high对其建立持久的内核映射, 当然也无需用kunmap_high释放。

  • 如果内存区域在高端内存区, 则内核通过kunmap_high释放该内存空间。

2 kunmap_high函数


kunmap_high函数定义mm/highmem.c

#ifdef CONFIG_HIGHMEM
/**
 * kunmap_high - unmap a highmem page into memory
 * @page: &struct page to unmap
 *
 * If ARCH_NEEDS_KMAP_HIGH_GET is not defined then this may be called
 * only from user context.
 */
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]) {    /*减小这个值,因为在映射的时候对其进行了加2*/  
    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);
}

EXPORT_SYMBOL(kunmap_high);
#endif

三、临时内核映射


刚才描述的kmap函数不能用于中断处理程序, 因为它可能进入睡眠状态。如果pkmap数组中没有空闲位置, 该函数会进入睡眠状态, 直至情形有所改善。

因此内核提供了一个备选的映射函数, 其执行是原子的, 逻辑上称为kmap_atomic。该函数的一个主要优点是它比普通的kmap快速。但它不能用于可能进入睡眠的代码. 因此, 它对于很快就需要一个临时页的简短代码,是非常理想的。

3.1 kmap_atomic函数


 include/linux/highmem-internal.h

page是一个指向高端内存页的管理结构的指针。

static inline void *kmap_atomic_prot(struct page *page, pgprot_t prot)
{
	if (IS_ENABLED(CONFIG_PREEMPT_RT))
		migrate_disable();
	else
		preempt_disable();

	pagefault_disable();
	return __kmap_local_page_prot(page, prot);
}

static inline void *kmap_atomic(struct page *page)
{
	return kmap_atomic_prot(page, kmap_prot);
}

这个函数不会被阻塞, 因此可以用在中断上下文和起亚不能重新调度的地方。它也禁止内核抢占, 这是有必要的, 因此映射对每个处理器都是唯一的(调度可能对哪个处理器执行哪个进程做变动)。

3.2 kunmap_atomic函数


可以通过函数kunmap_atomic取消映射,include/linux/highmem-internal.h

/*
 * Prevent people trying to call kunmap_atomic() as if it were kunmap()
 * kunmap_atomic() should get the return value of kmap_atomic, not the page.
 */
#define kunmap_atomic(addr)                     \
do {                                \
    BUILD_BUG_ON(__same_type((addr), struct page *));       \
    __kunmap_atomic(addr);                  \
} while (0)

这个函数也不会阻塞。在很多体系结构中, 除非激活了内核抢占, 否则kunmap_atomic根本无事可做, 因为只有在下一个临时映射到来前上一个临时映射才有效。 因此, 内核完全可以”忘掉”kmap_atomic映射, kunmap_atomic也无需做什么实际的事情。下一个原子映射将自动覆盖前一个映射。

四、总结

在 Linux 6.1 内核中,高端内存映射(High Memory Mapping)依旧是一个重要的概念,尤其是在 32 位架构中,内核需要通过映射高端内存区域来处理超过内核虚拟地址空间限制(通常是 4GB)之外的物理内存。kmap() 是一个关键的内核函数,它为内核提供了对高端内存的访问能力。

高端内存(High Memory)通常指的是物理内存中无法直接映射到内核虚拟地址空间的部分。对于 32 位系统,内核的虚拟地址空间通常受到 4GB 限制,虽然系统可以有更多的物理内存,但超出 4GB 的内存无法直接访问。为了处理这种情况,Linux 内核使用了页表映射和分页机制来动态映射高端内存,使得内核能够访问这些区域。

kmap() 函数

kmap() 是 Linux 内核中的一个函数,用于将高端内存区域(即 ZONE_HIGHMEM)的物理页映射到内核虚拟地址空间。这使得内核能够访问那些高于 ZONE_NORMAL 区域的内存,而无需每次都通过内存分页的方式来进行映射。

kmap() 的作用

  • 内存映射kmap() 将物理内存的某一页(通常是高端内存)映射到内核虚拟地址空间,以便内核可以直接访问这部分内存。
  • 临时映射kmap() 会为每个页面创建一个临时的虚拟地址映射,并在使用完该页面后调用 kunmap() 来解除映射。

在 32 位系统中,kmap() 可以将高端内存页动态映射到内核地址空间的某个区域(如 VMALLOC 区域)。这种方式能够让内核以较低的开销访问大量的高端内存,而不需要频繁地进行复杂的分页操作。

kmap() 和持久内核映射

对于一些需要长期映射的高端内存,Linux 提供了持久内核映射(Persistent Kernel Mapping)机制。持久映射通常意味着内存页将被长期映射到内核的虚拟地址空间,而不需要每次访问时都进行动态映射和解除映射的操作。

kmap()kmap_atomic() 的区别

  • kmap():通常用于对高端内存的普通映射,适用于那些只需要短时间访问的场景,映射完成后需要通过 kunmap() 来解除映射。

  • kmap_atomic():通常用于临时性地映射高端内存,内核需要在很短的时间内访问这些内存,并且它是在原子上下文中使用的,适合于底层驱动或中断处理等情况。

对于需要“持久性”的映射,Linux 内核可能使用不同的机制来实现,尤其是通过 kmap_atomic() 或者其他持久映射的方式,使得内核可以在不解除映射的情况下,持久访问高端内存。

kmap() 和 持久映射的实际应用

持久映射通常发生在以下几种场景:

  • 内存池管理:例如,在内存池(memory pool)中,内核可能需要长时间访问某些高端内存区域。通过持久映射,这些内存区域可以被映射并持续使用。

  • 设备驱动:在一些特殊的设备驱动中,需要对大块的内存区域进行频繁操作。为了提高性能,可以使用持久映射,避免反复的映射和解除映射操作。

  • DMA(直接内存访问):在需要通过设备进行 DMA 操作时,内核可能需要长期保持某些内存区域的映射,以便硬件可以直接访问。

kmap()kunmap() 的配对使用

为了确保系统的稳定性,kmap() 需要配对使用 kunmap()kunmap() 用于解除高端内存页的映射,释放虚拟地址空间。这是非常重要的,因为内存映射的频繁和不当解除映射可能导致内存泄漏或内存碎片化。

  • kmap() 使用:通常在访问高端内存时调用,如对内存页面进行读取或写入操作。

  • kunmap() 使用:当内核操作完成后,必须调用 kunmap() 来解除对高端内存的映射。

示例:使用 kmap()kunmap()

void *virt_addr;
struct page *page; // 假设这是一个高端内存页

// 将高端内存页映射到内核虚拟地址空间
virt_addr = kmap(page);

// 使用映射的内存进行操作
memcpy(virt_addr, source, PAGE_SIZE);

// 完成后解除映射
kunmap(page);

在上述代码中,kmap() 将物理页映射到内核虚拟地址空间,kunmap() 用于解除映射。

内核 6.1 中的 kmap 机制优化

在 Linux 6.1 内核中,内存映射和管理机制有所优化,特别是对高端内存的管理。内核进一步优化了 kmap()kunmap() 的效率,减少了高端内存页映射的开销,同时改进了对高端内存的动态管理。这些改进提高了内存管理的效率,尤其在系统内存紧张或者高端内存使用频繁的场景中。

持久映射的优化

在 6.1 版本中,内核增加了一些优化,使得在高端内存映射时能够更好地平衡性能和资源使用,尤其是在持久映射的情况下,内核不再需要频繁地进行映射/解除映射操作,从而减少了映射所带来的开销。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值