有没有什么API来确定Linux中的虚拟地址的物理地址?

Linux操作系统中有没有用于从虚拟地址确定物理地址的API?

Solutions Collecting From Web of "有没有什么API来确定Linux中的虚拟地址的物理地址?"

内核和用户空间使用由内存管理硬件映射到物理地址的虚拟地址(也称为线性地址)。 这个映射是由操作系统设置的页表定义的。

DMA设备使用总线地址。 在i386 PC上,总线地址与物理地址相同,但是其他架构可能有特殊的地址映射硬件将总线地址转换为物理地址。

在Linux中,您可以使用asm/io.h这些函数:

  • virt_to_phys(到virt_addr_size);
  • phys_to_virt(phys_addr);
  • virt_to_bus(到virt_addr_size);
  • bus_to_virt(bus_addr);

所有这些都是关于访问普通内存的。 PCI或ISA总线上还有“共享内存”。 它可以使用ioremap()映射到32位地址空间中,然后通过readb(),writeb()(等)函数使用。

生活由于周围存在各种缓存这一事实而变得复杂,因此访问相同物理地址的不同方式不需要得到相同的结果。

另外,虚拟地址后面的真实物理地址可以改变。 甚至更多 – 在访问内存之前,不会有与虚拟地址关联的地址。

至于用户空间的API,我没有意识到。

正如之前所回答的,正常的程序不需要担心物理地址在虚拟地址空间中的运行。 此外,不是每个虚拟地址都有一个物理地址,可能属于映射文件或交换页面。 然而,即使在用户区域,有时看到这个映射也许是有趣的。

为此,Linux内核通过/proc中的一组文件将其映射到userland。 文档可以在这里找到。 简短的摘要:

  1. /proc/$pid/maps提供了虚拟地址的映射列表以及附加信息,例如映射文件的相应文件。
  2. /proc/$pid/pagemap提供关于每个映射页面的更多信息,包括物理地址(如果存在)。

本网站提供了一个C程序,它使用这个接口转储所有正在运行的进程的映射,并说明它的功能。

最小的可运行userland /proc/<pid>/pagemap示例

virt_to_phys_user.c

 #define _XOPEN_SOURCE 700 #include <fcntl.h> /* open */ #include <stdint.h> /* uint64_t */ #include <stdio.h> /* printf */ #include <stdlib.h> /* size_t */ #include <unistd.h> /* pread, sysconf */ typedef struct { uint64_t pfn : 54; unsigned int soft_dirty : 1; unsigned int file_page : 1; unsigned int swapped : 1; unsigned int present : 1; } PagemapEntry; /* Parse the pagemap entry for the given virtual address. * * @param[out] entry the parsed entry * @param[in] pagemap_fd file descriptor to an open /proc/pid/pagemap file * @param[in] vaddr virtual address to get entry for * @return 0 for success, 1 for failure */ int pagemap_get_entry(PagemapEntry *entry, int pagemap_fd, uintptr_t vaddr) { size_t nread; ssize_t ret; uint64_t data; nread = 0; while (nread < sizeof(data)) { ret = pread(pagemap_fd, &data, sizeof(data), (vaddr / sysconf(_SC_PAGE_SIZE)) * sizeof(data) + nread); nread += ret; if (ret <= 0) { return 1; } } entry->pfn = data & (((uint64_t)1 << 54) - 1); entry->soft_dirty = (data >> 54) & 1; entry->file_page = (data >> 61) & 1; entry->swapped = (data >> 62) & 1; entry->present = (data >> 63) & 1; return 0; } /* Convert the given virtual address to physical using /proc/PID/pagemap. * * @param[out] paddr physical address * @param[in] pid process to convert for * @param[in] vaddr virtual address to get entry for * @return 0 for success, 1 for failure */ int virt_to_phys_user(uintptr_t *paddr, pid_t pid, uintptr_t vaddr) { char pagemap_file[BUFSIZ]; int pagemap_fd; snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%ju/pagemap", (uintmax_t)pid); pagemap_fd = open(pagemap_file, O_RDONLY); if (pagemap_fd < 0) { return 1; } PagemapEntry entry; if (pagemap_get_entry(&entry, pagemap_fd, vaddr)) { return 1; } close(pagemap_fd); *paddr = (entry.pfn * sysconf(_SC_PAGE_SIZE)) + (vaddr % sysconf(_SC_PAGE_SIZE)); return 0; } int main(int argc, char **argv) { pid_t pid; uintptr_t vaddr, paddr = 0; if (argc < 3) { printf("Usage: %s pid vaddr\n", argv[0]); return EXIT_FAILURE; } pid = strtoull(argv[1], NULL, 0); vaddr = strtoull(argv[2], NULL, 0); if (virt_to_phys_user(&paddr, pid, vaddr)) { fprintf(stderr, "error: virt_to_phys_user\n"); return EXIT_FAILURE; }; printf("0x%jx\n", (uintmax_t)paddr); return EXIT_SUCCESS; } 

用法:

 sudo ./virt_to_phys_user.out <pid> <physical-address> 

sudo被要求读取/proc/<pid>/pagemap即使你有文件权限,在https://unix.stackexchange.com/questions/345915/how-to-change-permission-of-proc-self -pagemap文件/ 383838#383838

正如在https://stackoverflow.com/a/46247716/895245中所提到的那样,Linux懒惰地分配页表,因此在使&#x7528;virt_to_phys_user之前,请确保从测试程序读取和写入一个字节。

如何测试出来

测试程序:

 #define _XOPEN_SOURCE 700 #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> enum { I0 = 0x12345678 }; static volatile uint32_t i = I0; int main(void) { printf("vaddr %p\n", (void *)&i); printf("pid %ju\n", (uintmax_t)getpid()); while (i == I0) { sleep(1); } printf("i %jx\n", (uintmax_t)i); return EXIT_SUCCESS; } 

测试程序输出它拥有的变量的地址和PID,例如:

 vaddr 0x600800 pid 110 

然后你可以通过转换虚拟地址:

 sudo ./virt_to_phys_user.out 110 0x600800 

最后,转换可以通过使用/dev/mem来观察/修改内存来测试,但是如果不重新编译内核,则不能在Ubuntu 17.04上执行此操作: CONFIG_STRICT_DEVMEM=n ,另请参阅: 从用户访问物理地址空间 Buildroot 是一个简单的方法来克服,但是。

或者,你可以使用像QEMU监视器的xp命令的虚拟机: 如何在Linux中解码/ proc / pid / pagemap条目?

看到这个转储所有页面: 如何解码在Linux / proc / pid / pagemap条目?

这个问题的Userland子集: 如何在Linux中从用户空间查找变量的物理地址?

使用/proc/<pid>/maps转储所有进程页面

/proc/<pid>/maps列出了进程的所有地址范围,所以我们可以通过它来转换所有页面: / proc / [pid] / pagemaps和/ proc / [pid] / maps | Linux的

virt_to_phys只适用于kmalloc地址

例如,模块变量失败。 arc/x86/include/asm/io.h文档:

返回的物理地址是给定内存地址的物理(CPU)映射。 只有在通过kmalloc直接映射或分配的地址上使用此函数才是有效的。

这里是一个内核模块,用来说明 用户态测试 。

所以这不是一个普遍的可能性。 请参阅: 如何从Linux内核模块中的逻辑地址获取物理地址? 仅限于内核模块方法。

我想知道为什么没有用户土地的API。

因为用户登陆内存的物理地址是未知的。

Linux使用用户陆地存储器的需求分页。 您的用户地面对象在访问之前不会有物理内存。 当系统内存不足时,您的用户地面对象可能会被换出并丢失物理内存,除非该进程被锁定。 当你再次访问对象时,它被交换并给予物理内存,但它可能与前一个物理内存不同。 您可以拍摄页面映射的快照,但在下一刻不能保证一致。

所以,寻找用户地物的物理地址通常是没有意义的。

上面建议的C程序通常可以工作,但是它至少可以通过两种方式返回误导结果:

  1. 该页面不存在(但虚拟地址映射到一个页面!)。 发生这种情况是由于操作系统的延迟映射:它只在实际访问它们时映射地址。
  2. 返回的PFN指向一些可能是暂时的物理页面,由于写时复制可能会很快改变。 例如:对于内存映射文件,PFN可以指向只读副本。 对于匿名映射,映射中所有页面的PFN可以是一个特定的只读页面(全部为0)(写入时产生所有匿名页面)。

底线是为了确保更可靠的结果:对于只读映射,在查询PFN之前至少从每一页读取一次。 对于支持写入的页面,在查询PFN之前至少写入一次。

当然,理论上,即使在获得“稳定”PFN之后,映射在运行时总是可以随意改变的(例如,当页面移入和移出交换页面时),不应该依赖映射。