mmap 内存中的内容错误(内核空间<>用户空间)

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

我通过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 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;
}

最佳答案

But on userspace side the content of the first 0x1000`

0x1000是页面映射的大小

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

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

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

关于mmap 内存中的内容错误(内核空间<>用户空间),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36300948/

相关文章:

c - 为什么我在这里遇到内存访问冲突?

c++ - 分配具有不同数量和大小的项目的特定大小的数组

c - 如何在C中重新排序n维矩阵的维度? (类似于排列(A,[2 :n 1]) in Matlab )

c - 如何通过 ctrl+C 或 ctrl+Z 杀死父进程及其子进程

c - 关于使用 fork() 创建的子进程的退出状态值的查询

c# - 在 C# 和 C 之间共享变量

c - 在 c 中的数组中使用时,模式被打印得很奇怪

python - 使用 Python 吃内存

java - 如何强制 Javamail 清除其邮件缓存?

swift - 为什么 self 不会在 block 中自动声明为无主?