在Linux操作系统中,是否有用于从虚拟地址确定物理地址的API?
在Linux操作系统中,是否有用于从虚拟地址确定物理地址的API?
内核和用户空间使用虚拟地址(也称为线性地址),由内存管理硬件将其映射到物理地址。这种映射是由操作系统设置的页表定义的。
DMA设备使用总线地址。在i386 PC上,总线地址与物理地址相同,但其他架构可能有特殊的地址映射硬件来将总线地址转换为物理地址。
在Linux中,您可以使用asm/io.h
中的这些函数:
所有这些都是关于访问普通内存的。PCI或ISA总线上还有“共享内存”。可以使用ioremap()将其映射到32位地址空间内,然后通过readb()、writeb()等函数使用。
事实上,由于存在各种缓存,因此访问相同物理地址的不同方式可能会产生不同的结果,使得情况变得更加复杂。
此外,虚拟地址背后的真实物理地址可能会发生变化。更甚者,直到访问该内存之前,虚拟地址可能没有与之关联的地址。
就用户空间API而言,我不知道有任何可用的。
virt_to_phys
仅适用于 kmalloc
内存:https://dev59.com/dm025IYBdhLWcg3w4Z-u#45128487 - Ciro Santilli OurBigBook.com/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 : 55;
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;
uintptr_t vpn;
vpn = vaddr / sysconf(_SC_PAGE_SIZE);
nread = 0;
while (nread < sizeof(data)) {
ret = pread(pagemap_fd, ((uint8_t*)&data) + nread, sizeof(data) - nread,
vpn * sizeof(data) + nread);
nread += ret;
if (ret <= 0) {
return 1;
}
}
entry->pfn = data & (((uint64_t)1 << 55) - 1);
entry->soft_dirty = (data >> 55) & 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> <virtual-address>
sudo
权限是必须的,即使您具有文件权限也需要如此处所述。要读取/proc/<pid>/pagemap
。
正如在此处所述,Linux会惰性地分配页表,因此在使用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
,另请参见:如何在Linux中从用户空间访问物理地址?然而,Buildroot是一种简单的方法可以克服这一点。
或者,您可以使用像QEMU监视器的xp
命令这样的虚拟机:如何解码Linux中的/ proc / pid / pagemap条目?
查看此处以转储所有页面:如何解码Linux中的/ proc / pid / pagemap条目?
此问题的用户空间子集:如何在Linux的用户空间中找到变量的物理地址?
使用/proc /<pid>/maps
转储所有进程页面
/ proc /<pid>/maps
列出进程的所有地址范围,因此我们可以遍历它来翻译所有页面:/ proc / [pid] / pagemaps和/ proc / [pid] / maps | linux
Kerneland virt_to_phys()
仅适用于kmalloc()
地址
从内核模块中提到了virt_to_phys()
。
但是,重要的是要强调它具有此限制。
例如,它无法处理模块变量。arc/x86/include/asm/io.h
文档:
返回的物理地址是给定内存地址的物理(CPU)映射。仅对直接映射或通过
kmalloc()
分配的地址使用此函数有效。
这里有一个内核模块,它展示了如何与用户空间测试一起获取虚拟地址的物理地址。
因此这不是一个非常普遍的可能性。请参见:如何在Linux内核模块中完全使用内核模块方法从逻辑地址获取物理地址?
pagemap
的权限,你知道有哪些sysctl配置可以限制只有root才能访问吗?编辑:看起来我只能访问我拥有的进程的pagemap
。 - Roi