【问题标题】:Content wrong inside mmap'ed memory (Kernelspace<>Userspace)mmap 内存中的内容错误(内核空间<>用户空间)
【发布时间】:2016-03-30 06:07:27
【问题描述】:

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

【问题讨论】:

  • 请注意,您复制到内核模块内存中的后两个字符串不是以零结尾的。
  • 为什么要这样?在这种情况下,这是一个复制“字符”的memcpy 操作。我编辑帖子以显示Somewhere 出现在两侧,但Somehow 没有。
  • 问题是当你复制到sbuff然后打印sbuff时,你在用户空间应用程序中把数据当作一个以零结尾的字符串。
  • 好的。我明白你的意思了。我在字符串末尾添加了一些\0。但这并不能解决重复问题。我现在主要看for-loop,它只打印字符。

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


【解决方案1】:

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

0x1000 是映射的页面大小

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

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

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

【讨论】:

  • 哦 - 这很有道理。如何更正故障处理程序?
  • 您需要将带有一些偏移量的data 传递给virt_to_page(info-&gt;data) 调用。偏移量可以通过错误页面和vma 的起始页面的差异来确定。检查vm_area_structvm_fault 结构的定义以获取所需信息。
  • 是的。我将解决方案添加到我的帖子中。谢谢:)
  • 这是使用故障处理程序映射内存的好方法,还是我可以在vm_operations_struct.open 时做这些事情?更推荐的方式是什么?为什么?
  • 据我所知,两种方式都很好。甚至可以混合使用它们:例如,在.open() 回调中分配第一页,在.fault() 中分配其他页。这取决于您的模块的要求,哪种方式更可取。
猜你喜欢
  • 2016-03-10
  • 2018-03-27
  • 2020-12-04
  • 2016-08-14
  • 1970-01-01
  • 2012-03-02
  • 1970-01-01
  • 2012-02-03
  • 2011-11-29
相关资源
最近更新 更多