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

如何在linux设备驱动程序中实现内存映射功能?,linux,linux-kernel,linux-device-driver,mmap,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> #includ

我正在尝试学习设备驱动程序,我从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;}
#包括
#包括
#包括//printk()
#包括
#包括
#包括
#包括//copy_from,to_user
#包括//重新映射\u pfn\u范围
//#包括//private\u mapping\u确定
#定义BUFF_大小128
#定义开发名称“MyDevice”
模块许可证(“GPL”);
//方法声明
int mod_open(结构索引节点*,结构文件*);
int mod_发布(结构索引节点*,结构文件*);
ssize_t mod_read(结构文件*,字符*,大小,loff_t*);
ssize_t mod_write(结构文件*,字符*,大小,loff_t*);
int mod_mmap(结构文件*,结构虚拟区域_结构*);
失效模式退出(失效);
int mod_init(无效);
//声明常用文件访问函数的结构
结构文件操作模块fops={
读:mod_read,
写:mod_write,
打开:mod_打开,
发布:mod_发布,
mmap:mod_mmap
};
静态常量结构虚拟机操作结构模块内存操作={
};
模块初始化(模块初始化);
模块出口(模块出口);
字符*read_buf;
char*write_buf;
静态int专业;
//静态int设备_Open=0;
int buffsize=0;
int mod_init(无效)
{
主要=寄存器chrdev(0,设备名称和模块fops);
如果(主要<0)
{
printk(内核警报“无法注册%s。未分配主要编号”,开发人员名称);
返回专业;
}
//为缓冲区分配内存
read_buf=kmalloc(BUFF_大小,GFP_内核);
write_buf=kmalloc(BUFF_大小,GFP_内核);
如果(!read_buf | | |!write_buf)
{
mod_exit();
return-ENOMEM;
}
//重置缓冲区
内存集(读取大小,0,缓冲大小);
memset(写入buf,0,缓冲大小);
printk(KERN_INFO“我被分配给了%d个主修号以便与之交谈”,主修);
printk(KERN_INFO“驱动程序,使用\n创建一个开发文件”);
printk(KERN_INFO“'mknod/dev/%s c%d 0.\n”,dev_NAME,Major);
printk(KERN_INFO“尝试各种小数字。尝试cat并回显到\n”);
printk(KERN_INFO“设备文件”。\n”);
printk(KERN_INFO“完成后删除设备文件和模块。\n”);
返回0;
}
void mod_出口(void)
{
注销chrdev(专业,“内存”);
if(read_buf)kfree(read_buf);
if(write_buf)kfree(write_buf);
printk(KERN_INFO“删除模块”\n);
}
int mod_mmap(结构文件*filp,结构vm_区域*vma)
{
size\u t size=vma->vm\u end-vma->vm\u start;
vma->vm_ops=&mod_mem_ops;
/*重新映射pfn范围将标记范围VM_IO*/
if(重新映射pfn_范围(vma,vma->vm_开始,vma->vm_关闭,大小,vma->vm_页面保护)){
返回-伊根;
}

printk(KERN_INFO“VMA Open.Virt_addr:%lx,phy_addr:%lx\n”,VMA->vm_start,VMA->vm_pgoff我的代码中出现了错误。我没有将缓冲区映射到VMA->vm_pgoff。只需在调用rmap_pfn_range之前添加以下代码,此代码就可以正常工作了

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

尽管找到了根本原因,但代码中仍然存在一些潜在问题

  • “vma->vm_pgoff=virt_to_phys(read_buff)>>翻页” 在本例中编程不是很好的做法,因为基本上您正在覆盖一个用户文件偏移量(以页面大小为单位)。如果您的驱动程序需要支持mmap内存偏移量,那么显然存在一个问题。在这种情况下,您只需将virt_传递给_phys(read_buff)>>PAGE_SHIFT即可

  • 不建议使用kmalloc为重新映射目的分配内存,因为它需要页面对齐,您可以只使用内核页面API(如get_free_page)来分配内存,此外,最好以页面大小为单位重新映射内存,而不是此处的128字节

  • 这种计算(从字节到页面)实际上就是
    virt\u to\u pfn()
    所做的。
    vma->vm_pgoff = virt_to_phys(read_buff)>>PAGE_SHIFT;