c语言 - mmap'ed 内存(内核空间<>用户空间)内的内容错误



我通过mmap实现内存映射。我的内核模块将一些内容写入内存,用户空间应用程序读取这些内容。简而言之,我分配0x10000内存(kcalloc在内核端,mmap在用户空间端)。然后,我使用memcpy向地址偏移0x00xf000xf000写入一些内容。在内核侧,我可以正确地读回内存。但在用户空间方面,第一个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 datan");
    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 memoryn");
    /* insufficient memory: you must handle this error! */
    return ENOMEM;
  }
  info->data = data;
printk(KERN_INFO "  > ->data:          0x%16pn",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%cn", // 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 readn",tbuff);
  memcpy(sbuff, address,80);
  printf("Initial message: %sn", sbuff);
  memcpy(sbuff, address+0xf00,80);
  printf("Initial message: %sn", sbuff);
  memcpy(sbuff, address+0xf000,80);
  printf("Initial message: %sn", sbuff);
  for(i=0; i<PAGE_SIZE; i++)
  {
    printf("%16p: %cn",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:已更正故障处理程序。这现在考虑了调用CCD_ 12的偏移量。现在它就像一个符咒。感谢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 datan");
    return 0;
  }
  page = virt_to_page((info->data)+(vmf->pgoff*PAGE_SIZE));
  get_page(page);
  vmf->page = page;
  return 0;
}

但在用户空间方面,第一个0x1000`的内容

0x1000是使用映射的页面的大小

page = virt_to_page(info->data);
get_page(page);
vmf->page = page;

结构vm_operations_struct的回调.fault针对用户访问但尚未映射的每一页(4096字节)被调用。

所以您的代码只需将data的前4096个字节(0x1000)映射到用户空间访问的每个页面。

最新更新