web-dev-qa-db-fra.com

Accès direct à la mémoire sous Linux

J'essaie d'accéder directement à la mémoire physique pour un projet Linux intégré, mais je ne sais pas comment je peux désigner au mieux la mémoire pour mon usage.

Si je démarre régulièrement mon appareil et accède à/dev/mem, je peux facilement lire et écrire à peu près partout où je veux. Cependant, dans ce cas, j'accède à une mémoire qui peut facilement être allouée à n'importe quel processus; ce que je ne veux pas faire

Mon code pour/dev/mem est (toute vérification d'erreur, etc. supprimée):

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

Et ça marche. Cependant, j'aimerais utiliser une mémoire que personne d'autre ne touchera. J'ai essayé de limiter la quantité de mémoire que le noyau voit en démarrant avec mem = XXXm, puis en définissant BASE_ADDRESS à quelque chose au-dessus de cela (mais en dessous de la mémoire physique), mais il ne semble pas accéder systématiquement à la même mémoire.

Sur la base de ce que j'ai vu en ligne, je soupçonne que j'ai peut-être besoin d'un module de noyau (qui est OK) qui utilise soit ioremap () ou remap_pfn_range () (ou les deux ???), mais je n'ai absolument aucune idée de comment; quelqu'un peut-il aider?

EDIT: Ce que je veux, c'est un moyen d'accéder toujours à la même mémoire physique (disons, 1,5 Mo) et de mettre cette mémoire de côté afin que le noyau ne l'alloue à aucun autre processus.

J'essaie de reproduire un système que nous avions dans d'autres systèmes d'exploitation (sans gestion de la mémoire) grâce auquel je pouvais allouer un espace en mémoire via l'éditeur de liens et y accéder en utilisant quelque chose comme

*(unsigned char *)0x12345678

EDIT2: Je suppose que je devrais fournir plus de détails. Cet espace mémoire sera utilisé pour un RAM buffer pour une solution de journalisation haute performance pour une application embarquée. Dans les systèmes que nous avons, il n'y a rien qui efface ou brouille la mémoire physique lors d'un redémarrage en douceur. Ainsi , si j'écris un peu sur une adresse physique X et que je redémarre le système, le même bit sera toujours défini après le redémarrage. Cela a été testé sur le même matériel exécutant VxWorks (cette logique fonctionne également très bien dans Nucleus RTOS et OS20 sur différentes plates-formes, FWIW). Mon idée était d'essayer la même chose sous Linux en adressant directement la mémoire physique; par conséquent, il est essentiel que j'obtienne les mêmes adresses à chaque démarrage.

Je devrais probablement préciser que c'est pour le noyau 2.6.12 et plus récent.

EDIT3: Voici mon code, d'abord pour le module du noyau, puis pour l'application de l'espace utilisateur.

Pour l'utiliser, je démarre avec mem = 95m, puis insmod foo-module.ko, puis mknod mknod/dev/foo c 32 0, puis je lance foo-user, où il meurt. L'exécution sous gdb montre qu'elle meurt lors de l'affectation, bien que dans gdb, je ne puisse pas déréférencer l'adresse que j'obtiens de mmap (bien que printf le puisse)

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

Je pense que vous pouvez trouver beaucoup de documentation sur la partie kmalloc + mmap. Cependant, je ne suis pas sûr que vous puissiez allouer autant de mémoire de manière contiguë et l'avoir toujours au même endroit. Bien sûr, si tout est toujours le même, vous obtiendrez peut-être une adresse constante. Cependant, chaque fois que vous changez le code du noyau, vous obtiendrez une adresse différente, donc je n'irais pas avec la solution kmalloc.

Je pense que vous devriez réserver de la mémoire au démarrage, c'est-à-dire réserver de la mémoire physique afin qu'elle ne soit pas touchée par le noyau. Ensuite, vous pouvez cartographier cette mémoire qui vous donnera une adresse virtuelle du noyau, puis vous pouvez la mapper et écrire un pilote de périphérique Nice.

Cela nous ramène à pilotes de périphériques Linux au PDF. Jetez un œil au chapitre 15, il décrit cette technique à la page 443

Edit: ioremap et mmap. Je pense que cela pourrait être plus facile de déboguer en faisant les choses en deux étapes: commencez par obtenir le bon ioremap et testez-le en utilisant une opération de périphérique de caractères, c'est-à-dire en lecture/écriture. Une fois que vous savez que vous pouvez accéder en toute sécurité à toute la mémoire ioremappée à l'aide de la lecture/écriture, vous essayez de mapper toute la plage ioremappée.

Et si vous avez des problèmes, posez une autre question sur le mmaping

Edit: remap_pfn_range ioremap renvoie une virtual_adress, que vous devez convertir en pfn pour remap_pfn_ranges. Maintenant, je ne comprends pas exactement ce qu'est un pfn (Page Frame Number), mais je pense que vous pouvez obtenir un appel

virt_to_phys(pt) >> PAGE_SHIFT

Ce n'est probablement pas la bonne façon (tm) de le faire, mais vous devriez l'essayer

Vous devez également vérifier que FOO_MEM_OFFSET est l'adresse physique de votre bloc RAM. C'est-à-dire avant que quoi que ce soit ne se produise avec le mmu, votre mémoire est disponible à 0 dans la carte mémoire de votre processeur.

16
shodanex

Désolé de répondre mais pas tout à fait répondre, j'ai remarqué que vous avez déjà modifié la question. Veuillez noter que SO ne nous informe pas lorsque vous modifiez la question. Je donne une réponse générique ici, lorsque vous mettez à jour la question, veuillez laisser un commentaire, puis je modifierai ma réponse.

Oui, vous allez avoir besoin d'écrire un module. Cela revient à utiliser kmalloc() (allouer une région dans l'espace noyau) ou vmalloc() (allouer une région dans l'espace utilisateur).

L'exposition de l'avant est facile, exposer ce dernier peut être pénible à l'arrière avec le type d'interface que vous décrivez comme nécessaire. Vous avez noté que 1,5 Mo est une estimation approximative de la quantité que vous devez réellement réserver, est-ce que c'est du fer? C'est-à-dire êtes-vous à l'aise de prendre cela dans l'espace du noyau? Pouvez-vous gérer adéquatement ENOMEM ou EIO à partir de l'espace utilisateur (ou même en veille disque)? IOW, qu'est-ce qui se passe dans cette région?

En outre, la concurrence va-t-elle poser problème? Si oui, allez-vous utiliser un futex? Si la réponse à l'un ou l'autre est "oui" (en particulier ce dernier), il est probable que vous devrez mordre la balle et aller avec vmalloc() (ou risquer de pourrir le noyau de l'intérieur). De plus, si vous pensez même à une interface ioctl() avec le périphérique char (en particulier pour une idée de verrouillage ad hoc), vous voulez vraiment aller avec vmalloc().

Aussi, avez-vous lu this ? De plus, nous n'abordons même pas ce que grsec/selinux va penser de cela (s'il est utilisé).

14
Tim Post

/ dev/mem est correct pour les simples pics et pics de registre, mais une fois que vous avez traversé des interruptions et DMA territoire, vous devriez vraiment écrire un pilote en mode noyau. Ce que vous avez fait pour votre mémoire précédente- les OS sans gestion ne se greffent tout simplement pas bien à un OS à usage général comme Linux.

Vous avez déjà pensé au problème d'allocation de tampon DMA. Maintenant, pensez à l'interruption "DMA done" de votre appareil. Comment allez-vous installer une routine de service d'interruption?

En outre,/dev/mem est généralement verrouillé pour les utilisateurs non root, donc ce n'est pas très pratique pour une utilisation générale. Bien sûr, vous pouvez le modifier, mais vous avez ensuite ouvert un gros trou de sécurité dans le système.

Si vous essayez de garder la base de code du pilote similaire entre les systèmes d'exploitation, vous devriez envisager de la refactoriser dans des couches distinctes en mode utilisateur et noyau avec une interface de type IOCTL entre les deux. Si vous écrivez la partie en mode utilisateur en tant que bibliothèque générique de code C, il devrait être facile de porter entre Linux et d'autres systèmes d'exploitation. La partie spécifique au système d'exploitation est le code en mode noyau. (Nous utilisons ce type d'approche pour nos chauffeurs.)

Il semble que vous ayez déjà conclu qu'il est temps d'écrire un pilote de noyau, vous êtes donc sur la bonne voie. Le seul conseil que je puisse ajouter est de lire ces livres de bout en bout.

Pilotes de périphériques Linux

Comprendre le noyau Linux

(Gardez à l'esprit que ces livres datent de 2005 environ, donc les informations sont un peu datées.)

4
myron-semack

Avez-vous regardé le paramètre du noyau 'memmap'? Sur i386 et X64_64, vous pouvez utiliser le paramètre memmap pour définir comment le noyau va gérer des blocs de mémoire très spécifiques (voir la documentation paramètre du noyau Linux ). Dans votre cas, vous souhaitez marquer la mémoire comme "réservée" afin que Linux ne la touche pas du tout. Ensuite, vous pouvez écrire votre code pour utiliser cette adresse et cette taille absolues (malheur à vous si vous sortez de cet espace).

1
Craig Trader

Je ne suis de loin pas un expert en la matière, ce sera donc une question pour vous plutôt qu'une réponse. Y a-t-il une raison pour laquelle vous ne pouvez pas simplement créer une petite partition de disque RAM et l'utiliser uniquement pour votre application? Cela ne vous donnerait-il pas un accès garanti au même bloc de mémoire? Je ne suis pas sûr qu'il y aurait des problèmes de performances d'E/S ou des frais supplémentaires associés à cette opération. Cela suppose également que vous pouvez dire au noyau de partitionner une plage d'adresses spécifique en mémoire, vous ne savez pas si c'est possible.

Je m'excuse pour la question newb, mais j'ai trouvé votre question intéressante, et je suis curieux de savoir si le disque RAM pourrait être utilisé de cette manière.

1
pseudosaint