2009-03-15 26 views
41

Estoy intentando acceder a la memoria física directamente para un proyecto de Linux incorporado, pero no estoy seguro de cómo puedo designar la mejor memoria para mi uso.Acceso directo a memoria en Linux

Si reinicio mi dispositivo regularmente y accedo a/dev/mem, puedo leer y escribir fácilmente en cualquier lugar que desee. Sin embargo, en esto, estoy accediendo a la memoria que se puede asignar fácilmente a cualquier proceso; cosa que no quiero hacer

Mi código para/dev/mem es (todo comprobación de errores, etc. eliminado):

mem_fd = open("/dev/mem", O_RDWR)); 
mem_p = malloc(SIZE + (PAGE_SIZE - 1)); 
if ((unsigned long) mem_p % PAGE_SIZE) { 
    mem_p += PAGE_SIZE - ((unsigned long) mem_p % PAGE_SIZE); 
} 
mem_p = (unsigned char *) mmap(mem_p, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, mem_fd, BASE_ADDRESS); 

Y esto funciona. Sin embargo, me gustaría usar memoria que nadie más tocará. Intenté limitar la cantidad de memoria que ve el kernel arrancando con mem = XXXm, y luego estableciendo BASE_ADDRESS en algo por encima de eso (pero debajo de la memoria física), pero parece que no está accediendo a la misma memoria de manera consistente.

Basado en lo que he visto en línea, sospecho que puedo necesitar un módulo kernel (que está bien) que utiliza ya sea ioremap() o remap_pfn_range() (o ambos ???), pero no tengo ni idea cómo; ¿Alguien puede ayudar?

EDIT: Lo que quiero es una forma de acceder siempre a la misma memoria física (digamos, 1.5MB vale), y dejar esa memoria a un lado para que el kernel no la asigne a ningún otro proceso.

Estoy tratando de reproducir un sistema que teníamos en otros sistemas operativos (sin gestión de memoria), por lo que pude asignar un espacio en la memoria mediante el engarce, y acceder a ella usando algo como

*(unsigned char *)0x12345678 

Edit2: Supongo que debería proporcionar más detalles. Este espacio de memoria se usará para un búfer RAM para una solución de registro de alto rendimiento para una aplicación incorporada. En los sistemas que tenemos, no hay nada que borre o codifique la memoria física durante un reinicio suave. Por lo tanto, si escribo un bit en una dirección física X y reinicio el sistema, el mismo bit se establecerá después del reinicio. Esto ha sido probado en el mismo hardware exacto que ejecuta VxWorks (esta lógica también funciona muy bien en Nucleus RTOS y OS20 en diferentes plataformas, FWIW). Mi idea era probar lo mismo en Linux al abordar directamente la memoria física; por lo tanto, es esencial que obtenga las mismas direcciones en cada arranque.

Probablemente debería aclarar que esto es para kernel 2.6.12 y posteriores.

EDIT3: Aquí está mi código, primero para el módulo kernel, luego para la aplicación del espacio de usuario.

Para usarlo, reinicio con mem = 95m, luego insmod foo-module.ko, luego mknod mknod/dev/foo c 32 0, luego ejecuto foo-user, donde muere. Se ejecuta bajo el BGF muestra que muere a la asignación, aunque dentro de gdb, no puedo eliminar la referencia de la dirección de E obtener de mmap (aunque puede printf)

foo-module.c

#include <linux/module.h> 
#include <linux/config.h> 
#include <linux/init.h> 
#include <linux/fs.h> 
#include <linux/mm.h> 
#include <asm/io.h> 

#define VERSION_STR "1.0.0" 
#define FOO_BUFFER_SIZE (1u*1024u*1024u) 
#define FOO_BUFFER_OFFSET (95u*1024u*1024u) 
#define FOO_MAJOR 32 
#define FOO_NAME "foo" 

static const char *foo_version = "@(#) foo Support version " VERSION_STR " " __DATE__ " " __TIME__; 

static void *pt = NULL; 

static int  foo_release(struct inode *inode, struct file *file); 
static int  foo_open(struct inode *inode, struct file *file); 
static int  foo_mmap(struct file *filp, struct vm_area_struct *vma); 

struct file_operations foo_fops = { 
    .owner = THIS_MODULE, 
    .llseek = NULL, 
    .read = NULL, 
    .write = NULL, 
    .readdir = NULL, 
    .poll = NULL, 
    .ioctl = NULL, 
    .mmap = foo_mmap, 
    .open = foo_open, 
    .flush = NULL, 
    .release = foo_release, 
    .fsync = NULL, 
    .fasync = NULL, 
    .lock = NULL, 
    .readv = NULL, 
    .writev = NULL, 
}; 

static int __init foo_init(void) 
{ 
    int    i; 
    printk(KERN_NOTICE "Loading foo support module\n"); 
    printk(KERN_INFO "Version %s\n", foo_version); 
    printk(KERN_INFO "Preparing device /dev/foo\n"); 
    i = register_chrdev(FOO_MAJOR, FOO_NAME, &foo_fops); 
    if (i != 0) { 
     return -EIO; 
     printk(KERN_ERR "Device couldn't be registered!"); 
    } 
    printk(KERN_NOTICE "Device ready.\n"); 
    printk(KERN_NOTICE "Make sure to run mknod /dev/foo c %d 0\n", FOO_MAJOR); 
    printk(KERN_INFO "Allocating memory\n"); 
    pt = ioremap(FOO_BUFFER_OFFSET, FOO_BUFFER_SIZE); 
    if (pt == NULL) { 
     printk(KERN_ERR "Unable to remap memory\n"); 
     return 1; 
    } 
    printk(KERN_INFO "ioremap returned %p\n", pt); 
    return 0; 
} 
static void __exit foo_exit(void) 
{ 
    printk(KERN_NOTICE "Unloading foo support module\n"); 
    unregister_chrdev(FOO_MAJOR, FOO_NAME); 
    if (pt != NULL) { 
     printk(KERN_INFO "Unmapping memory at %p\n", pt); 
     iounmap(pt); 
    } else { 
     printk(KERN_WARNING "No memory to unmap!\n"); 
    } 
    return; 
} 
static int foo_open(struct inode *inode, struct file *file) 
{ 
    printk("foo_open\n"); 
    return 0; 
} 
static int foo_release(struct inode *inode, struct file *file) 
{ 
    printk("foo_release\n"); 
    return 0; 
} 
static int foo_mmap(struct file *filp, struct vm_area_struct *vma) 
{ 
    int    ret; 
    if (pt == NULL) { 
     printk(KERN_ERR "Memory not mapped!\n"); 
     return -EAGAIN; 
    } 
    if ((vma->vm_end - vma->vm_start) != FOO_BUFFER_SIZE) { 
     printk(KERN_ERR "Error: sizes don't match (buffer size = %d, requested size = %lu)\n", FOO_BUFFER_SIZE, vma->vm_end - vma->vm_start); 
     return -EAGAIN; 
    } 
    ret = remap_pfn_range(vma, vma->vm_start, (unsigned long) pt, vma->vm_end - vma->vm_start, PAGE_SHARED); 
    if (ret != 0) { 
     printk(KERN_ERR "Error in calling remap_pfn_range: returned %d\n", ret); 
     return -EAGAIN; 
    } 
    return 0; 
} 
module_init(foo_init); 
module_exit(foo_exit); 
MODULE_AUTHOR("Mike Miller"); 
MODULE_LICENSE("NONE"); 
MODULE_VERSION(VERSION_STR); 
MODULE_DESCRIPTION("Provides support for foo to access direct memory"); 

foo-user.c

#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h> 
#include <stdio.h> 
#include <sys/mman.h> 

int main(void) 
{ 
    int    fd; 
    char   *mptr; 
    fd = open("/dev/foo", O_RDWR | O_SYNC); 
    if (fd == -1) { 
     printf("open error...\n"); 
     return 1; 
    } 
    mptr = mmap(0, 1 * 1024 * 1024, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 4096); 
    printf("On start, mptr points to 0x%lX.\n",(unsigned long) mptr); 
    printf("mptr points to 0x%lX. *mptr = 0x%X\n", (unsigned long) mptr, *mptr); 
    mptr[0] = 'a'; 
    mptr[1] = 'b'; 
    printf("mptr points to 0x%lX. *mptr = 0x%X\n", (unsigned long) mptr, *mptr); 
    close(fd); 
    return 0; 
} 
+0

Para aclarar, desea (en un módulo) devolver un espacio de direcciones al espacio de usuario adquirido a través de vmalloc(), no kmalloc(), ¿correcto? ¿Cuánta memoria realmente necesitas? –

+0

Esto probablemente se hace más fácil con kmalloc(), lo que haría sería configurar 1,5 MB de espacio del kernel y presentarlo al espacio de usuario. Si eso es lo que quieres hacer, me refrescaré con unas cuantas entrañas de kernel e intentaré responder. –

+0

Nota, hacer esto con vmalloc() puede ser una tarea extremadamente desagradable. La cantidad que realmente necesita asignar influye en la respuesta, por lo que está seguro de que es de 1,5 MB o menos? –

Respuesta

13

Creo que puedes encontrar mucha documentación sobre la parte kmalloc + mmap. Sin embargo, no estoy seguro de que pueda hacer kmalloc tanta memoria de forma contigua, y tenerla siempre en el mismo lugar. Claro, si todo es siempre igual, entonces es posible que obtenga una dirección constante. Sin embargo, cada vez que cambie el código del núcleo, obtendrá una dirección diferente, por lo que no aceptaría la solución kmalloc.

Creo que deberías reservar algo de memoria en el momento del arranque, es decir, reservar algo de memoria física para que el kernel no la toque. Entonces puede ioremap esta memoria que le dará una dirección virtual del kernel, y luego puede mmap y escribir un buen controlador de dispositivo.

Esto nos lleva de vuelta a linux device drivers en formato PDF. Eche un vistazo al capítulo 15, está describiendo esta técnica en la página 443

Editar: ioremap y mmap. Creo que esto podría ser más fácil de depurar haciendo cosas en dos pasos: primero obtenga el ioremap a la derecha, y pruébelo usando una operación de dispositivo de caracteres, es decir, leer/escribir. Una vez que sepa que puede tener acceso de forma segura a toda la memoria asignada utilizando lectura/escritura, entonces intente mapear todo el rango ioremapped.

Y si te metes en problemas se pueden post otra pregunta sobre mmaping

Editar: remap_pfn_range ioremap devuelve un virtual_adress, que debe convertir a un PFN para remap_pfn_ranges. Ahora, no entiendo exactamente lo que un PFN (Número de página del capítulo) es, pero creo que se puede conseguir uno llamando

virt_to_phys(pt) >> PAGE_SHIFT 

Esto probablemente no es el camino correcto (tm) para hacerlo, pero debería intentarlo

También debe comprobar que FOO_MEM_OFFSET es la dirección física de su bloque de RAM. Es decir, antes de que ocurra algo con el mmu, su memoria estará disponible en 0 en el mapa de memoria de su procesador.

+0

Cuando dices "Creo que deberías reservar algo de memoria en el momento del arranque, es decir, reservar algo de memoria física para que el núcleo no la toque". ¿Te refieres a arrancar con mem = XXXm, donde XXX es menor que la dirección real? Eso es lo que originalmente estaba pensando. – Mikeage

+0

[cont] Veo un código allí usando ioremap(); Voy a intentar esto. Para confirmar: reinicie con mem = XXX, ioremap (XXX + 1), y luego ¿cuál es la mejor manera de traducir esto a una dirección de espacio de usuario? 1 – Mikeage

+0

Hrmmm. Intenté esto e implementé el mmap de la siguiente manera (se eliminó toda comprobación de errores): static int foo_mmap (struct archivo * filp, struct vm_area_struct * vma) { remap_pfn_range (vma, vma-> vm_start, (unsigned long) pt, vma-> vm_end - vma-> vm_start, PAGE_SHARED); } – Mikeage

12

Lamento contestar pero no del todo responder, me di cuenta de que ya ha editado la pregunta. Tenga en cuenta que SO no nos notifica cuando edita la pregunta. Estoy dando una respuesta genérica aquí, cuando actualices la pregunta, por favor deja un comentario, luego editaré mi respuesta.

Sí, vas a necesitar escribir un módulo. Lo que se reduce a esto es el uso de kmalloc() (asignando una región en el espacio del kernel) o vmalloc() (asignando una región en el espacio de usuario).

Exponer lo anterior es fácil, exponer este último puede ser un dolor en la parte trasera con el tipo de interfaz que está describiendo según sea necesario. Usted notó que 1.5 MB es una estimación aproximada de cuánto necesita reservar realmente, ¿está revestido de hierro? ¿Se siente cómodo sacando eso del espacio del núcleo? ¿Puede tratar adecuadamente con ENOMEM o EIO desde el espacio de usuario (o incluso desde el disco)? IOW, ¿qué entra en esta región?

Además, ¿la concurrencia va a ser un problema con esto? Si es así, ¿vas a utilizar un futex? Si la respuesta a cualquiera es 'sí' (especialmente la última), es probable que tengas que morder la bala e ir con vmalloc() (o arriesgarse a la putrefacción del grano desde adentro). Además, si está PENSANDO en una interfaz ioctl() con el dispositivo char (especialmente para alguna idea de bloqueo ad-hoc), realmente quiere ir con vmalloc().

Además, ¿has leído this? Además, ni siquiera estamos hablando de lo que grsec/selinux va a pensar de esto (si está en uso).

+0

este es un sistema integrado; No estoy preocupado por Selinux. En cuanto al resto de tus Q, estoy agregando más detalles ahora. – Mikeage

0

No soy en absoluto un experto en estas cuestiones, por lo que esta será una pregunta para usted en lugar de una respuesta. ¿Hay alguna razón por la que no puedas hacer una pequeña partición de disco ram y usarla solo para tu aplicación? ¿Eso no le daría acceso garantizado al mismo pedazo de memoria? No estoy seguro de que haya problemas de rendimiento de E/S o gastos generales adicionales asociados con eso. Esto también supone que puede decirle al kernel que particione un rango de direcciones específico en la memoria, sin estar seguro de si eso es posible.

Me disculpo por la nueva pregunta, pero encontré su pregunta interesante, y tengo curiosidad por saber si el disco RAM podría usarse de esa manera.

+0

La concurrencia puede ser un problema con esto, especialmente cuando se usa DM que no respeta las barreras de escritura. La pregunta (a pesar de las ediciones) es realmente ambigua, la forma en que realmente usarán ese tipo de región con ese tipo de interfaz es deficiente. –

+0

Además, (haciendo referencia a mi primer comentario) escribir caché puede ser un problema, especialmente con el pedido. –

+0

Ramdisk no funcionará; ver mi última edición – Mikeage

2

/dev/mem está bien para registros simples y pokes, pero una vez que se cruza con las interrupciones y el territorio DMA, realmente debe escribir un controlador kernel-mode. Lo que hizo por sus sistemas operativos anteriores sin administración de memoria simplemente no se adapta bien a un sistema operativo de propósito general como Linux.

Ya ha pensado en el problema de asignación de búfer DMA. Ahora, piense en la interrupción "DMA done" desde su dispositivo. ¿Cómo vas a instalar una rutina de servicio de interrupción?

Además,/dev/mem suele estar bloqueado para usuarios no root, por lo que no es muy práctico para uso general. Claro, podrías modificarlo, pero luego has abierto un gran agujero de seguridad en el sistema.

Si está tratando de mantener la base de código de controlador similar entre los sistemas operativos, debería considerar refaccionarla en capas de modo kernel de usuario separado & con una interfaz de tipo IOCTL en el medio. Si escribe la parte de modo de usuario como una biblioteca genérica de código C, debería ser fácil realizar un puerto entre Linux y otros sistemas operativos. La parte específica del sistema operativo es el código kernel-mode. (Utilizamos este tipo de enfoque para nuestros controladores.)

Parece que ya ha llegado a la conclusión de que es hora de escribir un kernel-driver, por lo que está en el camino correcto. El único consejo que puedo agregar es leer estos libros de principio a fin.

Linux Device Drivers

Understanding the Linux Kernel

(Tenga en cuenta que estos libros son circa-2005, por lo que la información es un poco anticuado.)

0

¿Has mirado el parámetro kernel 'memmap'? En i386 y X64_64, puede usar el parámetro memmap para definir cómo el núcleo entregará bloques de memoria muy específicos (consulte la documentación Linux kernel parameter). En su caso, querría marcar la memoria como 'reservada' para que Linux no la toque en absoluto. Luego puede escribir su código para usar esa dirección y tamaño absolutos (¡ay de usted si se sale de ese espacio!).

+0

De hecho, utilicé memmap [o simplemente mem = XXm @ YY, que mi kernel admite para reservar un bloque en el medio.] El problema abierto era cómo acceder a la memoria directamente. – Mikeage

Cuestiones relacionadas