我最近一直在阅读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_struct
的nopage
函数,但我想效仿实验室,try 用我自己的方式来做.