Linux中的直接内存访问

时间:2009-03-15 13:01:42

标签: linux memory memory-management embedded linux-kernel

我正在尝试直接访问嵌入式Linux项目的物理内存,但我不确定如何最好地为我的内容指定内存。

如果我定期启动设备并访问/ dev / mem,我可以轻松地读取和写入我想要的任何地方。但是,在这里,我正在访问可以轻松分配给任何进程的内存;我不想做什么

我的/ dev / mem代码是(删除所有错误检查等):

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

这很有效。但是,我想使用其他人无法触及的记忆。我已经尝试通过使用mem = XXXm启动来限制内核看到的内存量,然后将BASE_ADDRESS设置为高于该值(但低于物理内存),但它似乎并不是一致地访问相同的内存。

根据我在网上看到的内容,我怀疑我可能需要一个使用ioremap()或remap_pfn_range()(或两者兼而有之)的内核模块(可以),但我完全不知道如何;有人可以帮忙吗?

编辑: 我想要的是一种始终访问相同物理内存(比如1.5MB)的方法,并将该内存放在一边,以便内核不会将其分配给任何其他进程。

我正在尝试重现我们在其他操作系统中使用的系统(没有内存管理),我可以通过链接器在内存中分配空间,并使用类似

的方式访问它
*(unsigned char *)0x12345678

EDIT2: 我想我应该提供更多细节。此内存空间将用于RAM缓冲区,以用于嵌入式应用程序的高性能日志记录解决方案。在我们拥有的系统中,在软重启期间没有什么可以清除或扰乱物理内存。因此,如果我向物理地址X写入一个位并重新启动系统,则重新启动后仍将设置相同的位。这已经在运行VxWorks的完全相同的硬件上进行了测试(这种逻辑在不同平台上的Nucleus RTOS和OS20上也很好用,FWIW)。我的想法是通过直接解决物理内存在Linux中尝试相同的事情;因此,每次启动时都必须获得相同的地址。

我应该澄清这是针对内核2.6.12和更新的。

EDIT3: 这是我的代码,首先是内核模块,然后是用户空间应用程序。

要使用它,我用mem = 95m启动,然后insmod foo-module.ko,然后mknod mknod / dev / foo c 32 0,然后运行foo-user,它会死掉。在gdb下运行表明它在赋值时死了,虽然在gdb中,我无法取消引用从mmap获得的地址(虽然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;
}

5 个答案:

答案 0 :(得分:15)

我认为你可以找到很多关于kmalloc + mmap部分的文档。 但是,我不确定你能以连续的方式获得如此多的记忆,并且总是在同一个地方。当然,如果一切都是一样的话,那么你可能会得到一个恒定的地址。但是,每次更改内核代码时,都会得到不同的地址,因此我不会使用kmalloc解决方案。

我认为你应该在启动时保留一些内存,即保留一些物理内存,这样内核就不会触及它。然后你可以ioremap这个将给你的内存 一个内核虚拟地址,然后你可以mmap它并写一个不错的设备驱动程序。

这将我们带回PDF格式的linux device drivers。看一下第15章,它正在描述第443页的这种技术

编辑:ioremap和mmap。 我认为这可能更容易分两步调试:首先获取ioremap 对,并使用字符设备操作测试它,即读/写。一旦你知道你可以安全地使用读/写访问整个ioremapped内存,那么你尝试mmap整个ioremapped范围。

如果你遇到麻烦可能会发布另一个关于mmaping的问题

编辑:remap_pfn_range ioremap返回一个virtual_adress,您必须将其转换为pfn以获取remap_pfn_ranges。 现在,我不明白究竟什么是pfn(Page Frame Number),但我认为你可以得到一个叫

virt_to_phys(pt) >> PAGE_SHIFT

这可能不是正确的方法(tm),但你应该尝试一下

您还应该检查FOO_MEM_OFFSET是RAM块的物理地址。也就是说,在mmu发生任何事情之前,你的内存在处理器的内存映射中为0。

答案 1 :(得分:14)

很抱歉回答但不完全回答,我注意到您已经编辑了这个问题。请注意,在您编辑问题时,SO不会通知我们。我在这里给出一个通用答案,当你更新问题时请发表评论,然后我会编辑我的答案。

是的,你需要写一个模块。它归结为使用kmalloc()(在内核空间中分配区域)或vmalloc()(在用户空间中分配区域)。

暴露前置很容易,暴露后者可能是后方的痛苦,你需要的界面类型。你注意到1.5 MB是你实际需要预留多少的粗略估计,那是铁板吗?你是否觉得从内核空间中获取它?你能从用户空间(甚至磁盘睡眠)充分处理ENOMEM或EIO吗? IOW,这个地区会发生什么?

此外,并发会成为一个问题吗?如果是这样,你会使用futex吗?如果两者的答案都是'是'(尤其是后者),那么很可能你必须咬紧牙关并使用vmalloc()(或者从内部冒犯内核腐烂)。此外,如果您甚至想到关于char设备的ioctl()接口(特别是对于某些临时锁定的想法),您真的想要使用vmalloc()

另外,你看过this吗?此外,我们甚至没有涉及到grsec / selinux会想到这个问题(如果在使用中)。

答案 2 :(得分:3)

/ dev / mem对于简单的寄存器查看和戳是可以的,但是一旦你进入中断和DMA区域,你真的应该编写一个内核模式驱动程序。你为以前没有内存管理的操作系统所做的事情根本不适合像Linux这样的通用操作系统。

您已经考虑过DMA缓冲区分配问题。现在,考虑一下设备的“DMA done”中断。您打算如何安装中断服务程序?

此外,/ dev / mem通常被非root用户锁定,因此对于一般用途来说不太实用。当然,你可以对它进行调整,但是你已经在系统中打开了一个很大的安全漏洞。

如果您尝试在操作系统之间保持驱动程序代码库类似,则应考虑将其重构为单独的用户和放大器。内核模式层,中间有类似IOCTL的接口。如果将用户模式部分编写为C代码的通用库,则应该很容易在Linux和其他操作系统之间进行移植。特定于操作系统的部分是内核模式代码。 (我们为司机使用这种方法。)

似乎你已经得出结论是时候编写一个内核驱动程序了,所以你走在了正确的轨道上。我可以添加的唯一建议是阅读这些封面的书。

Linux Device Drivers

Understanding the Linux Kernel

(请记住,这些书大约是2005年,因此信息有点陈旧。)

答案 3 :(得分:1)

到目前为止,我对这些问题都不是专家,所以这对你来说是一个问题,而不是一个答案。有什么理由你不能只制作一个小的ram磁盘分区并仅将其用于你的应用程序吗?这会不会保证您可以访问相同的内存块?我不确定会出现任何I / O性能问题,或者与此相关的额外开销。这也假设你可以告诉内核在内存中划分特定的地址范围,不确定是否可能。

我为newb问题道歉,但我发现你的问题很有趣,并且好奇ram磁盘是否可以这样使用。

答案 4 :(得分:1)

你看过'memmap'内核参数了吗?在i386和X64_64上,您可以使用memmap参数来定义内核如何处理非常特定的内存块(请参阅Linux kernel parameter文档)。在您的情况下,您希望将内存标记为“保留”,以便Linux根本不触及它。然后你可以编写你的代码来使用那个绝对的地址和大小(如果你走出那个空间,那么你就可以了。)

相关问题