Memory mapping with character device

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello,

I am trying to implement a simple character device that
implements the mmap() method (struct file_operations).
I can't get it work, the source code is appended.

This is the scenario:
The kernel module, that implements the device, allocates
a page from the high memory zone upon insmod. The page
is freed upon rmmod. The mmap() method implementation
uses the nopage() approach (as described in LDD3 Ch15).

I understand it in a way, that the mmap() method just
sets up the nopage() method to be called when the
process first accesses the mmapped memory. The nopage()
method is supposed to provide the actual page to satisfy
the request. In my scenario, I want to use the page allocated
at insmod time. That's what the code (hopefully) does.

The problem is that the user space process segfaults,
when it tries to access the mmapped memory. It also
seems to me that the nopage() method is not called.
I am obviously doing something wrong but I have
a hard time figuring it out.

This is the source code of the character device implementation:

// Linux Kernel 2.6.12.2
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/page.h>
#include <linux/gfp.h>
#include <linux/string.h>
#include <linux/cdev.h>
#include <linux/mm.h>

#define FIRST_MINOR 0
#define MINOR_COUNT 1
#define DEVICE_NAME "mmap"

static dev_t device_number;
static struct cdev device;

static unsigned long page;

static int open_fop(struct inode *, struct file *);
static int release_fop(struct inode *, struct file *);
static int mmap_fop(struct file *, struct vm_area_struct *);

static struct file_operations fops=
{
   owner:THIS_MODULE,
   open:open_fop,
   release:release_fop,
   mmap:mmap_fop,
};

static void open_vm_op(struct vm_area_struct *area);
static void close_vm_op(struct vm_area_struct *area);
struct page* nopage_vm_op(struct vm_area_struct *area,
       unsigned long address, int *type);

static struct vm_operations_struct vm_ops=
{
   open:open_vm_op,
   close:close_vm_op,
   nopage:nopage_vm_op,
};

static int __init init(void)
{
   int ret;


   if(!(page=__get_free_page(GFP_HIGHUSER))){
       ret=-ENOMEM;
       goto out;
   }
   memset((char*)page, 0, PAGE_SIZE);

   if((ret=alloc_chrdev_region(&device_number, FIRST_MINOR,
           MINOR_COUNT, DEVICE_NAME))<0)
       goto err1;

   cdev_init(&device, &fops);
   if((ret=cdev_add(&device, device_number, MINOR_COUNT))<0)
       goto err2;

out:
   return ret;


err2:
   unregister_chrdev_region(device_number, MINOR_COUNT);
err1:
   free_page(page);
   goto out;
}


static void __exit exit(void)
{
   cdev_del(&device);
   unregister_chrdev_region(device_number, MINOR_COUNT);
   free_page(page);
}


module_init(init);
module_exit(exit);
MODULE_LICENSE("GPL");


static int open_fop(struct inode *inode, struct file *file)
{
   printk(KERN_NOTICE "open()\n");
   try_module_get(THIS_MODULE);
   return 0;
}

static int release_fop(struct inode *inode, struct file *file)
{
   printk(KERN_NOTICE "release()\n");
   module_put(THIS_MODULE);
   return 0;
}

static int mmap_fop(struct file *file, struct vm_area_struct *vma)
{
   int size=vma->vm_end-vma->vm_start;

   printk(KERN_NOTICE "mmap()\n");

   if(size>PAGE_SIZE){
       printk(KERN_ERR "bad size (%d)\n", size);
       return -EINVAL;
   }

   if(vma->vm_pgoff!=0){
       printk(KERN_ERR "bad offset (%lu)\n", vma->vm_pgoff);
       return -EINVAL;
   }

   vma->vm_flags=VM_RESERVED;
   vma->vm_ops=&vm_ops;

   return 0;
}

static void open_vm_op(struct vm_area_struct *area)
{
   printk(KERN_NOTICE "open_vm()\n");
}

static void close_vm_op(struct vm_area_struct *area)
{
   printk(KERN_NOTICE "close_vm()\n");
}

// Does not seem to be called when the user space process
// tries to access the mmap-ed memory.
struct page* nopage_vm_op(struct vm_area_struct *area,
       unsigned long address, int *type)
{
   struct page *ret;

   printk("nopage_vm(%lu)\n", address);

   ret=pfn_to_page(__pa(page)>>PAGE_SHIFT);
   get_page(ret);

   if(type)
       *type=VM_FAULT_MINOR;
   return ret;
}



This is the source code of the program that maps the memory:
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>


#define START       0
#define LENGTH      4096 // man 2 getpagesize
#define PROTECTION  ( PROT_READ | PROT_WRITE )
#define FLAGS       MAP_SHARED
#define OFFSET      0
#define COUNT       128


int main(int argc, char *argv[])
{
   // mknod mmap c 253 0
   char *device="mmap";
   volatile int *shp;
   int sh;
   void *mem;
   int fd;
   int it;


   if(argc==2)
       device=argv[1];

   fd=open(device, O_RDWR);
   assert(fd!=-1);

   mem=mmap(START, LENGTH, PROTECTION, FLAGS, fd, OFFSET);
   if(mem==(void*)-1){
       perror("mmap");
       assert(0);
   }

   printf("pid(%d)\n", getpid());

//    sleep(10);

   it=COUNT;
   shp=(int*)mem;
   while(it--){
       // Segmentation fault here.
       sh=*shp;
       printf("read(%d), write(%d)\n", sh, ++sh);
       *shp=sh;
       sleep(1);
   }

   munmap(mem, LENGTH);
   return 0;
}


The output of the program:
pid(12270)
Segmentation fault

The output of the dmesg:
open()
mmap()
close_vm()
release()



BlackHole

--
To unsubscribe from this list: send an email with
"unsubscribe kernelnewbies" to ecartis@xxxxxxxxxxxx
Please read the FAQ at http://kernelnewbies.org/FAQ


[Index of Archives]     [Newbies FAQ]     [Linux Kernel Mentors]     [Linux Kernel Development]     [IETF Annouce]     [Git]     [Networking]     [Security]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux SCSI]     [Linux ACPI]
  Powered by Linux