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

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

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

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()(等)函数使用。

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

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

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

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

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

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

这个网站提供了一个C程序,它使用这个接口转储所有正在运行的进程的映射,并解释它的function。

最小的可运行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懒惰地分配页表,因此在使用;virt_to_phys_user之前,请确保从testing程序读取和写入一个字节。

如何testing出来

testing程序:

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

testing程序输出它拥有的variables的地址和PID,例如:

 vaddr 0x600800 pid 110 

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

 sudo ./virt_to_phys_user.out 110 0x600800 

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

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

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

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

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

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

virt_to_phys只适用于kmalloc地址

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

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

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

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

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

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

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

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

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

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

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

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