我最近一直在阅读Linux设备驱动程序第三版,读到了第15章:内存映射和DMA.

我也遇到了linux-kernel-labs人,特别是他们在Memory mapping lab人中的练习.

我try 做second exercise,这是为了实现一个设备驱动程序,将非连续的物理内存(例如通过vmalloc()获得)映射到用户空间.

它在书中读到,vmalloc()不会获得物理上连续的内存,因此需要分别映射每个页面.

这是我的try --

/*
 * 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;
}

问题是,当我加载驱动程序并try 用程序测试它时,它崩溃了,我得到了以下输出-

Mapped memory is at 0x7f502b436000
Bus error (core dumped)

有谁能告诉我哪里做错了吗?

附注:我知道这本书使用了vm_operations_structnopage函数,但我想效仿实验室,try 用我自己的方式来做.

推荐答案

TL;DR: use 100.

如果映射是写入时拷贝(CoW),则有a check in remap_pfn_range()可确保要重新映射的请求范围必须正好从vma->vm_startvma->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;
    }

如果一个映射的vma->vm_flags没有VM_SHARED个集合而有VM_MAYWRITE个集合(即该映射不是共享的,并且可以通过mprotect设置为可写),则该映射被认为是COW.

在您的例子中,VMA被认为是COW,并且判断失败,因为您一次映射一个页面,所以您永远不会同时匹配vma->vm_startvma->vm_end.因此,您的remap_pfn_range()将失败,返回-EINVAL,您将错过它,因为您没有判断返回值是否有错误.

你有三个 Select :

  1. 将用户空间设置为mmap,整个区域为MAP_SHARED.
  2. 将用户空间设置为mmap个单独页面,与MAP_PRIVATE个页面分开.
  3. 在将页面映射到用户空间之前,从vma->vm_flags中删除VM_MAYWRITE,以禁止在将来使页面可写(例如,使用mprotect),这将反过来使其成为非COW.

上面的数字1是IMHO在映射特殊设备时最有意义的,也是最常见的选项.


附言:注意,printf("[%x]\n", ((char*)mapped_memory)[0]);是错误的,它将读取一个char(一个字节),并将其提升为int,并带有符号扩展,因此您将获得[ffffffdd].如果你想得到[aabbccdd],你应该做((unsigned*)mapped_memory)[0]).

C++相关问答推荐

为什么双重打印与C中的float具有不同的大小时具有相同的值?

使用GOTO从多个嵌套循环C继续

为什么cudaFree不需要数据 struct 的地址?

文件权限为0666,但即使以超级用户身份也无法打开

在我的代码中,我需要在哪里编写输出函数?

调用mProtection将堆栈上的内存设置为只读,直接导致程序SIGSEGV

对于C中给定数组中的每个查询,如何正确编码以输出给定索引范围(1到N)中所有数字的总和?

为什么我会收到释放后堆使用错误?

如何在CANbus RX/TX FIFO起始地址寄存器(ATSAME 51)的特定地址初始化数组?

在创建动态泛型数组时,通过realloc对故障进行分段

在句子中转换单词的问题

等同于铁 rust 的纯C语言S未实现!()宏

如何读取文件并将内容保存在字符串中?(在C语言中,没有崩溃或核心转储错误)

C-try 将整数和 struct 数组存储到二进制文件中

无法识别C编程语言的语法,如书中所示

从不兼容的指针类型返回&&警告,但我看不出原因

Ubuntu编译:C中的文件格式无法识别错误

为什么写入关闭管道会返回成功

通过修改c中的合并排序对数组的偶数索引进行排序

将字节/字符序列写入标准输出的最简单形式