我正在寻找 DAX(直接访问),并看到它被引入作为 XIP(就地执行)的替代品;但是,我怀疑它是否确实使应用程序在不复制到 RAM 的情况下执行。它说它是“直接访问文件”,但可执行文件也是内核的文件,不是吗?那么它是否使内核执行文件而不将它们复制到RAM?如果是,它是如何运作的?它是否保留 .text 区域,但创建 .data 区域的副本?
我有一个实验设置:我配置了 Linux 内核 4.6.2,并支持 DAX。创建了一个 ram 支持的块设备。使用 dax 选项安装 ramdisk:
# mount -t ramfs -o dax,size=8m ext2 /ramdisk
# mount
rootfs on / type rootfs (rw,size=59124k,nr_inodes=14781)
proc on /proc type proc (rw,relatime)
tmpfs on /tmp type tmpfs (rw,relatime)
ext2 on /ramdisk type ramfs (rw,relatime,dax,size=8m)
#
现在,我已将 ramfs 安装到 /ramdisk,使用 ext2 格式化并具有 dax 支持。如果我现在将应用程序复制到 /ramdisk 并执行;我如何确保它不会被复制到 RAM 上的任何其他位置并从那里执行?
不幸的是,dax 的文档太少了。它是内核解释说:
对于类似内存的块设备,页面缓存页面将是原始存储的不必要的副本。 DAX 代码通过直接对存储设备执行读取和写入来删除额外的副本。对于文件映射,存储设备直接映射到用户空间。
这看起来好像它会给我执行,而无需将可执行文件复制到 RAM 中的第二个位置。不过,也有人说:
即使内核或其模块存储在支持 DAX 的块设备上支持 DAX 的文件系统上,它们仍然会被复制到 RAM 中。
总之,我对 DAX 功能感到困惑,并且好奇它是否可以为我提供一种执行应用程序的方法,而无需将它们复制到 RAM 上的其他位置(复制到缓存对我来说超出了主题)。我很高兴听到对其工作原理的解释。
答案1
在讨论你的例子之前,有一点免责声明:这是现实的一个简化版本,有很多极端情况和例外我没有解释,但它应该足以让你理解正在发生的事情......
块设备
让您感到困惑的是滥用“块设备”术语。块设备通常是 HDD、CD、SSD...顾名思义,您无法向这些设备读取/写入单个字节,您需要将它们写入块(通常大小为 512 字节)。
块设备有几个寄存器和小内存区域映射到处理器地址空间,可用于读取设备状态、发送命令等。但是,它们(通常)不提供直接访问其所保存数据的方法。这通常是通过向设备发送命令并等待硬件中断(表示 DMA 操作(读取或写入)已完成)来完成的。
所以你看,对于这些类型的设备,不使用主存储器(DRAM)是有些困难(如果不是不可能的话),因为它们的操作涉及 DMA 操作等。 DAX 在这些情况下所做的就是消除访问数据所涉及的一些开销,但仅此而已。
DIMM 格式* NVM
然而,最近市场上推出了一些 DIMM 格式* 非易失性存储器 (NVM)。这些设备将其全部内容映射到处理器地址空间,因此可以被访问直接地由处理器通过存储和加载指令。内核甚至不需要知道这些设备正在被访问:从所有意图和目的来看,就好像进程正在访问常规 DRAM 支持的内存页面。
*DIMM 格式只是一个示例。还有一些其他现有的接口,例如 PCI,就是这样做的......
令人困惑的部分
混乱来了......直到最近,“存储设备”实际上是“块设备”的同义词。 Linux 内核将这些新的 NVM 识别为存储设备/块设备,并通过在 /dev 中创建条目来相应地对待它们,就像对待 SSD 一样。 (如果您没有这些 NVM 支持的设备之一,您可以通过指定应将常规 DRAM 的给定内存范围视为 NVM 来进行模拟。看这里有关如何执行此操作的更多信息。)
如果您在这样的设备上创建文件系统,它将像使用常规 HDD 一样工作。它将尝试通过将内容缓存到 DRAM 来提高性能。支持 DAX 的文件系统所做的是避免创建缓存,这本来是为了加快访问速度,但在这些情况下很可能会降低性能。
即使内核或其模块存储在支持 DAX 的块设备上支持 DAX 的文件系统上,它们仍然会被复制到 RAM 中。
我确实找不到这种行为的坚实原因,但我猜这是出于安全和性能原因,以保证内核和内核模块不会从慢速(比 DRAM 慢)设备运行,并且它们的内容在内核执行期间不会被弄乱。
但是,只要使用 NVM 支持的内存直接从 NVM 运行可执行文件,就不会有任何问题,只要它保留在用户空间中即可。
看看项目Pmem.io来自英特尔和阿特拉斯来自惠普。它们是专门为此类事情创建的编程接口。
现在关于你的例子:
# mount -t ramfs -o dax,size=8m ext2 /ramdisk
# mount
rootfs on / type rootfs (rw,size=59124k,nr_inodes=14781)
proc on /proc type proc (rw,relatime)
tmpfs on /tmp type tmpfs (rw,relatime)
ext2 on /ramdisk type ramfs (rw,relatime,dax,size=8m)
#
您没有创建 RAM 支持的 EXT2 文件系统。您正在使用虚拟名称 ext2 的 ramfs 创建一个 RAM 支持的文件系统。如果你像这样安装它没有什么区别:
# mount -t ramfs -o dax,size=8m winter_is_coming /ramdisk
答案2
它是否保留 .text 区域,但创建 .data 区域的副本?
无论如何,其工作方式与exec()
相同。这些页面以只读方式映射到进程的虚拟地址空间。因此写入会导致页错误中断。这些页面错误的处理方式描述为mmap()
MAP_PRIVATE
写时复制。
对于 DAX,虚拟页面一开始会映射到设备上的物理页面。但是 MAP_PRIVATE 上的写页错误会将页数据复制到 RAM 中的新页。 (然后相应地更新进程的映射,并重新启动中断的程序指令)。
请注意,DAX 是 XIP 的泛化,允许写入和读取,即 MAP_SHARED 和 MAP_PRIVATE。例如,MAP_SHARED 可用于数据库文件。
其实.text
也可能是写在共享库的情况下。不是位置无关可执行文件并且包含对自身的引用的库,需要根据相关库加载的地址来更新这些引用。这个过程被称为“重定位”。库还引用其他库,例如 libc;更新这些引用被称为“符号解析”。
即使内核或其模块存储在支持 DAX 的块设备上支持 DAX 的文件系统上,它们仍然会被复制到 RAM 中。
内核模块很特殊。它们也需要符号解析。但是,内核不使用 COW。 (更一般地说,它的代码和数据段不使用请求分页)。内核内部的页面错误是致命的,因为处理它们可能会导致无限递归!因此,在 DAX 之前,很明显内核模块需要完整复制到 RAM 中。内核代码和数据段较小;当 DAX 被实现时,在具有字节可寻址存储的服务器上进行修改是没有任何好处的。
内核本身在历史上是被压缩的,显然它是被解压到RAM中的。
也就是说,XIP是支持的对于未压缩的内核。这通常用于“嵌入式”系统,即非常有限的硬件。到那时,与使用可加载模块相比,内置大部分所需的代码可能不是问题。