Linux中的直接内存访问
我试图直接访问一个embedded式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()(或两者)的内核模块(这是OK),但我绝对不知道如何; 谁能帮忙?
编辑:我想要的是一种方式来总是访问相同的物理内存(比如1.5MB),并将内存放在一边,以便内核不会将其分配给任何其他进程。
我试图重现我们在其他操作系统(没有内存pipe理),我可以通过链接器在内存中分配一个空间,并使用类似
*(unsigned char *)0x12345678
编辑2:我想我应该提供一些更多的细节。 此内存空间将用于RAM缓冲区,以用于embedded式应用程序的高性能日志logging解决scheme。 在我们的系统中,没有任何东西在软重启过程中清除或加扰物理内存。 因此,如果我向物理地址X写入一个位,并重新启动系统,重新启动后仍然会设置相同的位。 这已经在运行VxWorks的完全相同的硬件上进行了testing(这个逻辑在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下运行表明它在分配中死了,尽pipe在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; }
我想你可以find很多关于kmalloc + mmap部分的文档。 不过,我不确定你是否可以连续记忆这么多的记忆,而且总是在同一个地方。 当然,如果一切都一样,那么你可能会得到一个不变的地址。 但是,每次更改内核代码时,都会得到不同的地址,所以我不会使用kmalloc解决scheme。
我想你应该在启动的时候保留一些内存,也就是保留一些物理内存,这样内核就不会触及它了。 然后你可以把这个内存映射到内核虚拟地址,然后你可以把它映射到一个很好的设备驱动上。
这将我们带回到PDF格式的Linux设备驱动程序 。 看看第15章,它是在443页描述这个技术
编辑:ioremap和mmap。 我认为这可能更容易分两步进行debugging:首先获取ioremap,然后使用字符设备操作(即读/写)对其进行testing。 一旦你知道你可以安全地使用读/写访问整个ioremapped内存,那么你试图mmap整个ioremapped范围。
如果您遇到麻烦,可能会发布另一个关于mmaping的问题
编辑:remap_pfn_range ioremap返回一个virtual_adress,您必须将其转换为remap_pfn_ranges的pfn。 现在,我不太清楚pfn(Page Frame Number)是什么,但我认为你可以打一个电话
virt_to_phys(pt) >> PAGE_SHIFT
这可能不是正确的做法,但你应该尝试一下
你还应该检查FOO_MEM_OFFSET是你的RAM块的物理地址。 也就是说,在mmu发生任何事情之前,您的内存在处理器的内存映射中可用0。
很抱歉回答,但没有回答,我注意到你已经编辑了这个问题。 请注意,当您编辑问题时,SO不会通知我们。 我在这里给出一个通用的答案,当你更新的问题,请留下评论,然后我会编辑我的答案。
是的,你将需要编写一个模块。 它涉及到使用kmalloc()
(在内核空间分配一个区域)或vmalloc()
(在用户空间分配一个区域)。
揭露事先是很容易的,揭露后者可能是一种背后的痛苦,你需要描述的那种界面。 你注意到1.5 MB是你真正需要保留多less的粗略估计,是铁包裹? 也就是说你是否愿意从内核空间中获取? 你能够充分处理来自用户空间(甚至磁盘睡眠)的ENOMEM或EIO吗? IOW,这个地区到底是怎么回事?
另外,并发将是一个问题呢? 如果是这样,你会使用futex吗? 如果对“是”(尤其是后者)的回答是肯定的,那么你很可能不得不硬着头皮走vmalloc()
(或者从内部冒风险)。 另外,如果你甚至想到一个到char设备的ioctl()
接口(特别是对于一些ad-hoclocking想法),你真的想用vmalloc()
去。
另外,你读过这个吗? 另外,我们甚至不会触及grsec / selinux会怎么想(如果使用的话)。
/ dev / mem对于简单的寄存器窥探和戳是没问题的,但是一旦你进入了中断和DMA区域,你应该写一个内核模式驱动程序。 你以前为无内存pipe理的操作系统所做的事情根本不能很好地适应像Linux这样的通用操作系统。
您已经考虑过DMA缓冲区分配问题。 现在,考虑一下设备的“DMA完成”中断。 你打算如何安装一个中断服务程序?
另外,/ dev / mem通常被非root用户locking,因此对于一般用户来说不是很实用。 当然,你可以修改它,但是你已经在系统中打开了一个很大的安全漏洞。
如果您试图保持操作系统之间的驱动程序代码库相似,则应该考虑将其重构为独立的用户和内核模式层,并在其间插入类似IOCTL的接口。 如果将用户模式部分编写为C代码的通用库,则应该很容易在Linux和其他操作系统之间进行移植。 特定于操作系统的部分是内核模式代码。 (我们的司机使用这种方法。)
看来你已经得出结论,现在是时候编写一个内核驱动程序,所以你是在正确的轨道上。 我可以补充的唯一build议是阅读这些书封面。
Linux设备驱动程序
了解Linux内核
(请记住,这些书大约在2005年,所以信息有点过时。)
我在这些问题上至今还没有专家,所以这对你来说是一个问题,而不是一个答案。 是否有任何理由,你不能只是一个小的内存磁盘分区,只用于您的应用程序? 这不会给你保证访问相同的内存块吗? 我不确定是否会有任何I / O性能问题,或与此相关的额外开销。 这也假设你可以告诉内核在内存中划分一个特定的地址范围,不知道这是否可能。
我为新问题表示歉意,但是我发现你的问题很有意思,而且我很好奇,是否可以用这种方式使用RAM磁盘。
你看过'memmap'内核参数吗? 在i386和X64_64上,可以使用memmap参数来定义内核将如何处理非常特定的内存块(请参阅Linux内核参数文档)。 在你的情况下,你想把内存标记为“保留”,这样Linux根本不会触及它。 然后你可以编写你的代码来使用这个绝对的地址和大小(如果你超出这个空间的话,那么对你来说是不好的)。