提问人:jon doe 提问时间:11/8/2023 最后编辑:jon doe 更新时间:11/9/2023 访问量:88
将不连续的物理内存映射到用户空间
Mapping non-contiguous physical memory to userspace
问:
我最近一直在阅读 Linux 设备驱动程序第 3 版,并且已经阅读了第 15 章:内存映射和 DMA。
我还遇到过 linux-kernel-labs,特别是他们在内存映射实验室中的练习。
我尝试进行第二个练习,即实现一个设备驱动程序,该驱动程序将不连续的物理内存(例如通过 )映射到用户空间。vmalloc()
它在书中阅读,没有获得物理上连续的记忆,因此每一页都需要单独映射。vmalloc()
这是我的尝试——
/*
* PSO - Memory Mapping Lab(#11)
*
* Exercise #2: memory mapping using vmalloc'd kernel areas
*/
#include <linux/version.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/sched.h>
#include <linux/sched/mm.h>
#include <linux/mm.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
MODULE_DESCRIPTION("simple mmap driver");
MODULE_AUTHOR("PSO");
MODULE_LICENSE("Dual BSD/GPL");
#define MY_MAJOR 42
/* how many pages do we actually vmalloc */
#define NPAGES 16
/* character device basic structure */
static struct cdev mmap_cdev;
/* pointer to the vmalloc'd area, rounded up to a page boundary */
static char *vmalloc_area;
static int my_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int my_release(struct inode *inode, struct file *filp)
{
return 0;
}
static int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
int i;
long length = vma->vm_end - vma->vm_start;
unsigned long start = vma->vm_start;
char *vmalloc_area_ptr = vmalloc_area;
unsigned long pfn;
if (length > NPAGES * PAGE_SIZE)
return -EIO;
/* TODO 1: map pages individually */
for (i = 0; i < length; i += PAGE_SIZE) {
pfn = vmalloc_to_pfn(vmalloc_area_ptr + i);
remap_pfn_range(vma, vma->vm_start + i, pfn, PAGE_SIZE, vma->vm_page_prot);
}
return 0;
}
static const struct file_operations mmap_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.mmap = my_mmap,
};
static int __init my_init(void)
{
int ret = 0;
int i;
ret = register_chrdev_region(MKDEV(MY_MAJOR, 0), 1, "maps");
if (ret < 0) {
pr_err("could not register region\n");
goto out;
}
/* TODO 1: allocate NPAGES using vmalloc */
vmalloc_area = (char *) vmalloc(NPAGES * PAGE_SIZE);
if (!vmalloc_area) {
pr_err("Failed to allocate vmalloc area\n");
ret = -ENOMEM;
goto out_unreg;
}
/* TODO 1: mark pages as reserved */
for (i = 0; i < NPAGES * PAGE_SIZE; i += PAGE_SIZE) {
SetPageReserved(vmalloc_to_page((void*) vmalloc_area + i));
}
/* TODO 1: write data in each page */
for (i = 0; i < NPAGES * PAGE_SIZE; i += PAGE_SIZE) {
vmalloc_area[i + 0] = 0xdd;
vmalloc_area[i + 1] = 0xcc;
vmalloc_area[i + 2] = 0xbb;
vmalloc_area[i + 3] = 0xaa;
}
cdev_init(&mmap_cdev, &mmap_fops);
mmap_cdev.owner = THIS_MODULE;
ret = cdev_add(&mmap_cdev, MKDEV(MY_MAJOR, 0), 1);
if (ret < 0) {
pr_err("could not add device\n");
goto out_vfree;
}
return 0;
out_vfree:
vfree(vmalloc_area);
out_unreg:
unregister_chrdev_region(MKDEV(MY_MAJOR, 0), 1);
out:
return ret;
}
static void __exit my_exit(void)
{
int i;
cdev_del(&mmap_cdev);
/* TODO 1: clear reservation on pages and free mem.*/
if (vmalloc_area) {
for (i = 0; i < NPAGES * PAGE_SIZE; i += PAGE_SIZE) {
ClearPageReserved(vmalloc_to_page((void*)vmalloc_area + i));
}
vfree(vmalloc_area);
}
unregister_chrdev_region(MKDEV(MY_MAJOR, 0), 1);
}
module_init(my_init);
module_exit(my_exit);
写入每页的前 4 个字节的目的是为了在映射内存后,我可以在用户空间中测试这些值。
这是我为测试此驱动程序而编写的程序 -
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
int main(void) {
int fd, i, page_size = getpagesize();
void* mapped_memory = NULL;
fd = open("/dev/maps0", O_RDONLY);
if (fd < 0) {
printf("Failed to open /dev/maps\n");
return -1;
}
mapped_memory = mmap(NULL, page_size*16, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (mapped_memory == MAP_FAILED) {
printf("Mapping failed\n");
return -1;
}
printf("Mapped memory is at %p\n", mapped_memory);
printf("[%x]\n", ((char*)mapped_memory)[0]);
return 0;
}
问题是,当我加载驱动程序并尝试使用程序对其进行测试时,它崩溃了,我得到以下输出 -
Mapped memory is at 0x7f502b436000
Bus error (core dumped)
谁能指出我做错了什么?
P.S. 我知道这本书使用了 的功能,但我想按照实验室的方式去做。nopage
vm_operations_struct
答:
TL的;DR:使用MAP_SHARED
。
remap_pfn_range()
中有一个检查,可确保如果映射是写入时复制 (CoW),则请求重新映射的范围必须完全从 to(即它必须在物理上是连续的)。vma->vm_start
vma->vm_end
/* [...]
*
* There's a horrible special case to handle copy-on-write
* behaviour that some programs depend on. We mark the "original"
* un-COW'ed pages by matching them up with "vma->vm_pgoff".
* See vm_normal_page() for details.
*/
if (is_cow_mapping(vma->vm_flags)) {
if (addr != vma->vm_start || end != vma->vm_end)
return -EINVAL;
vma->vm_pgoff = pfn;
}
如果映射没有设置并且已经设置,则该映射被视为 CoW(即映射未共享,可以通过 写入)。vma->vm_flags
VM_SHARED
VM_MAYWRITE
mprotect
在您的情况下,VMA 被视为 CoW,并且检查失败,因为您一次映射一个页面,因此您永远不会同时匹配 和 。因此,您失败了,并且您错过了它,因为您没有检查返回值是否有错误。vma->vm_start
vma->vm_end
remap_pfn_range()
-EINVAL
您有 3 个选择:
- 使用 使用户空间成为整个区域。
mmap
MAP_SHARED
- 使用 单独创建用户空间单页。
mmap
MAP_PRIVATE
- 在将页面映射到用户空间之前删除,以禁止将来使页面可写(即 with ),这反过来又会使其成为非 CoW。
VM_MAYWRITE
vma->vm_flags
mprotect
恕我直言,上面的第 1 条是最有意义的,并且(据我所知)是映射特殊设备时最常见的选项。
PS:请注意这是错误的,它将读取一个(一个字节)并将其提升为带有符号扩展名,因此您将获得.如果你想得到 .printf("[%x]\n", ((char*)mapped_memory)[0]);
char
int
[ffffffdd]
((unsigned*)mapped_memory)[0])
[aabbccdd]
评论
mmap()