如何在linux中的设备驱动程序中实现内存映射功能?

时间:2014-02-12 09:24:51

标签: linux linux-kernel linux-device-driver mmap

我正在尝试学习设备驱动程序,我开始使用char设备驱动程序。我实现了一个小程序,它能够从/向内核缓冲区读/写。此外,我试图实现内存映射,这是行不通的。当我试图通过映射我的内核模块来阅读一个简单的过程时,它给了我垃圾值。任何人都可以帮忙吗?

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h> //printk()
#include<linux/errno.h>
#include<linux/types.h> 
#include<linux/proc_fs.h>
#include<asm/uaccess.h> //copy_from,to_user

#include<linux/mm.h> //remap_pfn_range
//#include<linux/mman.h> //private_mapping_ok
#define BUFF_SIZE 128
#define DEV_NAME "MyDevice"
MODULE_LICENSE("GPL");

//Method declarations
int mod_open(struct inode *,struct file *);
int mod_release(struct inode *,struct file *);
ssize_t mod_read(struct file *,char *,size_t ,loff_t *);
ssize_t mod_write(struct file *,char *,size_t ,loff_t *);
int mod_mmap(struct file *, struct vm_area_struct *);


void mod_exit(void);
int mod_init(void);

//Structure that declares the usual file access functions

struct file_operations mod_fops = {
    read: mod_read,
    write: mod_write,
    open: mod_open,
    release: mod_release,
    mmap: mod_mmap
};

static const struct vm_operations_struct mod_mem_ops = {

};


module_init(mod_init);
module_exit(mod_exit);

char *read_buf;
char *write_buf;

static int Major;
//static int Device_Open = 0;
int buffsize = 0;

int mod_init(void)
{
    Major = register_chrdev(0,DEV_NAME,&mod_fops);
    if(Major < 0)
    {
        printk(KERN_ALERT "Can not register %s. No major number alloted",DEV_NAME);
        return Major;
    }
    //allocate memory to buffers
    read_buf = kmalloc(BUFF_SIZE, GFP_KERNEL);
    write_buf = kmalloc(BUFF_SIZE, GFP_KERNEL);

    if(!read_buf || !write_buf)
    {
        mod_exit();
        return -ENOMEM;
    }
    //reset buffers
    memset(read_buf,0, BUFF_SIZE);
    memset(write_buf,0, BUFF_SIZE);
    printk(KERN_INFO "I was assigned major number %d. To talk to\n", Major);
        printk(KERN_INFO "the driver, create a dev file with\n");
        printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n",DEV_NAME, Major);
        printk(KERN_INFO "Try various minor numbers. Try to cat and echo to\n");
        printk(KERN_INFO "the device file.\n");
        printk(KERN_INFO "Remove the device file and module when done.\n");
    return 0;


}

void mod_exit(void)
{
    unregister_chrdev(Major,"memory");
    if(read_buf) kfree(read_buf);
    if(write_buf) kfree(write_buf);
    printk(KERN_INFO "removing module\n");
}

int mod_mmap(struct file *filp, struct vm_area_struct *vma)
{
    size_t size = vma->vm_end - vma->vm_start;
    vma->vm_ops = &mod_mem_ops;

    /* Remap-pfn-range will mark the range VM_IO */
    if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, size, vma->vm_page_prot)) {
        return -EAGAIN;
    }
    printk(KERN_INFO "VMA Open. Virt_addr: %lx, phy_addr: %lx\n",vma->vm_start, vma->vm_pgoff<<PAGE_SHIFT);



    return 0;
}


ssize_t mod_read(struct file *filp, char *buf, size_t len, loff_t *f_pos)
{
    ssize_t bytes;
    if(buffsize < len)
        bytes = buffsize;
    else
        bytes = len;
    printk(KERN_INFO "Buffer size availabe: %d\n", buffsize);
    printk(KERN_INFO "VMA Open. read buffer initial: %lx\n",read_buf);

    if(bytes == 0)
        return 0;
    int retval = copy_to_user(buf,read_buf,bytes);
    if(retval)
    {
        printk(KERN_INFO "copy_to_user fail");
        return -EFAULT;
    }
    else
    {
        printk(KERN_INFO "copy_to_user succeeded\n");
        buffsize -= bytes;
        return bytes;
    }
}

ssize_t mod_write( struct file *filp,char *buf, size_t len, loff_t *f_pos)
{
    memset(read_buf,0,BUFF_SIZE);
    memset(write_buf,0,BUFF_SIZE);
    if(len > BUFF_SIZE)
    {
        printk(KERN_ALERT "Buffer not available. Writing only %d bytes.\n",BUFF_SIZE);
        len = BUFF_SIZE;
    }
    printk(KERN_INFO "User space msg size: %d\n",len);
    int retval = copy_from_user(read_buf,buf,len);
    printk(KERN_INFO "read %d bytes as: %s\n", retval,read_buf);

//  memcpy(write_buf,read_buf,len);
//  printk(KERN_INFO "written: %s\n", write_buf);
    buffsize = len;
    return len;
}

int mod_open(struct inode *inode, struct file *filp){return 0;}
int mod_release(struct inode *inode, struct file *filp) {return 0;}

尝试访问此设备驱动程序的程序:

#include<stdio.h>
#include<sys/fcntl.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<sys/mman.h>
int main(int argc,char *argv[])
{
    int fd,n,len;
    char *buff;
    if(argc != 3)
    {
        printf("Too few arguments.\n");
        exit(EXIT_FAILURE);
    }
    buff = (char *)malloc(128);
    if(strcmp(argv[1],"read")==0)
    {
        if(-1 == (fd = open("/dev/MyDevice",O_RDONLY)))
        {
            printf("Device open fail. Error: %s",strerror(errno));
            exit(EXIT_FAILURE);
        }
        memset(buff,0,128);
        if(-1 == (buff = mmap(0,128,PROT_READ,MAP_SHARED | MAP_NORESERVE,fd,0)))
        {
            printf("Mapping failed. Error: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
    /*  if(-1 == (n = read(fd,buff,128)))
        {
            printf("Device read fail. Error: %s",strerror(errno));
            exit(EXIT_FAILURE);
        }
        */

        printf("Read from device:\n%s\n",buff);
        close(fd);

    }
    else if(strcmp(argv[1],"write")==0)
    {
        len = strlen(argv[2]);
        if(-1 == (fd = open("/dev/MyDevice",O_WRONLY)))
        {
            printf("Device open fail. Error: %s",strerror(errno));
            exit(EXIT_FAILURE);
        }
        if(-1 == (n = write(fd,argv[2],len)))
        {
            printf("Device write fail. Error: %s",strerror(errno));
            exit(EXIT_FAILURE);
        }
        printf("Written %d bytes successfully.\n",n);
        close(fd);

    }
    else
    {
        printf("Invalid argument..\n");
        exit(EXIT_FAILURE);
    }
    return 0;
}

2 个答案:

答案 0 :(得分:0)

我的代码中出现错误。我没有将我的缓冲区映射到vma-&gt; vm_pgoff。只需在调用rmap_pfn_range之前添加以下代码,然后此代码就可以正常工作

vma->vm_pgoff = virt_to_phys(read_buff)>>PAGE_SHIFT;

答案 1 :(得分:0)

尽管找到了根本原因,但代码中仍有几个潜在的问题。

  1. “vma-&gt; vm_pgoff = virt_to_phys(read_buff)&gt;&gt; PAGE_SHIFT” 在这个例子中编程并不是一个好习惯,因为基本上你要覆盖用户文件偏移量(以PAGE大小为单位)。如果你的驱动程序需要支持mmap内存偏移,那么显然存在问题。在这种情况下,您可以将virt_to_phys(read_buff)&gt;&gt; PAGE_SHIFT传递到位。

  2. 建议不要使用kmalloc为重映射目的分配内存,因为它需要页面对齐,你可以只使用内核页面API,比如get_free_page来分配内存,更多,它最好以PAGE大小为单位重新映射内存,而不是128字节。