【问题标题】:How to implement memory map feature in device drivers in linux?如何在linux的设备驱动程序中实现内存映射功能?
【发布时间】:2014-03-10 12:32:17
【问题描述】:

我正在尝试学习设备驱动程序,我从 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;
}

【问题讨论】:

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


    【解决方案1】:

    我的代码中有错误。我没有将缓冲区映射到 vma->vm_pgoff。只需在调用 rmap_pfn_range 之前添加以下代码,即可正常使用此代码

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

    【讨论】:

    • 那个计算(从字节到页)实际上是virt_to_pfn()正在做的。
    【解决方案2】:

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

    1. "vma->vm_pgoff = virt_to_phys(read_buff)>>PAGE_SHIFT" 在此示例中编程不是很好的做法,因为基本上您正在覆盖用户文件偏移量(以 PAGE 大小为单位)。如果您的驱动程序需要支持 mmap 内存偏移量,那么显然存在问题。在这种情况下,您可以直接传递 virt_to_phys(read_buff)>>PAGE_SHIFT。

    2. 不推荐使用 kmalloc 来分配内存用于 remap 目的,因为它需要页面对齐,您可以使用内核页面 API,例如 get_free_page 来分配内存,此外,它最好以 PAGE 大小为单位重新映射内存,而不是这里的 128 字节。

    【讨论】:

      猜你喜欢
      • 2016-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-01
      • 2017-12-13
      • 1970-01-01
      相关资源
      最近更新 更多