mmap内存中的内容错误(Kernelspace<> Userspace)

时间:2016-03-30 06:07:28

标签: c memory memory-management linux-kernel linux-device-driver

我通过mmap实现内存映射。我的内核模块将内容写入此内存,用户空间应用程序读取此内容。简而言之,我分配0x10000内存(内核端为kcalloc,用户空间端为mmap。然后我使用0x0向地址偏移0xf000xf000memcpy写了一些内容。在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;
}

1 个答案:

答案 0 :(得分:2)

  

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

0x1000是用

映射的页面大小
page = virt_to_page(info->data);
get_page(page);
vmf->page = page;

为每个页面(4096字节)调用结构.fault的回调vm_operations_struct,该页面由用户访问但尚未映射。

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