跳转至

11.mmap

概念

初始只需记得会在虚拟的内存空间和实际的空间建立映射关系,我们需要做的就是怎么把实际的物理空间映射出去。你想映射某个内核 buffer,你需要得到它的物理地址,这得由你提供。

驱动程序要做的事情有 3 点:

1、确定物理地址

2、确定属性:是否使用 cache、buffer

3、建立映射关系

驱动编程

在初始化的时候分配驱动的内存空间

static char *kernel_buf;
static int bufsiz = 1024*8;

static int __init hello_drv_init(void)
{
    int ret;

    kernel_buf = kmalloc(bufsiz, GFP_KERNEL);
    strcpy(kernel_buf, "old");

    /*设备注册*/

    return 0;
}

然后编写mmap函数

static int hello_drv_mmap(struct file *file, struct vm_area_struct *vma)
{
    /* 获得物理地址 */
    unsigned long phy = virt_to_phys(kernel_buf);

    /* 设置属性: cache, buffer */
    vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);

    /* map */
    if (remap_pfn_range(vma, vma->vm_start, phy >> PAGE_SHIFT,
                vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
        printk("mmap remap_pfn_range failed\n");
        return -ENOBUFS;
    }

    return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations hello_drv_fops = {
    .owner = THIS_MODULE,   
    .open = hello_drv_open,
    .read = hello_drv_read,
    .write = hello_drv_write,
    .release = hello_drv_release,
    .mmap    = hello_drv_mmap,
};

注销的时候释放内存空间

static void __exit hello_drv_exit(void)
{
    printk("%s %s line %d\r\n", __FILE__, __FUNCTION__, __LINE__);
    unregister_chrdev(hello.major, HELLO_DEV_NAME);

    //删除类、删除设备
    device_destroy(hello.class, MKDEV(hello.major, 0));
    class_destroy(hello.class);
    kfree(kernel_buf);
}

测试代码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

/*
 * ./hello_drv_test
 */
int main(int argc, char **argv)
{
    int fd;
    char *buf;
    int len;
    char str[1024];


    /* 1. 打开文件 */
    fd = open("/dev/hello", O_RDWR);
    if (fd == -1)
    {
        printf("can not open file /dev/hello\n");
        return -1;
    }

    /* 2. mmap 
     * MAP_SHARED  : 多个APP都调用mmap映射同一块内存时, 对内存的修改大家都可以看到。
     *               就是说多个APP、驱动程序实际上访问的都是同一块内存
     * MAP_PRIVATE : 创建一个copy on write的私有映射。
     *               当APP对该内存进行修改时,其他程序是看不到这些修改的。
     *               就是当APP写内存时, 内核会先创建一个拷贝给这个APP, 
     *               这个拷贝是这个APP私有的, 其他APP、驱动无法访问。
     */
    buf =  mmap(NULL, 1024*8, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (buf == MAP_FAILED)
    {
        printf("can not mmap file /dev/hello\n");
        return -1;
    }

    printf("mmap address = 0x%x\n", buf);
    printf("buf origin data = %s\n", buf); /* old */

    /* 3. write */
    strcpy(buf, "new");

    /* 4. read & compare */
    /* 对于MAP_SHARED映射:  str = "new" 
     * 对于MAP_PRIVATE映射: str = "old" 
     */
    read(fd, str, 1024);  
    if (strcmp(buf, str) == 0)
    {
        /* 对于MAP_SHARED映射,APP写的数据驱动可见
         * APP和驱动访问的是同一个内存块
         */
        printf("compare ok!\n");
    }
    else
    {
        /* 对于MAP_PRIVATE映射,APP写数据时, 是写入原来内存块的"拷贝"
         */
        printf("compare err!\n");
        printf("str = %s!\n", str);  /* old */
        printf("buf = %s!\n", buf);  /* new */
    }

    while (1)
    {
        sleep(10);  /* cat /proc/pid/maps */
    }

    munmap(buf, 1024*8);
    close(fd);

    return 0;
}