在Linux内核中,virt_to_phys和CPU的MMU之间有什么关系?

15
我正在阅读有关Linux内存管理的内容。我知道Linux内核负责创建和维护页表,但利用CPU的内存管理单元(MMU)将进程的虚拟内存访问转换为相应的物理内存访问。
但是,我也知道内核可以使用一些函数来管理内存,例如virt_to_phys()virt_to_page()__pa()等。
例如:
static inline unsigned long virt_to_phys(volatile void *address)
{
    return __pa(address);
}

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

我对它们感到非常困惑。请帮助我展示MMU的翻译和内核的翻译之间的关系并区分它们?


(MMU是内存管理单元,kernel指操作系统内核)
6个回答

10

在使用虚拟内存系统时,操作系统内核负责建立物理地址与虚拟地址之间的映射关系。

然而,当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分配的内存,并不能将任意物理地址转换为虚拟地址。该转换依赖于查找页面表映射。

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


您好,Gabriel先生,我理解内核负责设置页表和映射物理地址和虚拟地址之间的关系,这是软件方面的。在硬件方面,CPU将加载这些指令,并通过MMU从虚拟地址翻译成物理地址以访问存储器。但是我对内核的翻译还不太清楚,能否详细解释一下内核的工作原理呢?例如,在调用virt_to_phys()后,内核是否可以立即获取物理地址?这个物理地址是否类似于MMU翻译后的地址? - Tiktac
@Sam,我添加了一些内核代码的细节,以帮助解释你提到的函数是如何工作的。 - Gabriel Southern
嗨,Gabriel,那么可以说内核只负责创建和维护页表,这意味着一些函数仅在引导或创建进程阶段由内核使用以创建页表,内核使用它们来知道和配置每个虚拟地址区域将映射到哪个物理地址区域?内核中标识映射段中的所有地址都将通过__pa()函数映射到物理地址,并且其他区域中的所有地址都将映射到其他物理区域(因为映射段将通过kmap()映射到物理地址)?我的理解有误吗? - Tiktac

2

MMU地址转换是一种硬件(CPU)行为。必须进行转换,因为物理地址是硬件用于访问内存的有效地址。另一方面,内核函数例如va_to_pa()被用于将内核逻辑地址(VA)转换为物理地址(PA),这意味着内核使用的是虚拟地址而非物理地址,尽管VA和PA之间只是一个常量偏移。

内核指令和数据位于虚拟地址中,但内核需要使用物理地址来进行许多操作,例如准备页表条目、获取设备的DMA地址等等。所以内核需要va_to_pa()等函数。


谢谢你的回答!我想深入了解内存,但感觉很困难... - Tiktac
我认为最好的方法是了解特定架构的底层硬件机制(尽管大多数CPU具有相同的概念),例如TLB环模式(特权)虚拟地址空间 - Chris Tsui

1
据我了解,在内核端使用物理地址仅用于参考,大多数情况下实际更改/移动时会被转换回来。其中有许多原因,其中之一是内存可能会被重新分配为其他目的,例如将数据从物理内存移动到页面文件(磁盘内存),或者如果需要更多空间来存储该数据,则在物理内存分配中移动。因此,如果内核只是要移动它而不告诉您,则不应使用物理地址。 这里有一个关于该主题的有趣片段。 还有更多详细信息在这里。

1
可能是关于Linux内核中内存最全面的指南在这里(PDF)。或者如果您更喜欢--在HTML中。 - Sam Protsenko
@SamProtsenko 那绝对包含了你所想知道的一切,甚至到门级别。第三部分(虚拟内存)稍微涉及到了这个问题,但没有深入探讨为什么以及Linux特定的细节。 - Ian M

1
virt_to_phys()和其他函数实际上利用了Page表的不同属性,例如PAGE_OFFSET等。这些Page表是由内核的内存管理子系统创建的,但它们又利用MMU硬件来读/写主内存中的物理页面。
您还可以参考其他文档,其中之一是: https://www.kernel.org/doc/gorman/html/understand/understand006.html

1

以下是要点。

1.) 内核和其他软件以虚拟地址为基础。每次需要查找相应的物理地址,都需要进行硬件页表查找(或TLB获取)。

2.) 在引导时,内核建立虚拟映射:简单地说,它将地址0x0..n映射到0xc0000000..0xc0000000 + n(所谓的低内存)。

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

virt_to_page(), __pa(), ...

它的意思是

virtual address = physical address + some offset

因此,在内核代码中,您可以轻松地获取与低内存页面相对应的物理/虚拟地址(MMU每次使用通用机制即页面表遍历)。这个偏移量只是惯例,没有更多的含义。


0
你可以阅读《深入理解Linux内核》这本书以获取更多信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接