首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Linux中的直接内存访问

Linux中的直接内存访问
EN

Stack Overflow用户
提问于 2009-03-15 13:01:42
回答 5查看 45.7K关注 0票数 45

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

如果我定期引导我的设备,并访问/dev/mem,我就可以很容易地读写我想要的任何地方。但是,在这种情况下,我访问的内存可以很容易地分配给任何进程;我不想这样做

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

代码语言:javascript
复制
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 )的方法,并将该内存放在一边,这样内核就不会将其分配给任何其他进程。

我正在尝试复制我们在其他OSes中使用的系统(没有内存管理),这样我就可以通过链接器在内存中分配一个空间,并使用如下内容访问它

代码语言:javascript
复制
*(unsigned char *)0x12345678

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

我应该澄清一下,这是针对内核2.6.12和更高版本的。

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

要使用它,我使用mem=95m启动,然后输入mknod foo-module.ko,然后是mknod mknod/dev/fooc320,然后运行foo-user,在那里它就死了。在gdb下运行显示它在赋值时终止,尽管在gdb中,我不能取消引用我从mmap获得的地址(尽管printf可以)。

foo-module.c

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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;
}
EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2009-03-16 09:07:34

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

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

这就把我们带回了linux device drivers格式。请看第15章,它在第443页描述了这种技术。

编辑: ioremap和mmap。我认为这可能更容易调试,分两步做事情:首先获得正确的ioremap,并使用字符设备操作测试它,即读/写。一旦您知道可以使用读/写安全地访问整个ioremapped内存,然后尝试mmap整个ioremapped范围。

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

编辑: remap_pfn_range ioremap返回一个virtual_adress,您必须将其转换为remap_pfn_ranges的pfn。现在,我不是很清楚pfn (Page Frame Number,页框编号)是什么,但我认为你可以得到一个调用

代码语言:javascript
复制
virt_to_phys(pt) >> PAGE_SHIFT

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

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

票数 16
EN

Stack Overflow用户

发布于 2009-03-15 14:23:34

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

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

暴露前者是很容易的,暴露后者可能是一件痛苦的事情,因为你所描述的那种界面是你所需要的。你注意到1.5MB是你实际需要预留的粗略估计,这是铁板一块的吗?也就是说,你对从内核空间中获取它感到舒服吗?您能否充分处理来自用户空间(甚至是磁盘休眠)的ENOMEM或EIO?哇,这个地区到底发生了什么?

另外,并发性会是一个问题吗?如果是这样,你会使用futex吗?如果答案是肯定的(特别是后者),那么你很可能不得不咬紧牙关使用vmalloc() (否则就会冒着内核内部腐烂的风险)。此外,如果您甚至正在考虑为char设备提供ioctl()接口(特别是为了一些临时锁定的想法),那么您真的希望使用vmalloc()

另外,你读过this吗?此外,我们甚至没有涉及grsec / selinux将如何考虑这一点(如果正在使用)。

票数 14
EN

Stack Overflow用户

发布于 2009-03-15 19:27:53

/dev/mem对于简单的寄存器窥视和戳是可以接受的,但是一旦进入中断和DMA领域,就真的应该编写内核模式驱动程序了。您为以前的无内存管理的OSes所做的事情并不能很好地移植到像Linux这样的通用操作系统上。

您已经考虑过DMA缓冲区分配问题。现在,考虑一下来自设备的"DMA完成“中断。您将如何安装中断服务例程?

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

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

看起来您已经得出结论,是时候编写内核驱动程序了,所以您已经走上了正确的道路。我唯一的建议是把这些书从头到尾读一遍。

Linux Device Drivers

Understanding the Linux Kernel

(请记住,这些书大约是2005年左右的,所以信息有点过时。)

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/647783

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档