我通过mmap
实现内存映射。我的内核模块将内容写入此内存,用户空间应用程序读取此内容。简而言之,我分配0x10000
内存(内核端为kcalloc
,用户空间端为mmap
。然后我使用0x0
向地址偏移0xf00
,0xf000
和memcpy
写了一些内容。在kernelside我可以正确读回内存。但是在用户空间方面,第一个0x1000
字节的内容在整个内存中重复(16次)。但为什么呢?
她来了内核模块的代码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/mm.h>
#define DEV_MODULENAME "expdev"
#define DEV_CLASSNAME "expdevclass"
static int majorNumber;
static struct class *devClass = NULL;
static struct device *devDevice = NULL;
#ifndef VM_RESERVED
# define VM_RESERVED (VM_DONTEXPAND | VM_DONTDUMP)
#endif
struct mmap_info
{
char *data;
int reference;
};
static void
dev_vm_ops_open( struct vm_area_struct *vma )
{
struct mmap_info *info;
// counting how many applications mapping on this dataset
info = (struct mmap_info *)vma->vm_private_data;
info->reference++;
}
static void
dev_vm_ops_close( struct vm_area_struct *vma )
{
struct mmap_info *info;
info = (struct mmap_info *)vma->vm_private_data;
info->reference--;
}
static int
dev_vm_ops_fault( struct vm_area_struct *vma,
struct vm_fault *vmf)
{
struct page *page;
struct mmap_info *info;
info = (struct mmap_info *)vma->vm_private_data;
if (!info->data)
{
printk("No data\n");
return 0;
}
page = virt_to_page(info->data);
get_page(page);
vmf->page = page;
return 0;
}
static const struct vm_operations_struct dev_vm_ops =
{
.open = dev_vm_ops_open,
.close = dev_vm_ops_close,
.fault = dev_vm_ops_fault,
};
int
fops_mmap( struct file *filp,
struct vm_area_struct *vma)
{
vma->vm_ops = &dev_vm_ops;
vma->vm_flags |= VM_RESERVED;
vma->vm_private_data = filp->private_data;
dev_vm_ops_open(vma);
return 0;
}
int
fops_close( struct inode *inode,
struct file *filp)
{
struct mmap_info *info;
info = filp->private_data;
free_page((unsigned long)info->data);
kfree(info);
filp->private_data = NULL;
return 0;
}
int
fops_open( struct inode *inode,
struct file *p_file)
{
struct mmap_info *info;
char *data;
info = kmalloc(sizeof(struct mmap_info), GFP_KERNEL);
// allocating memory on the heap for the data
data = kcalloc(0x10000,sizeof(char),GFP_KERNEL);
if( data==NULL )
{
printk(KERN_ERR "insufficient memory\n");
/* insufficient memory: you must handle this error! */
return ENOMEM;
}
info->data = data;
printk(KERN_INFO " > ->data: 0x%16p\n",info->data);
memcpy(info->data, "Initial entry on mapped memory by the kernel module", 52);
memcpy((info->data)+0xf00, "Somewhere", 9);
memcpy((info->data)+0xf000, "Somehow", 7);
printk(KERN_INFO " > ->data: %c%c%c\n", // the output here is correct
*(info->data+0xf000+0),
*(info->data+0xf000+1),
*(info->data+0xf000+2));
/* assign this info struct to the file */
p_file->private_data = info;
return 0;
}
static const struct file_operations dev_fops =
{
.open = fops_open,
.release = fops_close,
.mmap = fops_mmap,
};
static int __init
_module_init(void)
{
int ret = 0;
// Try to dynamically allocate a major number for the device
majorNumber = register_chrdev(0, DEV_MODULENAME, &dev_fops);
if (majorNumber<0)
{
printk(KERN_ALERT "Failed to register a major number.\n");
return -EIO; // I/O error
}
// Register the device class
devClass = class_create(THIS_MODULE, DEV_CLASSNAME);
// Check for error and clean up if there is
if (IS_ERR(devClass))
{
printk(KERN_ALERT "Failed to register device class.\n");
ret = PTR_ERR(devClass);
goto goto_unregister_chrdev;
}
// Create and register the device
devDevice = device_create(devClass,
NULL,
MKDEV(majorNumber, 0),
NULL,
DEV_MODULENAME
);
// Clean up if there is an error
if( IS_ERR(devDevice) )
{
printk(KERN_ALERT "Failed to create the device.\n");
ret = PTR_ERR(devDevice);
goto goto_class_destroy;
}
printk(KERN_INFO "Module registered.\n");
return ret;
// Error handling - using goto
goto_class_destroy:
class_destroy(devClass);
goto_unregister_chrdev:
unregister_chrdev(majorNumber, DEV_MODULENAME);
return ret;
}
static void __exit
_module_exit(void)
{
device_destroy(devClass, MKDEV(majorNumber, 0));
class_unregister(devClass);
class_destroy(devClass);
unregister_chrdev(majorNumber, DEV_MODULENAME);
printk(KERN_INFO "Module unregistered.\n");
}
module_init(_module_init);
module_exit(_module_exit);
MODULE_LICENSE("GPL");
这里是应用程序的代码
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#define PAGE_SIZE (0x10000)
int main ( int argc, char **argv )
{
int fd;
char *address = NULL;
time_t t = time(NULL);
char *sbuff;
int i;
sbuff = (char*) calloc(PAGE_SIZE,sizeof(char));
fd = open("/dev/expdev", O_RDWR);
if(fd < 0)
{
perror("Open call failed");
return -1;
}
address = mmap( NULL,
PAGE_SIZE,
PROT_READ|PROT_WRITE,
MAP_SHARED,
fd,
0);
if (address == MAP_FAILED)
{
perror("mmap operation failed");
return -1;
}
printf("%s: first userspace read\n",tbuff);
memcpy(sbuff, address,80);
printf("Initial message: %s\n", sbuff);
memcpy(sbuff, address+0xf00,80);
printf("Initial message: %s\n", sbuff);
memcpy(sbuff, address+0xf000,80);
printf("Initial message: %s\n", sbuff);
for(i=0; i<PAGE_SIZE; i++)
{
printf("%16p: %c\n",address+i, (char)*(address+i));
}
if (munmap(address, PAGE_SIZE) == -1)
{
perror("Error un-mmapping the file");
}
close(fd);
return 0;
}
这是应用程序的输出:
0x7fe61b522000: I
0x7fe61b522001: n
0x7fe61b522002: i
0x7fe61b522003: t
0x7fe61b522004: i
0x7fe61b522005: a
0x7fe61b522006: l
...
0x7fe61b522f00: S
0x7fe61b522f01: o
0x7fe61b522f02: m
0x7fe61b522f03: e
0x7fe61b522f04: w
0x7fe61b522f05: h
0x7fe61b522f06: e
0x7fe61b522f07: r
0x7fe61b522f08: e
...
0x7fe61b523000: I
0x7fe61b523001: n
0x7fe61b523002: i
0x7fe61b523003: t
0x7fe61b523004: i
0x7fe61b523005: a
0x7fe61b523006: l
...
0x7fe61b523f00: S
0x7fe61b523f01: o
0x7fe61b523f02: m
0x7fe61b523f03: e
0x7fe61b523f04: w
0x7fe61b523f05: h
0x7fe61b523f06: e
0x7fe61b523f07: r
0x7fe61b523f08: e
...
0x7fe61b524000: I
0x7fe61b524001: n
0x7fe61b524002: i
0x7fe61b524003: t
0x7fe61b524004: i
0x7fe61b524005: a
0x7fe61b524006: l
...
在我看来,重复的是一页的大小。但这对我来说毫无意义。
编辑1:
将Somewhere
添加到输出中。注意:永远不会发生Somehow
!
编辑2:
纠正了错误处理程序。现在考虑了调用vmf
的偏移量。现在它像魅力一样运行。感谢Tsyvarev!
static int
dev_vm_ops_fault( struct vm_area_struct *vma,
struct vm_fault *vmf)
{
struct page *page;
struct mmap_info *info;
info = (struct mmap_info *)vma->vm_private_data;
if (!info->data)
{
printk("No data\n");
return 0;
}
page = virt_to_page((info->data)+(vmf->pgoff*PAGE_SIZE));
get_page(page);
vmf->page = page;
return 0;
}
答案 0 :(得分:2)
但在用户空间方面,第一个0x1000`的内容
0x1000
是用
page = virt_to_page(info->data);
get_page(page);
vmf->page = page;
为每个页面(4096字节)调用结构.fault
的回调vm_operations_struct
,该页面由用户访问但尚未映射。
因此,您的代码只会将data
的前4096字节(0x1000)映射到用户空间访问的每个页面。