Linux 中的直接内存访问
- 2024-11-13 08:36:00
- admin 原创
- 18
问题描述:
我正在尝试直接访问嵌入式 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 中也运行良好,仅供参考)。我的想法是通过直接寻址物理内存在 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-模块.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-用户.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;
}
解决方案 1:
我认为您可以找到很多有关 kmalloc + mmap 部分的文档。但是,我不确定您是否可以以连续的方式 kmalloc 如此多的内存,并且始终将其放在同一位置。当然,如果一切都始终相同,那么您可能会得到一个恒定的地址。但是,每次更改内核代码时,您都会得到一个不同的地址,因此我不会采用 kmalloc 解决方案。
我认为您应该在启动时保留一些内存,即保留一些物理内存,以便内核不会触及这些内存。然后您可以 ioremap 该内存,这将为您提供内核虚拟地址,然后您可以 mmap 它并编写一个不错的设备驱动程序。
这让我们回到PDF 格式的Linux 设备驱动程序。请查看第 15 章,第 443 页描述了这种技术
编辑:ioremap 和 mmap。我认为分两步进行调试可能会更容易:首先正确获取 ioremap,然后使用字符设备操作(即读/写)对其进行测试。一旦您知道可以使用读/写安全地访问整个 ioremapped 内存,然后尝试 mmap 整个 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 状态。
解决方案 2:
抱歉,我回答得不太好,我注意到您已经编辑了这个问题。请注意,当您编辑问题时,SO 不会通知我们。我在这里给出一个通用答案,当您更新问题时,请发表评论,然后我会编辑我的答案。
是的,你需要编写一个模块。归根结底是使用kmalloc()
(在内核空间中分配一个区域)还是vmalloc()
(在用户空间中分配一个区域)。
暴露前者很容易,而暴露后者可能会很麻烦,因为您需要描述这种接口。您指出 1.5 MB 是您实际需要保留的粗略估计,这是铁定的吗?也就是说,您是否愿意从内核空间中获取这些空间?您能充分处理来自用户空间(甚至磁盘休眠)的 ENOMEM 或 EIO 吗?换句话说,这个区域里有什么?
另外,并发性会成为问题吗?如果是,你会使用 futex 吗?如果任何一个问题的答案是“是”(尤其是后者),那么你很可能不得不咬紧牙关,继续使用vmalloc()
(否则可能会让内核从内部腐烂)。另外,如果你甚至在考虑ioctl()
字符设备的接口(尤其是对于某些临时锁定想法),你真的想使用vmalloc()
。
另外,你读过这篇文章吗?另外,我们甚至没有触及 grsec / selinux 会如何看待这个问题(如果正在使用的话)。
解决方案 3:
/dev/mem 可以用于简单的寄存器查看和查看,但一旦涉及中断和 DMA 领域,您就真的应该编写一个内核模式驱动程序。您之前为无内存管理的操作系统所做的工作根本无法很好地移植到 Linux 等通用操作系统上。
您已经考虑过 DMA 缓冲区分配问题。现在,考虑一下来自设备的“DMA 完成”中断。您将如何安装中断服务例程?
此外,/dev/mem 通常对非 root 用户是锁定的,因此对于一般用途来说不太实用。当然,您可以对其进行 chmod,但这样一来,您就打开了系统中一个很大的安全漏洞。
如果您试图让操作系统之间的驱动程序代码库保持相似,则应考虑将其重构为单独的用户模式层和内核模式层,并在两者之间使用类似 IOCTL 的接口。如果您将用户模式部分编写为通用 C 代码库,则应该很容易在 Linux 和其他操作系统之间移植。操作系统特定的部分是内核模式代码。(我们对驱动程序使用这种方法。)
看起来你已经得出结论,是时候编写内核驱动程序了,所以你走在正确的道路上。我唯一能补充的建议是把这些书从头到尾读一遍。
Linux 设备驱动程序
了解 Linux 内核
(请记住这些书大约是 2005 年的,因此信息有点过时。)
解决方案 4:
我远非这些问题的专家,所以这对您来说是一个问题,而不是答案。有什么理由不能只创建一个小的 RAM 磁盘分区并将其仅用于您的应用程序?这是否不能保证您能够访问同一块内存?我不确定这样做是否会产生任何 I/O 性能问题或额外的开销。这还假设您可以告诉内核对内存中的特定地址范围进行分区,不确定这是否可行。
对于这个新手问题,我深表歉意,但我发现你的问题很有趣,并且很好奇 RAM 磁盘是否可以以这种方式使用。
解决方案 5:
您看过“memmap”内核参数了吗?在 i386 和 X64_64 上,您可以使用 memmap 参数来定义内核如何处理非常具体的内存块(请参阅Linux 内核参数文档)。就您而言,您需要将内存标记为“保留”,这样 Linux 就不会触碰它。然后,您可以编写代码来使用该绝对地址和大小(如果您超出该空间,那就糟了)。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件