virt_to_phys和Linux内核中CPU的MMU有什么关系?

What is the relation between virt_to_phys and the CPU's MMU in the Linux kernel?

我正在阅读 Linux 内存管理。我知道

The Linux kernel is responsible for creating and maintaining page tables but employs the CPU’s memory management unit (MMU) to translate the virtual memory accesses of a process into corresponding physical memory accesses.

但是,我也知道内核可以使用它的一些函数来管理内存,例如 virt_to_phys()virt_to_page()__pa()、...

示例:

static inline unsigned long virt_to_phys(volatile void *address)
{
    return __pa(address);
}

用于将虚拟地址转换为物理地址。

我对他们很困惑。请帮我看看MMU的翻译和内核的翻译之间的关系并区分它们?

根据我的理解,在内核端对物理地址的任何使用仅供参考,很可能只是为任何实际 changes/moves 翻译回来。有很多原因,其中一个是内存可能会被重新分配用于另一个目的,IE 将数据从物理内存移动到页面文件(磁盘内存),或者在物理内存分配内移动,如果更多 space 是该数据所必需的。因此,如果内核只是要在不告诉您的情况下移动物理地址,那么使用物理地址并不是一件好事。 Here is an interesting bit on the subject. And a lot more detail here.

virt_to_phys() 等实际上利用了页表的不同属性,如 PAGE_OFFSET 等。这些页表是由内核的内存管理子系统创建的,但它们反过来利用 MMU 硬件 read/write 主内存中的物理页面。

您还可以参考可用的不同文档,其中之一是: https://www.kernel.org/doc/gorman/html/understand/understand006.html

这里是要点。

1.) 内核和其他软件在虚拟地址方面的工作。每次查找相应的物理地址硬件页面都需要 tables 查找(或 TLB 获取)。

2.) 在启动时内核建立虚拟映射:为简单起见,我会说它将内存映射到地址 0x0 .. n 到 0xc0000000 .. 0xc0000000 + n(所谓的低内存)。

3.) 建立的映射是静态的。对于低内存地址,以下函数是 suitable:

virt_to_page(), __pa(), ...

表示

virtual address = physical address + some offset

因此您可以轻松地在内核代码中获取相应低内存页面的 phys/virt 地址(MMU 使用通用机制,即页面 table 每次都遍历)。这个偏移量只是惯例,仅此而已。

在具有虚拟内存的系统中,OS 内核负责建立物理地址和虚拟地址之间的映射。

但是,当 CPU 执行访问内存的指令时,CPU 执行从进程的虚拟地址到指示内存中实际位置的物理地址的转换。

您提到的函数可以在内核代码中使用,以获取内核代码中使用的某些地址的虚拟地址到物理地址的转换。例如,对于 x86 目标,您可以在 io.h:

中查看 virt_to_phys 上的定义
/**
 *  virt_to_phys    -   map virtual addresses to physical
 *  @address: address to remap
 *
 *  The returned physical address is the physical (CPU) mapping for
 *  the memory address given. It is only valid to use this function on
 *  addresses directly mapped or allocated via kmalloc.
 *
 *  This function does not give bus mappings for DMA transfers. In
 *  almost all conceivable cases a device driver should not be using
 *  this function
 */

static inline phys_addr_t virt_to_phys(volatile void *address)
{
    return __pa(address);
}

如果您遵循 __pa(address) 的定义,您会看到它最终调用 __phys_addr,其定义为:

unsigned long __phys_addr(unsigned long x)
{
    if (x >= __START_KERNEL_map) {
        x -= __START_KERNEL_map;
        VIRTUAL_BUG_ON(x >= KERNEL_IMAGE_SIZE);
        x += phys_base;
    } else {
        VIRTUAL_BUG_ON(x < PAGE_OFFSET);
        x -= PAGE_OFFSET;
        VIRTUAL_BUG_ON(!phys_addr_valid(x));
    }
    return x;
}

所以你可以看到内核正在使用偏移量从虚拟地址计算物理地址。根据体系结构,为翻译编译的代码将有所不同。正如 virt_to_phys 的评论所提到的,这仅适用于通过 kmalloc 直接映射或分配的内核中的内存,它不会将任意物理地址转换为虚拟地址。该翻译依赖于查找页面 table 映射。

在任何一种情况下,作为内核的一部分在 CPU 上执行的实际指令仍将依赖 CPU 的 MMU 将它们操作的虚拟地址转换为数据实际位于内存中的物理地址。

MMU 地址转换是一种硬件(cpu)行为。必须进行转换,因为物理地址是硬件可以用来访问内存的有效地址。另一方面,va_to_pa()这样的内核函数用于将内核逻辑地址(va)转换为物理地址(pa),这意味着内核使用虚拟地址而不是物理地址,尽管它只是在va和每年

内核指令和数据在虚拟地址中,但是内核使用物理地址做很多事情,例如准备页面table入口,获取设备的dma地址等等。所以内核需要像 va_to_pa().

这样的函数

您可以阅读《理解Linux内核》一书以获取更多信息。