内核和用户空间共享内存的实现例程-mmap 设备驱动

2169阅读 0评论2009-12-13 ubuntuer
分类:LINUX

map_driver.h

#include <asm/atomic.h>
#include <asm/semaphore.h>
#include <linux/cdev.h>

struct mapdrv{
    struct cdev mapdev;
    atomic_t usage;
    /*Since this is read-only, we don't need sem or locks.*/
};

 

map_driver.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <asm/io.h>
#include <linux/mman.h>
#include "map_driver.h"
#define MAPLEN (PAGE_SIZE*10)

extern struct mm_struct init_mm;
/* device open */
int mapdrv_open(struct inode *inode, struct file *file);
/* device close */
int mapdrv_release(struct inode *inode, struct file *file);
/*device mmap */
int mapdrv_mmap(struct file *file, struct vm_area_struct *vma);
/* vm area open */
void map_vopen(struct vm_area_struct *vma);
/* vm area close */
void map_vclose(struct vm_area_struct *vma);
/* vm area nopage */
struct page *map_nopage(struct vm_area_struct *vma, unsigned long address,
            int *type);

static struct file_operations mapdrv_fops = {
    .owner = THIS_MODULE,
    .mmap = mapdrv_mmap,
    .open = mapdrv_open,
    .release = mapdrv_release,
};

static struct vm_operations_struct map_vm_ops = {
    .open = map_vopen,
    .close = map_vclose,
    .nopage = map_nopage,
};

static int *vmalloc_area = NULL;
static int major;        /*major number of device */

volatile void *vaddr_to_kaddr(volatile void *address)
{
    pgd_t *pgd;
    pmd_t *pmd;
    pte_t *ptep, pte;
    unsigned long va, ret = 0UL;
    va = (unsigned long)address;
    /* get the page directory. Use the kernel memory map. */
    pgd = pgd_offset_k(va);
    /* check whether we found an entry */
    if (!pgd_none(*pgd)) {
        /* get the page middle directory */
        pmd = pmd_offset(pgd, va);
        /* check whether we found an entry */
        if (!pmd_none(*pmd)) {
            /* get a pointer to the page table entry */
            ptep = pte_offset_kernel(pmd, va);
            pte = *ptep;
            /* check for a valid page */
            if (pte_present(pte)) {
                /* get the address the page is refering to */
                ret =
                 (unsigned long)page_address(pte_page(pte));
                /* add the offset within the page to the page address */
                ret |= (va & (PAGE_SIZE - 1));
            }
        }
    }
    return ((volatile void *)ret);
}

struct mapdrv* md;
MODULE_LICENSE("GPL");

static int __init mapdrv_init(void)
{

    unsigned long virt_addr;
    int result, err;
    dev_t dev = 0;
    md = kmalloc(sizeof(struct mapdrv), GFP_KERNEL);
    if (!md)
        goto fail1;
    result = alloc_chrdev_region(&dev, 0, 1, "mapdrv");
    major = MAJOR(dev);
    if (result < 0) {
        printk(KERN_WARNING "mapdrv: can't get major %d\n", major);
        goto fail2;
    }
    cdev_init(&md->mapdev, &mapdrv_fops);
    md->mapdev.owner = THIS_MODULE;
    md->mapdev.ops = &mapdrv_fops;
    err = cdev_add (&md->mapdev, dev, 1);
    if (err) {
        printk(KERN_NOTICE "Error %d adding mapdrv", err);
        goto fail3;
    }
    atomic_set(&md->usage, 0);
    /* get a memory area that is only virtual contigous. */
    vmalloc_area = vmalloc(MAPLEN);
    if (!vmalloc_area)
        goto fail4;
    for (virt_addr = (unsigned long)vmalloc_area;
     virt_addr < (unsigned long)(&(vmalloc_area[MAPLEN / sizeof(int)]));
     virt_addr += PAGE_SIZE) {

        SetPageReserved(virt_to_page
                (vaddr_to_kaddr((void *)virt_addr)));
    }
    /* set a hello message to kernel space for read by user */
    strcpy((char *)vmalloc_area, "hello world from kernel space !");

    printk("vmalloc_area at 0x%p (phys 0x%lx)\n", vmalloc_area,
     virt_to_phys((void *)vaddr_to_kaddr(vmalloc_area)));
    return 0;
fail4:
    cdev_del(&md->mapdev);    
fail3:
    unregister_chrdev_region(dev, 1);
fail2:
    kfree(md);
fail1:
    return -1;
}

static void __exit mapdrv_exit(void)
{
    unsigned long virt_addr;
    dev_t devno = MKDEV(major, 0);
    /* unreserve all pages */
    for (virt_addr = (unsigned long)vmalloc_area;
     virt_addr < (unsigned long)(&(vmalloc_area[MAPLEN / sizeof(int)]));
     virt_addr += PAGE_SIZE) {
        ClearPageReserved(virt_to_page
                 (vaddr_to_kaddr((void *)virt_addr)));
    }
    /* and free the two areas */
    if (vmalloc_area)
        vfree(vmalloc_area);
    cdev_del(&md->mapdev);
    /* unregister the device */
    unregister_chrdev_region(devno, 1);
    kfree(md);
}

/* device open method */
int mapdrv_open(struct inode *inode, struct file *file)
{
    struct mapdrv *md;
    md = container_of(inode->i_cdev, struct mapdrv, mapdev);
    atomic_inc(&md->usage);
    return 0;
}

/* device close method */
int mapdrv_release(struct inode *inode, struct file *file)
{
    struct mapdrv* md;
    md = container_of(inode->i_cdev, struct mapdrv, mapdev);
    atomic_dec(&md->usage);
    return 0;
}

int mapdrv_mmap(struct file *file, struct vm_area_struct *vma)
{
    unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;

    unsigned long size = vma->vm_end - vma->vm_start;
    if (offset & ~PAGE_MASK) {
        printk("offset not aligned: %ld\n", offset);
        return -ENXIO;
    }
    if (size > MAPLEN) {
        printk("size too big\n");
        return -ENXIO;
    }
    /* only support shared mappings. */
    if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED)) {
        printk("writeable mappings must be shared, rejecting\n");
        return -EINVAL;
    }
    /* do not want to have this area swapped out, lock it */
    vma->vm_flags |= VM_LOCKED;
    if (offset == 0) {
        vma->vm_ops = &map_vm_ops;
        /* call the open routine to increment the usage count */
        map_vopen(vma);
    } else {
        printk("offset out of range\n");
        return -ENXIO;
    }
    return 0;
}

/* open handler for vm area */
void map_vopen(struct vm_area_struct *vma)
{
    /* needed to prevent the unloading of the module while
     somebody still has memory mapped */

}

/* close handler form vm area */
void map_vclose(struct vm_area_struct *vma)
{
}

/* page fault handler */
struct page *map_nopage(struct vm_area_struct *vma, unsigned long address,
            int *type)
{
    unsigned long offset;
    unsigned long virt_addr;
    /* determine the offset within the vmalloc'd area */
    offset = address - vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT);
    /* translate the vmalloc address to kmalloc address */
    virt_addr =
     (unsigned long)vaddr_to_kaddr(&vmalloc_area[offset / sizeof(int)]);
    if (virt_addr == 0UL) {
        return ((struct page *)0UL);
    }
    /* increment the usage count of the page */
    get_page(virt_to_page(virt_addr));
    printk("map_drv: page fault for offset 0x%lx (kseg x%lx)\n", offset,
     virt_addr);
    return (virt_to_page(virt_addr));

}

module_init(mapdrv_init);
module_exit(mapdrv_exit);

 

maptest.c


 

#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#define LEN (10*4096)
int main(void)
{
    int fd, ret = 0;
    char *vadr;

    if ((fd = open("/dev/mapdrv0", O_RDWR)) < 0) {
        perror("open");
        ret = -1;
        goto fail;
    }
    vadr = mmap(0, LEN, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, fd, 0);

    if (vadr == MAP_FAILED) {
        perror("mmap");
        ret = -1;
        goto fail_close;
    }
    printf("%s\n", vadr);
    if (-1 == munmap(vadr, LEN))
        ret = -1;
fail_close:
    close(fd);
fail:
    exit(ret);
}

 

Makefile 

obj-m:= map_driver.o
modules:
    make -C /lib/modules/`uname -r`/build M=`pwd` modules

install:
    chmod +x ./install.sh
    ./install.sh
uninstall:
    rm -f /dev/mapdrv0
    /sbin/rmmod map_driver
test: maptest.c
    gcc -Wall -o maptest maptest.c
clean:
    rm -rf maptest
    rm -rf *.o *.ko *.mod.c .tmp_versions .*.cmd Module.symvers

 

install-sh

#! /bin/sh

/sbin/insmod ./map_driver.ko
major=$(awk '$2 ~ /mapdrv/ {print $1}' /proc/devices)
echo $major
mknod /dev/mapdrv0 c $major 0


上一篇:劫持函数调用
下一篇:程序颜色设置