简单学习mmap

之前的netlink是一种应用进程与内核通信的方式, 现在再来简单学习一下mmap. 有关mmapldd3的第十五节有比较详细的介绍(话说概念比较多, 还需要慢慢消化).

个人对这种通信方式的了解是: 应用调用mmap, 对应设备的驱动实现对应的mmap方法, 对需要共享的内存进行映射(重新构建用户进程的page table), 一般可以使用remap_pfn_range函数来做. 之前的在网上看到了一个比较简单的例子. 从这个例子中学习一下~

misc设备驱动

misc设备可以看作是一种特殊的字符设备, 两者的差别请看这个问答.这里还有一篇介绍misc驱动的文章, 很实用. 相比其他设备, misc设备驱动更加好写, 因此在测试代码的时候特别好用.

内存申请

因为mmap主要和内存打交道, 主要涉及两个概念: virtual addresspage. 有关内存申请的内容在ldd3的第八章有详细介绍. 在后面自己写的小例子中会使用__get_free_pages这个函数(返回的是virtual address). 两者相互转换的函数分别是virt_to_pagepage_address.

接下来仿照上面的程序写个简单的小程序. 先是内核态的:

fun.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pagemap.h>
#include <linux/miscdevice.h>

static char *buf;
static int   buf_size = 1024;

module_param(buf_size, int, S_IRUSR | S_IWUSR | S_IRUSR | S_IWGRP);
MODULE_PARM_DESC(buf_size, "the size of the buf");

static int
fun_map(struct file *filp, struct vm_area_struct *vma)
{
    unsigned long page;

    page = virt_to_phys(buf);
    if (remap_pfn_range(vma, vma->vm_start, page >> PAGE_SHIFT, buf_size, PAGE_SHARED) < 0) {
        return -1;
    }

    snprintf(buf, buf_size, "fun world %d", buf_size);

    return 0;
}

static struct file_operations fun_ops = {
    .mmap = fun_map,
};

static struct miscdevice fun_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name  = "fun",
    .fops  = &fun_ops,
};

static int __init fun_init(void)
{
    printk(KERN_WARNING "init module; request memory %d(%d)", buf_size, get_order(buf_size));
    buf = (char *)__get_free_pages(GFP_ATOMIC, get_order(buf_size));
    if (buf == NULL) {
        printk(KERN_ERR "%s: failed to get pages for %d", __func__, get_order(buf_size));
        return -1;
    }

    /* misc device will be created */
    misc_register(&fun_dev);

    return 0;
}

static void __exit fun_exit(void)
{
    if (buf != NULL) {
        free_pages((long)buf, get_order(buf_size));
    }

    misc_deregister(&fun_dev);

    return;
}

module_init(fun_init);
module_exit(fun_exit);

MODULE_LICENSE("FUN");

主要是创建一个misc设备, 然后实现对应的map方法. 申请内存使用了__get_free_pages这个函数. 接下来是用户态程序:

user.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main(int argc, char *argv)
{
    int         fd;
    char       *buf;
    const char *path = "/dev/fun";

    fd = open(path, O_RDWR);
    if (fd < 0) {
        printf("open %s failed: %s\n", path, strerror(errno));
        return -1;
    }

    buf = mmap(0, 1024, PROT_READ, MAP_SHARED, fd, 0);
    if ((void *)buf == MAP_FAILED) {
        printf("mmap failed: %s\n", strerror(errno));
        close(fd);
        return -1;
    }

    printf("from kernel: %s\n", buf);

    close(fd);
    return 0;
}

自己还需要继续消化一下, 多看一下实际的代码.

Comments