一尘不染

Linux中的直接内存访问

linux

我正在尝试直接为嵌入式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的完全相同的硬件上进行了测试(此逻辑在不同平台(FWIW)上的Nucleus
RTOS和OS20中也能很好地工作)。我的想法是通过直接寻址物理内存在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;
}

阅读 517

收藏
2020-06-02

共1个答案

一尘不染

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

我认为您应该在引导时保留一些内存,即保留一些物理内存,以免被内核触及。然后,您可以映射该内存,这将为您提供内核虚拟地址,然后您可以映射它并编写一个不错的设备驱动程序。

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

编辑:ioremap和mmap。我认为分两步调试服务器可能会更容易:首先正确设置ioremap,然后使用字符设备操作(即读/写)对其进行测试。一旦知道可以使用读/写安全地访问整个ioremapped内存,则可以尝试映射整个ioremapped范围。

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

编辑:remap_pfn_range
ioremap返回virtual_adress,您必须将其转换为remap_pfn_ranges的pfn。现在,我不完全了解pfn(页面框架号)是什么,但我认为您可以接到一个电话

virt_to_phys(pt) >> PAGE_SHIFT

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

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

2020-06-02