小智 33

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

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,没有我所知道的.

  • 至于user-land API,我可以想到的一种方法是编写内核空间函数,并让用户空间进行查询(获取值).获取物理地址的动机可能是针对某些恶意软件检测用法.别人注意:不要以为它没用,只是因为你无法想到它的使用. (3认同)
  • @S.Man:是否有用户界面 API 并不重要。更重要的是 - 用户态 API 能为您做什么?在我看来,这是没有用的。还有很多人认为我相信。为什么一个人要花时间和精力去做无用的事情呢? (2认同)

ing*_*net 18

如前所述,正常程序不需要担心物理地址,因为它们在虚拟地址空间中运行并具有所有便利性.此外,并非每个虚拟地址都具有物理地址,可以属于映射文件或交换页面.但是,有时看到这种映射可能会很有趣,即使在用户区也是如此.

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

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

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


Cir*_*四事件 12

/proc/<pid>/pagemap userland minimal runnable example

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;
    uintptr_t vpn;

    vpn = vaddr / sysconf(_SC_PAGE_SIZE);
    nread = 0;
    while (nread < sizeof(data)) {
        ret = pread(pagemap_fd, &data, sizeof(data) - nread,
                vpn * 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;
}
Run Code Online (Sandbox Code Playgroud)

GitHub上游.

用法:

sudo ./virt_to_phys_user.out <pid> <physical-address>
Run Code Online (Sandbox Code Playgroud)

sudo/proc/<pid>/pagemap即使你有文件权限也需要阅读,如下所述:https://unix.stackexchange.com/questions/345915/how-to-change-permission-of-proc-self-pagemap-file/383838#383838

如上所述:https://stackoverflow.com/a/46247716/895245 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;
}
Run Code Online (Sandbox Code Playgroud)

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

vaddr 0x600800
pid 110
Run Code Online (Sandbox Code Playgroud)

然后你可以传递转换虚拟地址:

sudo ./virt_to_phys_user.out 110 0x600800
Run Code Online (Sandbox Code Playgroud)

最后,可以通过使用/dev/mem观察/修改内存来测试转换,但是如果不按需要重新编译内核,则无法在Ubuntu 17.04上执行此操作:CONFIG_STRICT_DEVMEM=n另请参阅:如何从Linux中的用户空间访问物理地址?然而,Buildroot 是一种克服这种情况的简单方法.

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

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

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

转储所有流程页面 /proc/<pid>/maps

/proc/<pid>/maps列出的过程中所有的地址范围,所以我们可以走这条翻译所有的网页:的/ proc/[PID]/pagemaps和的/ proc/[PID] /地图| Linux的

Kerneland virt_to_phys仅适用于kmalloc地址

从内核模块,virt_to_phys已经提到过.

但是,重要的是强调它有这个限制.

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

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

这是一个内核模块,它用户态测试一起说明.

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

  • 哎呀。我错误地认为该程序应该只输出 PFN,但它实际上输出了整个物理地址。该程序运行正常:+1 赞成。 (2认同)
  • @AneesAhmed777 啊,感谢您的确认。如果您发现任何问题,请随时告诉我。 (2认同)

小智 9

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

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

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

当然,从理论上讲,即使在获得“稳定”的 PFN 后,映射也始终可能在运行时任意更改(例如,将页面移入和移出交换区时),因此不应依赖该映射。


小智 5

我想知道为什么没有用户态 API。

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

Linux 对用户态内存使用按需分页。您的用户空间对象在被访问之前不会拥有物理内存。当系统内存不足时,除非为进程锁定页面,否则您的用户空间对象可能会被换出并丢失物理内存。当您再次访问该对象时,它会被交换并获得物理内存,但它可能与前一个物理内存不同。您可以拍摄页面映射的快照,但不保证下一时刻是相同的。

因此,寻找用户陆地对象的物理地址通常是没有意义的。


归档时间:

查看次数:

38821 次

最近记录:

5 年,11 月 前