Kmap

Kmap


void *kmap(struct page *page)

这个函数在高端内存或低端内存上都能用。如果page结构对应的是低端内存中的一页,函数只会单纯的返回该页的虚拟地址。如果页位于高端内存,则会建立一个永久映射,再返回地址。这个函数可以睡眠,因此kmap()只能在进程上下文中。
因为允许永久映射是有限的,当不需要高端内存时,应该接触映射,可以通过以下函数解除。

void kunmap(struct page *page)

高端内存

Linux将内核地址空间划分为三部分ZONE_DMA ZONE_NORMAL ZONE_HIGHMEM,高端内存HIGH_MEM地址空间范围为 0xF8000000 ~ 0xFFFFFFFF(896MB~1024MB)。那么如内核是如何借助128MB高端内存地址空间是如何实现访问可以所有物理内存?
当内核想访问高于896MB物理地址内存时,从0xF8000000 ~ 0xFFFFFFFF地址空间范围内找一段相应大小空闲的逻辑地址空间,借用一会。借用这段逻辑地址空间,建立映射到想访问的那段物理内存(即填充内核PTE页面表),临时用一会,用完后归还。这样别人也可以借用这段地址空间访问其他物理内存,实现了使用有限的地址空间,访问所有所有物理内存。如下图。
这里写图片描述

例 如内核想访问2G开始的一段大小为1MB的物理内存,即物理地址范围为0×80000000 ~ 0x800FFFFF。访问之前先找到一段1MB大小的空闲地址空间,假设找到的空闲地址空间为0xF8700000 ~ 0xF87FFFFF,用这1MB的逻辑地址空间映射到物理地址空间0×80000000 ~ 0x800FFFFF的内存。映射关系如下:

逻辑地址物理内存地址
0xF87000000×80000000
0xF87000010×80000001
0xF87000020×80000002
0xF87FFFFF0x800FFFFF

当内核访问完0×80000000 ~ 0x800FFFFF物理内存后,就将0xF8700000 ~ 0xF87FFFFF内核线性空间释放。这样其他进程或代码也可以使用0xF8700000 ~ 0xF87FFFFF这段地址访问其他物理内存。


具体实现

void *kmap(struct page *page)
{
    might_sleep();
    if (!PageHighMem(page))//如果是低端内存,直接返回内存页对应的虚拟地址。
        return page_address(page);
    return kmap_high(page);//否则 需要进行高端内存映射
}

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)//如果上一步失败
        vaddr = map_new_virtual(page);//建立新的映射
    pkmap_count[PKMAP_NR(vaddr)]++;//如果建立成功,引用加1,表示已被映射,2表示此映射还有额外的引用
    BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);
    unlock_kmap();
    return (void*) vaddr;
}

static inline unsigned long map_new_virtual(struct page *page)
{
    unsigned long vaddr;
    int count;

start:
    count = LAST_PKMAP;
    /* Find an empty entry */
    for (;;) {
        last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;
        if (!last_pkmap_nr) {
            flush_all_zero_pkmaps();//先解除一部分映射
            count = LAST_PKMAP;
        }
        if (!pkmap_count[last_pkmap_nr])//计数为零 证明没有人在使用
            break;  /* Found a usable entry */
        if (--count)//遍历整个kmap空间,如果不存在未使用的映射,就睡眠等待kunmap
            continue;

        /*
         * Sleep for somebody else to unmap their entries
         */
        {
            DECLARE_WAITQUEUE(wait, current);

            __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;
}

实现流程图

这里写图片描述


参考文档

linux 用户空间与内核空间——高端内存详解

在 Linux 内核中,DMA(Direct Memory Access)操作要求内存区域在物理上是连续的,并且可以被外设访问。为了实现这一点,内核提供了一系列机制来分配和映射 DMA 缓冲区。其中,`kmap()` 通常用于将高端内存(highmem)页映射到内核地址空间,但不推荐用于 DMA 缓冲区分配。 ### `dma kmap` 的实现原理 DMA 缓冲区的映射通常涉及两个关键步骤:内存分配和地址转换。在某些情况下,需要将 `struct page` 指针转换为虚拟地址,以便访问该页的内容。`kmap()` 就是用于将 `struct page` 映射到内核虚拟地址空间的函数。然而,由于 DMA 操作需要物理地址,且某些硬件可能依赖 IOMMU(如 PCI 设备),直接使用 `kmap()` 返回的虚拟地址进行 DMA 操作是不可靠的,甚至可能导致数据不一致或硬件访问失败[^1]。 DMA 缓冲区的正确映射通常使用 `dma_map_single()` 或 `dma_alloc_coherent()` 等接口,它们会处理缓存一致性、IOMMU 映射以及物理地址的正确获取。这些接口返回的地址是设备可访问的总线地址,而不是直接的内核虚拟地址。如果确实需要访问 DMA 缓冲区的内容,应使用 `kmap()` 或 `vmap()` 来获取虚拟地址,但必须确保内存一致性,例如通过调用 `dma_sync_single_for_cpu()` 和 `dma_sync_single_for_device()` 来同步缓存[^1]。 ### `dma kmap` 的使用方法 在实际驱动开发中,若需要使用 `kmap()` 对 DMA 缓冲区进行访问,应遵循以下步骤: 1. 分配 DMA 缓冲区,通常使用 `dma_alloc_coherent()` 或 `dma_pool_alloc()` 获取一致性内存。 2. 获取缓冲区的 `struct page` 指针,例如通过 `virt_to_page()`。 3. 调用 `kmap()` 将 `struct page` 映射到内核虚拟地址空间。 4. 访问缓冲区内容。 5. 完成访问后调用 `kunmap()` 解除映射。 以下是一个示例代码: ```c struct device *dev; // 假设为设备指针 size_t size = 4096; void *dma_buf; dma_addr_t dma_handle; // 分配一致性DMA内存 dma_buf = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL); if (!dma_buf) { // 处理分配失败 } // 获取struct page指针 struct page *page = virt_to_page(dma_buf); // 映射到内核虚拟地址空间 void *vaddr = kmap(page); if (!vaddr) { // 处理映射失败 } // 使用vaddr访问DMA缓冲区 memset(vaddr, 0, size); // 解除映射 kunmap(page); // 释放DMA内存 dma_free_coherent(dev, size, dma_buf, dma_handle); ``` 需要注意的是,即使使用了 `kmap()`,仍需确保缓存一致性。例如,在 DMA 传输前调用 `dma_sync_single_for_device()`,在 CPU 访问前调用 `dma_sync_single_for_cpu()`。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值