linux - mac - musicbrainz picard español
¿Hay alguna API para determinar la dirección física de la dirección virtual en Linux? (5)
¿Hay alguna API para determinar la dirección física de la dirección virtual en el sistema operativo Linux?
Me pregunto por qué no hay API de usuario de la tierra.
Porque la dirección física de la memoria terrestre del usuario es desconocida.
Linux usa paginación de demanda para la memoria de tierra del usuario. El objeto de tierra del usuario no tendrá memoria física hasta que se acceda a él. Cuando el sistema tiene poca memoria, el objeto de tierra del usuario se puede intercambiar y perder memoria física a menos que la página esté bloqueada para el proceso. Cuando vuelve a acceder al objeto, se intercambia y se le asigna memoria física, pero es probable que tenga una memoria física diferente a la anterior. Puede tomar una instantánea de la asignación de páginas, pero no se garantiza que sea la misma en el próximo momento.
Entonces, buscar la dirección física de un objeto de tierra del usuario generalmente no tiene sentido.
Como se contestó anteriormente, los programas normales no deberían tener que preocuparse por las direcciones físicas, ya que se ejecutan en un espacio de direcciones virtuales con todas sus comodidades. Además, no todas las direcciones virtuales tienen una dirección física, pueden pertenecer a archivos mapeados o páginas intercambiadas. Sin embargo, a veces puede ser interesante ver este mapeo, incluso en el sitio de usuario.
Para este propósito, el kernel de Linux expone su mapeo a un usuario a través de un conjunto de archivos en /proc
. La documentación se puede encontrar here . Breve resumen:
-
/proc/$pid/maps
proporciona una lista de asignaciones de direcciones virtuales junto con información adicional, como el archivo correspondiente para los archivos asignados. -
/proc/$pid/pagemap
proporciona más información sobre cada página asignada, incluida la dirección física si existe.
Este sitio web proporciona un programa en C que descarga las asignaciones de todos los procesos en ejecución utilizando esta interfaz y una explicación de lo que hace.
El kernel y el espacio de usuario funcionan con direcciones virtuales (también llamadas direcciones lineales) que el hardware de gestión de memoria asigna a direcciones físicas. Esta asignación está definida por tablas de páginas, configuradas por el sistema operativo.
Los dispositivos DMA usan direcciones de bus. En una PC i386, las direcciones de bus son las mismas que las direcciones físicas, pero otras arquitecturas pueden tener hardware de asignación de direcciones especial para convertir las direcciones de bus en direcciones físicas.
En Linux, puede usar estas funciones de asm/io.h
:
- virt_to_phys (virt_addr);
- phys_to_virt (phys_addr);
- virt_to_bus (virt_addr);
- bus_to_virt (bus_addr);
Todo esto se trata de acceder a la memoria ordinaria. También hay "memoria compartida" en el bus PCI o ISA. Se puede mapear dentro de un espacio de direcciones de 32 bits usando ioremap (), y luego se usa a través de las funciones readb (), writeb () (etc.).
La vida se complica por el hecho de que hay varios cachés alrededor, por lo que las diferentes formas de acceder a la misma dirección física no necesitan dar el mismo resultado.
Además, la dirección física real detrás de la dirección virtual puede cambiar. Incluso más que eso, no podría haber una dirección asociada con una dirección virtual hasta que acceda a esa memoria.
En cuanto a la API user-land, no hay ninguna que yo sepa.
El programa C sugerido anteriormente generalmente funciona, pero puede devolver resultados engañosos en (al menos) dos formas:
- La página no está presente (¡pero la dirección virtual está asignada a una página!). Esto sucede debido a la asignación diferida del sistema operativo: correlaciona las direcciones solo cuando realmente se accede a ellas.
- El PFN devuelto apunta a una página física posiblemente temporal que podría cambiarse poco después debido a la copia por escritura. Por ejemplo: para archivos mapeados en memoria, el PFN puede apuntar a la copia de solo lectura. Para las asignaciones anónimas, el PFN de todas las páginas del mapeo podría ser una página específica de solo lectura llena de ceros (de la cual se generan todas las páginas anónimas cuando se escribe).
En pocas palabras, para garantizar un resultado más confiable: para las asignaciones de solo lectura, lea de cada página al menos una vez antes de consultar su PFN. Para las páginas habilitadas para escritura, escriba en cada página al menos una vez antes de consultar su PFN.
Por supuesto, teóricamente, incluso después de obtener un PFN "estable", las asignaciones siempre podrían cambiar arbitrariamente en el tiempo de ejecución (por ejemplo, al mover páginas dentro y fuera del intercambio) y no se debe confiar en ellas.
Ejemplo userland /proc/<pid>/pagemap
ejecutable mínimo
#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;
}
Uso:
sudo ./virt_to_phys_user.out <pid> <physical-address>
se requiere sudo
para leer /proc/<pid>/pagemap
incluso si tiene permisos de archivo como se explica en: https://unix.stackexchange.com/questions/345915/how-to-change-permission-of-proc-self-pagemap-file/383838#383838
Como se menciona en: https://.com/a/46247716/895245 Linux asigna tablas de página de forma perezosa, así que asegúrese de leer y escribir un byte en esa dirección desde el programa de prueba antes de usar virt_to_phys_user
.
Cómo probarlo
Programa de prueba:
#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;
}
El programa de prueba genera la dirección de una variable que posee, y su PID, por ejemplo:
vaddr 0x600800
pid 110
y luego puede pasar la conversión de la dirección virtual con:
sudo ./virt_to_phys_user.out 110 0x600800
Finalmente, la conversión puede probarse usando /dev/mem
para observar / modificar la memoria, pero no puede hacer esto en Ubuntu 17.04 sin recompilar el núcleo como lo requiere: CONFIG_STRICT_DEVMEM=n
, vea también: Cómo acceder a las direcciones físicas del espacio de usuario en Linux? Buildroot es una manera fácil de superar eso sin embargo.
Alternativamente, puede utilizar una máquina virtual como el comando xp
del monitor QEMU: ¿Cómo decodificar / proc / pid / entradas de mapa de páginas en Linux?
Ver esto para volcar todas las páginas: ¿Cómo decodificar / proc / pid / entradas de mapa de página en Linux?
Subgrupo de Userland de esta pregunta: ¿Cómo encontrar la dirección física de una variable del espacio de usuario en Linux?
Vaciar todas las páginas de proceso con /proc/<pid>/maps
/proc/<pid>/maps
lista todos los rangos de direcciones del proceso, por lo que podemos recorrerlo para traducir todas las páginas: / proc / [pid] / pagemaps y / proc / [pid] / maps | linux
Kerneland virt_to_phys
solo funciona para direcciones kmalloc
Desde un módulo kernel, se ha mencionado virt_to_phys
.
Sin embargo, es importante resaltar que tiene esta limitación.
Por ejemplo, falla para las variables del módulo. arc/x86/include/asm/io.h
:
La dirección física devuelta es la asignación física (CPU) para la dirección de memoria indicada. Solo es válido utilizar esta función en direcciones directamente asignadas o asignadas a través de kmalloc.
Aquí hay un módulo kernel que lo ilustra junto con una prueba de usuario .
Entonces esta no es una posibilidad muy general. Ver: ¿Cómo obtener la dirección física de la lógica en un módulo kernel de Linux? para los métodos de módulo kernel exclusivamente.