web-dev-qa-db-fra.com

Existe-t-il une API pour déterminer l'adresse physique à partir d'une adresse virtuelle sous Linux?

Existe-t-il une API pour déterminer l'adresse physique à partir de l'adresse virtuelle dans le système d'exploitation Linux?

28
Karthik Balaguru

Le noyau et l'espace utilisateur fonctionnent avec des adresses virtuelles (également appelées adresses linéaires) qui sont mappées à des adresses physiques par le matériel de gestion de la mémoire. Ce mappage est défini par des tables de pages, configurées par le système d'exploitation.

Les appareils DMA utilisent des adresses de bus. Sur un PC i386, les adresses de bus sont les mêmes que les adresses physiques, mais d'autres architectures peuvent avoir un matériel de mappage d'adresses spécial pour convertir les adresses de bus en adresses physiques.

Sous Linux, vous pouvez utiliser ces fonctions à partir de asm/io.h:

  • virt_to_phys (virt_addr);
  • phys_to_virt (phys_addr);
  • virt_to_bus (virt_addr);
  • bus_to_virt (bus_addr);

Tout cela concerne l'accès à la mémoire ordinaire. Il existe également une "mémoire partagée" sur le bus PCI ou ISA. Elle peut être mappée à l'intérieur d'un espace d'adressage 32 bits à l'aide de ioremap (), puis utilisée via readb (), writeb ( ) (etc.).

La vie est compliquée par le fait qu'il existe plusieurs caches autour, de sorte que différentes façons d'accéder à la même adresse physique n'ont pas besoin de donner le même résultat.

De plus, la véritable adresse physique derrière l'adresse virtuelle peut changer. Encore plus - il ne peut y avoir d'adresse associée à une adresse virtuelle tant que vous n'avez pas accédé à cette mémoire.

Quant à l'API utilisateur-terre, il n'y en a pas que je sache.

32
user405725

Comme indiqué précédemment, les programmes normaux ne devraient pas avoir à se soucier des adresses physiques car ils s'exécutent dans un espace d'adressage virtuel avec toutes ses commodités. De plus, toutes les adresses virtuelles n'ont pas d'adresse physique, elles peuvent appartenir à des fichiers mappés ou à des pages échangées. Cependant, il peut parfois être intéressant de voir cette cartographie, même dans les pays utilisateurs.

À cette fin, le noyau Linux expose son mappage à l'espace utilisateur via un ensemble de fichiers dans le /proc. La documentation peut être trouvée ici . Court résumé:

  1. /proc/$pid/maps fournit une liste de mappages d'adresses virtuelles ainsi que des informations supplémentaires, telles que le fichier correspondant pour les fichiers mappés.
  2. /proc/$pid/pagemap fournit plus d'informations sur chaque page mappée, y compris l'adresse physique si elle existe.

Ce site Web fournit un programme C qui vide les mappages de tous les processus en cours d'exécution à l'aide de cette interface et une explication de ce qu'il fait.

18
ingomueller.net

/proc/<pid>/pagemap exemple exécutable minimal userland

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

GitHub en amont .

Usage:

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

Sudo est nécessaire pour lire /proc/<pid>/pagemap même si vous disposez d'autorisations de fichiers, comme expliqué sur: https://unix.stackexchange.com/questions/345915/how-to-change-permission-of-proc-self-pagemap-file/383838# 383838

Comme mentionné sur: https://stackoverflow.com/a/46247716/895245 Linux alloue les tables de pages paresseusement, alors assurez-vous de lire et d'écrire un octet à cette adresse dans le programme de test avant d'utiliser virt_to_phys_user.

Comment le tester

Programme de test:

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

Le programme de test sort l'adresse d'une variable qu'il possède et son PID, par exemple:

vaddr 0x600800
pid 110

puis vous pouvez passer convertir l'adresse virtuelle avec:

Sudo ./virt_to_phys_user.out 110 0x600800

Enfin, la conversion peut être testée en utilisant /dev/mem pour observer/modifier la mémoire, mais vous ne pouvez pas le faire sur Ubuntu 17.04 sans recompiler le noyau car cela nécessite: CONFIG_STRICT_DEVMEM=n, voir aussi: Comment accéder aux adresses physiques depuis l'espace utilisateur sous Linux? Buildroot est un moyen simple pour surmonter cela cependant.

Alternativement, vous pouvez utiliser une machine virtuelle comme la commande xp du moniteur QEMU: Comment décoder/proc/pid/pagemap les entrées sous Linux?

Voir ceci pour vider toutes les pages: Comment décoder les entrées/proc/pid/pagemap sous Linux?

Sous-ensemble Userland de cette question: Comment trouver l'adresse physique d'une variable à partir de l'espace utilisateur sous Linux?

Vider toutes les pages de processus avec /proc/<pid>/maps

/proc/<pid>/maps répertorie toutes les plages d'adresses du processus, nous pouvons donc parcourir cela pour traduire toutes les pages: / proc/[pid]/pagemaps et/proc/[pid]/maps | linux

Kerneland virt_to_phys ne fonctionne que pour les adresses kmalloc

Depuis un module du noyau, virt_to_phys, a été mentionné.

Cependant, il est important de souligner qu'il a cette limitation.

Par exemple. il échoue pour les variables de module. arc/x86/include/asm/io.h documentation:

L'adresse physique retournée est le mappage physique (CPU) de l'adresse mémoire donnée. Il n'est valable d'utiliser cette fonction que sur des adresses directement mappées ou allouées via kmalloc.

Voici n module du noyau qui illustre cela avec un test userland .

Ce n'est donc pas une possibilité très générale. Voir: Comment obtenir l'adresse physique de l'adresse logique dans un module de noyau Linux? pour les méthodes de module de noyau exclusivement.

Le programme C suggéré ci-dessus fonctionne généralement, mais il peut renvoyer des résultats trompeurs de (au moins) deux façons:

  1. La page n'est pas présente (mais l'adresse virtuelle est mappée sur une page!). Cela se produit en raison du mappage paresseux du système d'exploitation: il mappe les adresses uniquement lorsqu'elles sont réellement accédées.
  2. Le PFN renvoyé pointe vers une page physique éventuellement temporaire qui pourrait être modifiée peu de temps après en raison de la copie sur écriture. Par exemple: pour les fichiers mappés en mémoire, le PFN peut pointer vers la copie en lecture seule. Pour les mappages anonymes, le PFN de toutes les pages du mappage peut être une page spécifique en lecture seule pleine de 0 (à partir de laquelle toutes les pages anonymes apparaissent lors de l'écriture).

En fin de compte, pour garantir un résultat plus fiable: pour les mappages en lecture seule, lisez chaque page au moins une fois avant d'interroger son PFN. Pour les pages activées en écriture, écrivez dans chaque page au moins une fois avant d'interroger son PFN.

Bien sûr, théoriquement, même après avoir obtenu un PFN "stable", les mappages peuvent toujours changer arbitrairement au moment de l'exécution (par exemple lors du déplacement de pages dans et hors du swap) et ne doivent pas être utilisés.

4
Roei Schus

Je me demande pourquoi il n'y a pas d'API utilisateur.

Parce que l'adresse physique de la mémoire utilisateur est inconnue.

Linux utilise la pagination de la demande pour la mémoire de l'utilisateur. Votre objet terrestre utilisateur n'aura pas de mémoire physique tant qu'il n'y aura pas accès. Lorsque le système manque de mémoire, votre objet terrestre utilisateur peut être échangé et perdre de la mémoire physique à moins que la page ne soit verrouillée pour le processus. Lorsque vous accédez à nouveau à l'objet, il est échangé et dispose d'une mémoire physique, mais il s'agit probablement d'une mémoire physique différente de la précédente. Vous pouvez prendre un instantané du mappage de page, mais il n'est pas garanti qu'il en soit de même dans l'instant suivant.

Ainsi, la recherche de l'adresse physique d'un objet terrestre utilisateur n'a généralement aucun sens.

2
hiro