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 的完全相同的硬件上进行了测试(该逻辑在不同平台上的 Nucleus RTOS 和 OS20 中也能很好地工作,FWIW)。 我的想法是在 Linux 中尝试同样的事情,直接寻址物理内存; 因此,每次启动时获得相同的地址至关重要。
我可能应该澄清一下,这是针对内核 2.6.12 及更高版本的。
编辑3: 这是我的代码,首先是内核模块的代码,然后是用户空间应用程序的代码。
为了使用它,我用 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;
}
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.
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
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);
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.
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?
EDIT:
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
EDIT2:
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.
I should probably clarify that this is for kernel 2.6.12 and newer.
EDIT3:
Here's my code, first for the kernel module, then for the userspace application.
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
#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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我想你可以找到很多关于 kmalloc + mmap 部分的文档。
但是,我不确定您是否可以以连续的方式 kmalloc 这么多内存,并始终将其放在同一位置。 当然,如果一切都始终相同,那么您可能会得到一个恒定的地址。 但是,每次更改内核代码时,您都会得到不同的地址,因此我不会采用 kmalloc 解决方案。
我认为你应该在启动时保留一些内存,即保留一些物理内存,这样内核就不会触及它。 然后你可以重新映射这个内存,这会给你
一个内核虚拟地址,然后你可以映射它并编写一个漂亮的设备驱动程序。
这让我们回到 PDF 格式的 Linux 设备驱动程序。 看看第 15 章,它在第 443 页上描述了这种技术
编辑:ioremap 和 mmap。
我认为这可能更容易分两步调试:首先获取 ioremap
对了,并使用字符设备操作(即读/写)对其进行测试。 一旦您知道可以使用读/写安全地访问整个 ioremapped 内存,那么您可以尝试 mmap 整个 ioremapped 范围。
如果您遇到麻烦,可能会发布另一个有关 mmaping 的问题
编辑:remap_pfn_range
ioremap 返回一个 virtual_adress,您必须将其转换为 remap_pfn_ranges 的 pfn。
现在,我不明白pfn(页框号)到底是什么,但我认为你可以打电话给
这可能不是正确的方法(tm),但你应该尝试一下
你还应该检查一下FOO_MEM_OFFSET 是 RAM 块的物理地址。 也就是说,在 mmu 发生任何事情之前,您的内存在处理器内存映射中的 0 处可用。
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.
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.
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
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.
And if you get in trouble may be post another question about mmaping
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
This probably is not the Right Way (tm) to do it, but you should try it
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.
抱歉回答,但没有完全回答,我注意到您已经编辑了问题。 请注意,当您编辑问题时,SO 不会通知我们。 我在这里给出一个通用答案,当您更新问题时请发表评论,然后我会编辑我的答案。
是的,您将需要编写一个模块。 归根结底是使用
kmalloc()
(在内核空间中分配一个区域)或vmalloc()
(在用户空间中分配一个区域)。暴露先验信息很容易,但使用您根据需要描述的界面来暴露后者可能会很痛苦。 您指出 1.5 MB 是您实际需要保留多少的粗略估计,这是铁定的吗? 也就是说,您是否愿意从内核空间获取它? 您能否从用户空间(甚至磁盘睡眠)充分处理 ENOMEM 或 EIO? IOW,这个地区发生了什么?
另外,并发会成为一个问题吗? 如果是这样,你会使用 futex 吗? 如果其中一个的答案是“是”(尤其是后者),那么您可能必须硬着头皮使用
vmalloc()
(否则会面临内核内部腐烂的风险)。 另外,如果您甚至正在考虑 char 设备的ioctl()
接口(特别是对于某些临时锁定想法),那么您确实想使用vmalloc()
>。另外,你读过这个? 另外,我们甚至没有触及 grsec / selinux 对此的看法(如果使用的话)。
Sorry to answer but not quite answer, I noticed that you have already edited the question. Please note that SO does not notify us when you edit the question. I'm giving a generic answer here, when you update the question please leave a comment, then I'll edit my answer.
Yes, you're going to need to write a module. What it comes down to is the use of
kmalloc()
(allocating a region in kernel space) orvmalloc()
(allocating a region in userspace).Exposing the prior is easy, exposing the latter can be a pain in the rear with the kind of interface that you are describing as needed. You noted 1.5 MB is a rough estimate of how much you actually need to reserve, is that iron clad? I.e are you comfortable taking that from kernel space? Can you adequately deal with ENOMEM or EIO from userspace (or even disk sleep)? IOW, what's going into this region?
Also, is concurrency going to be an issue with this? If so, are you going to be using a futex? If the answer to either is 'yes' (especially the latter), its likely that you'll have to bite the bullet and go with
vmalloc()
(or risk kernel rot from within). Also, if you are even THINKING about anioctl()
interface to the char device (especially for some ad-hoc locking idea), you really want to go withvmalloc()
.Also, have you read this? Plus we aren't even touching on what grsec / selinux is going to think of this (if in use).
/dev/mem 对于简单的寄存器查看和戳戳来说是可以的,但是一旦你进入中断和 DMA 领域,你真的应该编写一个内核模式驱动程序。 您为以前的无内存管理操作系统所做的工作根本无法很好地移植到像 Linux 这样的通用操作系统上。
您已经考虑过 DMA 缓冲区分配问题。 现在,考虑一下来自设备的“DMA 完成”中断。 您将如何安装中断服务例程?
此外,/dev/mem 通常对非 root 用户是锁定的,因此对于一般用途来说不太实用。 当然,您可以对其进行 chmod,但是这样您就在系统中打开了一个很大的安全漏洞。
如果您试图保持操作系统之间的驱动程序代码库相似,则应考虑将其重构为单独的用户和用户。 内核模式层之间有一个类似 IOCTL 的接口。 如果将用户模式部分编写为 C 代码的通用库,那么在 Linux 和其他操作系统之间移植应该很容易。 操作系统特定的部分是内核模式代码。 (我们对我们的驱动程序使用这种方法。)
您似乎已经得出结论,是时候编写内核驱动程序了,所以您走在正确的轨道上。 我唯一可以补充的建议是从头到尾阅读这些书。
Linux 设备驱动程序
了解 Linux 内核
(请记住,这些书大约是 2005 年出版的,因此信息有点过时。)
/dev/mem is okay for simple register peeks and pokes, but once you cross into interrupts and DMA territory, you really should write a kernel-mode driver. What you did for your previous memory-management-less OSes simply doesn't graft well to an General Purpose OS like Linux.
You've already thought about the DMA buffer allocation issue. Now, think about the "DMA done" interrupt from your device. How are you going to install an Interrupt Service Routine?
Besides, /dev/mem is typically locked out for non-root users, so it's not very practical for general use. Sure, you could chmod it, but then you've opened a big security hole in the system.
If you are trying to keep the driver code base similar between the OSes, you should consider refactoring it into separate user & kernel mode layers with an IOCTL-like interface in-between. If you write the user-mode portion as a generic library of C code, it should be easy to port between Linux and other OSes. The OS-specific part is the kernel-mode code. (We use this kind of approach for our drivers.)
It seems like you have already concluded that it's time to write a kernel-driver, so you're on the right track. The only advice I can add is to read these books cover-to-cover.
Linux Device Drivers
Understanding the Linux Kernel
(Keep in mind that these books are circa-2005, so the information is a bit dated.)
到目前为止我不是这些问题的专家,所以这对你来说是一个问题而不是答案。 有什么理由不能只创建一个小的 ram 磁盘分区并将其仅用于您的应用程序吗? 这是否不能保证您访问同一块内存? 我不确定是否会出现任何 I/O 性能问题,或与此相关的额外开销。 这还假设您可以告诉内核对内存中的特定地址范围进行分区,不确定这是否可能。
我对新手问题表示歉意,但我发现你的问题很有趣,并且很好奇是否可以以这种方式使用内存磁盘。
I am by far no expert on these matters, so this will be a question to you rather than an answer. Is there any reason you can't just make a small ram disk partition and use it only for your application? Would that not give you guaranteed access to the same chunk of memory? I'm not sure of there would be any I/O performance issues, or additional overhead associated with doing that. This also assumes that you can tell the kernel to partition a specific address range in memory, not sure if that is possible.
I apologize for the newb question, but I found your question interesting, and am curious if ram disk could be used in such a way.
您看过“memmap”内核参数吗? 在 i386 和 X64_64 上,您可以使用 memmap 参数来定义内核如何处理非常特定的内存块(请参阅 Linux 内核参数文档)。 就您而言,您希望将内存标记为“保留”,以便 Linux 根本不会触及它。 然后,您可以编写代码来使用该绝对地址和大小(如果您超出该空间,您将遭遇不幸)。
Have you looked at the 'memmap' kernel parameter? On i386 and X64_64, you can use the memmap parameter to define how the kernel will hand very specific blocks of memory (see the Linux kernel parameter documentation). In your case, you'd want to mark memory as 'reserved' so that Linux doesn't touch it at all. Then you can write your code to use that absolute address and size (woe be unto you if you step outside that space).