Linux 中的直接内存访问 [英] Direct Memory Access in Linux

查看:37
本文介绍了Linux 中的直接内存访问的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to access physical memory directly for an embedded Linux project, but I'm not sure how I can best designate memory for my use.

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

If I boot my device regularly, and access /dev/mem, I can easily read and write to just about anywhere I want. However, in this, I'm accessing memory that can easily be allocated to any process; which I don't want to do

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

My code for /dev/mem is (all error checking, etc. removed):

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 设置为高于该值(但低于物理内存),但它似乎并没有始终如一地访问相同的内存.

And this works. However, I'd like to be using memory that no one else will touch. I've tried limiting the amount of memory that the kernel sees by booting with mem=XXXm, and then setting BASE_ADDRESS to something above that (but below the physical memory), but it doesn't seem to be accessing the same memory consistently.

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

Based on what I've seen online, I suspect I may need a kernel module (which is OK) which uses either ioremap() or remap_pfn_range() (or both???), but I have absolutely no idea how; can anyone help?

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

What I want is a way to always access the same physical memory (say, 1.5MB worth), and set that memory aside so that the kernel will not allocate it to any other process.

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

I'm trying to reproduce a system we had in other OSes (with no memory management) whereby I could allocate a space in memory via the linker, and access it using something like

*(unsigned char *)0x12345678

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

I guess I should provide some more detail. This memory space will be used for a RAM buffer for a high performance logging solution for an embedded application. In the systems we have, there's nothing that clears or scrambles physical memory during a soft reboot. Thus, if I write a bit to a physical address X, and reboot the system, the same bit will still be set after the reboot. This has been tested on the exact same hardware running VxWorks (this logic also works nicely in Nucleus RTOS and OS20 on different platforms, FWIW). My idea was to try the same thing in Linux by addressing physical memory directly; therefore, it's essential that I get the same addresses each boot.

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

I should probably clarify that this is for kernel 2.6.12 and newer.

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

Here's my code, first for the kernel module, then for the userspace application.

为了使用它,我用 mem=95m 启动,然后是 insmod foo-module.ko,然后是 mknod mknod/dev/foo c 32 0,然后运行 ​​foo-user ,它会死掉.在 gdb 下运行显示它在分配时死亡,尽管在 gdb 中,我无法取消引用从 mmap 获得的地址(尽管 printf 可以)

To use it, I boot with mem=95m, then insmod foo-module.ko, then mknod mknod /dev/foo c 32 0, then run foo-user , where it dies. Running under gdb shows that it dies at the assignment, although within gdb, I cannot dereference the address I get from mmap (although printf can)

foo-module.c

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
");
    printk(KERN_INFO "Version %s
", foo_version);
    printk(KERN_INFO "Preparing device /dev/foo
");
    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.
");
    printk(KERN_NOTICE "Make sure to run mknod /dev/foo c %d 0
", FOO_MAJOR);
    printk(KERN_INFO "Allocating memory
");
    pt = ioremap(FOO_BUFFER_OFFSET, FOO_BUFFER_SIZE);
    if (pt == NULL) {
        printk(KERN_ERR "Unable to remap memory
");
        return 1;
    }
    printk(KERN_INFO "ioremap returned %p
", pt);
    return 0;
}
static void __exit foo_exit(void)
{
    printk(KERN_NOTICE "Unloading foo support module
");
    unregister_chrdev(FOO_MAJOR, FOO_NAME);
    if (pt != NULL) {
        printk(KERN_INFO "Unmapping memory at %p
", pt);
        iounmap(pt);
    } else {
        printk(KERN_WARNING "No memory to unmap!
");
    }
    return;
}
static int foo_open(struct inode *inode, struct file *file)
{
    printk("foo_open
");
    return 0;
}
static int foo_release(struct inode *inode, struct file *file)
{
    printk("foo_release
");
    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!
");
        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)
", 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
", 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...
");
        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.
",(unsigned long) mptr);
    printf("mptr points to 0x%lX. *mptr = 0x%X
", (unsigned long) mptr, *mptr);
    mptr[0] = 'a';
    mptr[1] = 'b';
    printf("mptr points to 0x%lX. *mptr = 0x%X
", (unsigned long) mptr, *mptr);
    close(fd);
    return 0;
}

推荐答案

我想你可以找到很多关于 kmalloc + mmap 部分的文档.但是,我不确定您是否可以以连续的方式 kmalloc 这么多内存,并且始终将其放在同一个地方.当然,如果一切都是一样的,那么你可能会得到一个固定的地址.但是,每次更改内核代码时,都会得到不同的地址,因此我不会采用kmalloc 解决方案.

I think you can find a lot of documentation about the kmalloc + mmap part. However, I am not sure that you can kmalloc so much memory in a contiguous way, and have it always at the same place. Sure, if everything is always the same, then you might get a constant address. However, each time you change the kernel code, you will get a different address, so I would not go with the kmalloc solution.

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

I think you should reserve some memory at boot time, ie reserve some physical memory so that is is not touched by the kernel. Then you can ioremap this memory which will give you a kernel virtual address, and then you can mmap it and write a nice device driver.

这让我们回到 PDF 格式的 linux 设备驱动程序.看看第 15 章,它在第 443 页描述了这种技术

This take us back to linux device drivers in PDF format. Have a look at chapter 15, it is describing this technique on page 443

ioremap 和 mmap.我认为这可能更容易通过两步来调试服务器:首先获取 ioremap对,并使用字符设备操作对其进行测试,即读/写.一旦你知道你可以安全地使用读/写访问整个ioremapped内存,然后你尝试mmap整个ioremapped范围.

Edit : ioremap and mmap. I think this might be easier to debug doing things in two step : first get the ioremap right, and test it using a character device operation, ie read/write. Once you know you can safely have access to the whole ioremapped memory using read / write, then you try to mmap the whole ioremapped range.

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

And if you get in trouble may be post another question about mmaping

remap_pfn_rangeioremap 返回一个 virtual_adress,您必须将其转换为 remap_pfn_ranges 的 pfn.现在,我不明白 pfn(页面框架编号)是什么,但我认为您可以拨打电话

Edit : remap_pfn_range ioremap returns a virtual_adress, which you must convert to a pfn for remap_pfn_ranges. Now, I don't understand exactly what a pfn (Page Frame Number) is, but I think you can get one calling

virt_to_phys(pt) >> PAGE_SHIFT

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

This probably is not the Right Way (tm) to do it, but you should try it

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

You should also check that FOO_MEM_OFFSET is the physical address of your RAM block. Ie before anything happens with the mmu, your memory is available at 0 in the memory map of your processor.

这篇关于Linux 中的直接内存访问的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆