44 votes

Accès direct à la mémoire sous Linux

Je suis en train d'accéder à la mémoire physique directement pour un Linux embarqué projet, mais je ne suis pas sûr de savoir comment je peux désigner mémoire pour mon utilisation.

Si je démarre mon appareil régulièrement, et d'accéder à /dev/mem, je peux facilement lire et écrire à peu près n'importe où je veux. Toutefois, dans ce domaine, je suis accès à de la mémoire qui peut facilement être attribuée à n'importe quel procédé; que je ne veux pas faire

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

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 cela fonctionne. Cependant, j'aimerais utiliser de la mémoire que personne ne le touche. 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, mais en dessous de la mémoire physique), mais il ne semble pas à l'accès à la mémoire même de manière cohérente.

Basé sur ce que j'ai vu en ligne, je pense que je peut avoir besoin d'un module du 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 toujours une façon d'accéder à la même mémoire physique (par exemple, 1.5 MO), et le jeu de mémoire côté de sorte que le noyau ne pas affecter à un autre processus.

Je suis en train de reproduire un système que nous avions dans d'autres Systèmes d'exploitation (sans la gestion de la mémoire) dans lequel j'ai pu allouer de l'espace dans la mémoire via l'éditeur de liens, et d'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ée pour mémoire tampon pour une haute performance de la solution d'enregistrement 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 logiciel. Ainsi, si j'écris un peu à une adresse physique X, et redémarrez le système, les mêmes bits seront toujours ensemble après le redémarrage de l'ordinateur. Cela a été testé sur exactement le même matériel en cours d'exécution VxWorks (cette logique fonctionne aussi bien dans le Noyau RTOS et OS20 sur différentes plates-formes, FWIW). Mon idée était d'essayer la même chose sous Linux par l'adressage de la mémoire physique directement; par conséquent, il est essentiel que je reçois la même adresse à chaque démarrage.

Dois-je préciser que c'est pour le noyau 2.6.12 et les plus récents.

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

Pour l'utiliser, je démarre avec mem=95m, puis insmod foo-module.ko, puis mknod mknod /dev/foo c 32 0, puis exécutez foo-utilisateur , où il meurt. Fonctionnant sous gdb montre qu'il meurt à la mission, bien que dans gdb, je ne peux pas déréférencer l'adresse que je reçois de mmap (bien que printf peut)

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

16voto

shodanex Points 7318

Je pense que vous pouvez trouver beaucoup de documentation sur le kmalloc + mmap partie. Cependant, je ne suis pas sûr que vous pouvez kmalloc autant de mémoire contiguë façon, et l'avoir toujours à la même place. Bien sûr, si tout est toujours la même, alors vous pourriez obtenir une constante de l'adresse. Cependant, chaque fois que vous modifiez le code du noyau, vous allez obtenir une autre adresse, donc je ne voudrais pas aller avec le kmalloc solution.

Je pense que vous devriez réserver de la mémoire au moment du démarrage, c'est à dire la réserve de la mémoire physique de sorte que c'est n'est pas touché par le noyau. Ensuite, vous pouvez ioremap cette mémoire qui vous donnera un noyau d'adressage virtuel, et puis vous pouvez mmap et d'écrire un beau pilote de périphérique.

Cela nous ramène à linux pilotes de périphérique au format PDF. Jetez un oeil au chapitre 15, il est décrit cette technique à la page 443

Edit : ioremap et mmap. Je pense que cela pourrait être plus facile à déboguer faire les choses en deux étapes : d'abord que le ioremap droit, et de la tester à l'aide d'un périphérique caractère de l'opération, c'est à dire en lecture/écriture. Une fois que vous savez que vous pouvez avoir en toute sécurité l'accès à l'ensemble de la ioremapped mémoire à l'aide de lecture / écriture, alors vous essayez de mmap l'ensemble de la ioremapped gamme.

Et si vous vous trouvez en difficulté, peut-être poster une autre question à propos de mmaping

Edit : remap_pfn_range ioremap renvoie une virtual_adress, qui vous doit se convertir à un pfn pour remap_pfn_ranges. Maintenant, je ne comprends pas exactement ce qu'est un pfn (Page Numéro de l'Image), mais je pense que vous pouvez en obtenir un appel

virt_to_phys(pt) >> PAGE_SHIFT

Ce n'est probablement pas la bonne Façon (tm) pour le faire, mais vous devez l'essayer

Vous devriez également vérifier que FOO_MEM_OFFSET est l'adresse physique de votre RAM bloc. Ie avant que quelque chose se passe avec la mmu, votre mémoire est disponible à 0 dans la carte mémoire de votre processeur.

14voto

Tim Post Points 21270

Désolé de répondre, mais pas tout à fait la réponse, j'ai remarqué que vous avez déjà édité la question. Veuillez noter qu'AFIN de ne pas nous informer lorsque vous modifiez la question. Je suis en train de donner une réponse générique ici, lorsque vous mettez à jour les questions s'il vous plaît laissez un commentaire, je vais modifier ma réponse.

Oui, vous allez avoir besoin d'écrire un module. Ce qui me vient à l'utilisation de l' kmalloc() (allocation d'une région dans l'espace noyau) ou vmalloc() (allocation d'une région dans l'espace utilisateur).

Exposer l'avant est facile, en exposant ce dernier peut être une douleur à l'arrière avec le type d'interface que vous décrivez comme nécessaire. Vous avez noté 1,5 MO est une estimation approximative de combien vous avez réellement besoin de réserver, c'est que iron clad? I. e vous êtes à l'aise qu'à partir de l'espace du noyau? Pouvez-vous faire face adéquatement à ENOMEM ou EIO partir de l'espace utilisateur (ou même la veille du disque)? OIE, ce qui va dans cette région?

Aussi, est la simultanéité va être un problème avec cela? Si oui, allez-vous être à l'aide d'un futex? Si la réponse est " oui " (surtout le second), il est probable que vous aurez à mordre la balle et aller de l' vmalloc() (ou risque de noyau pourrir de l'intérieur). Aussi, si vous êtes même de PENSER à un ioctl() interface pour le char de l'appareil (en particulier pour certains ad-hoc de verrouillage idée), vous voulez vraiment aller avec vmalloc().

Aussi, avez-vous lu cela? De Plus, nous ne sommes pas à même de toucher sur ce que grsec / selinux vont penser de cela (si utilisé).

4voto

msemack Points 3116

/dev/mem est acceptable pour un simple registre de sommets et de tâtonnements, mais une fois que vous croisez dans les interruptions DMA et territoire, vous devriez vraiment écrire un pilote en mode noyau. Ce que vous avez fait pour votre précédente de gestion de la mémoire-moins de Systèmes d'exploitation n'a tout simplement pas de greffe bien à un Usage Général des OS comme Linux.

Vous avez déjà pensé à propos de la mémoire tampon DMA problème d'allocation. Maintenant, pensez à la "DMA fait" interruption de votre appareil. Comment allez-vous installer une Routine de Service d'Interruption?

En outre, /dev/mem est généralement bloqué pour les utilisateurs non-root, de sorte qu'il n'est pas très pratique pour un usage général. Bien sûr, vous pourriez la commande chmod, mais alors que vous avez ouvert un gros trou de sécurité dans le système.

Si vous êtes en essayant de garder le pilote de la base de code similaire entre les Systèmes d'exploitation, vous devriez envisager de refactoring en séparer de l'utilisateur et en mode noyau couches avec un IOCTL-comme l'interface entre les deux. Si vous écrivez la partie mode utilisateur comme une bibliothèque générique de code C, il devrait être facile de port entre Linux et d'autres Systèmes d'exploitation. L'OS est la partie de code en mode noyau. (Nous utilisons ce type d'approche pour nos pilotes.)

Il semble que vous ayez déjà conclu qu'il est temps d'écrire un noyau conducteur, si vous êtes sur la bonne voie. Le seul conseil que je peux ajouter, c'est de lire ces livres traitent de couverture.

Linux Pilotes De Périphérique

La compréhension du Noyau Linux

(Gardez à l'esprit que ces livres sont circa-2005, de sorte que l'information date un peu.)

1voto

pseudosaint Points 31

Je suis de loin pas un expert sur ces questions, ce sera donc une question à vous plutôt que d'une réponse. Est-il une raison vous ne pouvez pas simplement faire une petite ram partition de disque et de les utiliser uniquement pour votre application? Ne serait-ce pas vous donner la garantie d'avoir accès à la même partie de la mémoire? Je ne suis pas sûr d'y I/O des problèmes de performance, ou d'autres frais généraux associés avec le faire. Cela suppose également que l'on peut dire que le noyau de la partition d'une plage d'adresses spécifique dans la mémoire, pas sûr si c'est possible.

Je m'excuse pour les newb question, mais je trouve ta question intéressante, et je suis curieux de savoir si la ram disque peut être utilisé dans une telle voie.

1voto

Craig Trader Points 8924

Avez-vous regardé le "memmap' paramètre de noyau? Sur les architectures i386 et X64_64, vous pouvez utiliser le memmap paramètre pour définir la façon dont le noyau main très spécifique des blocs de mémoire (voir le noyau Linux paramètre de la documentation). Dans votre cas, vous voulez marquer la mémoire comme "réservée" pour que Linux ne la touche pas du tout. Ensuite, vous pouvez écrire votre code à utiliser que l'adresse absolue et de la taille (malheur à vous si vous passez d'une étape à l'extérieur de l'espace).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X