Heng30的博客
搜索 分类 关于 订阅

如何给Linux字符驱动实现mmap?

2025-03-17

mmap是Linux系统中的一种系统调用,用于将文件或设备映射到进程的地址空间,从而实现内存映射文件或共享内存的功能。通过mmap,进程可以直接访问文件内容,而无需使用传统的read和write系统调用。下面就给字符设备驱动文件实现mmap函数,方便应用层客户端读写设备文件。

函数原形

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  • addr:指定映射区域的起始地址,通常设为 NULL,由系统自动选择。

  • length:映射区域的长度。

  • prot:映射区域的保护方式,可以是以下值的组合:

    • PROT_READ:可读
    • PROT_WRITE:可写
    • PROT_EXEC:可执行
    • PROT_NONE:不可访问
  • flags:映射区域的标志,常用的有:

    • MAP_SHARED:共享映射,对映射区域的修改会写回文件。
    • MAP_PRIVATE:私有映射,对映射区域的修改不会写回文件。
    • MAP_ANONYMOUS:匿名映射,不与文件关联,常用于分配内存。
  • fd:文件描述符,指定要映射的文件。如果是匿名映射,可以设为 -1。

  • offset:文件中的偏移量,通常设为 0。

  • 返回值

    • 成功时返回映射区域的起始地址。
    • 失败时返回MAP_FAILED(即 (void *) -1),并设置errno。

驱动代码

// simple.c

#include <linux/init.h>
#include <linux/io.h>
#include <linux/minmax.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/slab.h>

#define MAJOR_NUM 54
#define DEV_NAME "simpledev"

static void *my_data;

static ssize_t _read(struct file *file, char __user *user_buffer, size_t size,
                     loff_t *offs) {
    pr_info("simple_read\n");

    size = min(PAGE_SIZE, size);
    int remain_len = copy_to_user(user_buffer, my_data, size);
    return size - remain_len;
}

static ssize_t _write(struct file *file, const char __user *user_buffer,
                      size_t size, loff_t *offs) {
    pr_info("simple_write\n");

    size = min(PAGE_SIZE, size);
    int remain_len = copy_from_user(my_data, user_buffer, size);
    return size - remain_len;
}

static int _mmap(struct file *file, struct vm_area_struct *vma) {
    pr_info("simple_mmap\n");

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

    int status =
        remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
                        vma->vm_end - vma->vm_start, vma->vm_page_prot);

    if (status) {
        pr_err("remap_pfn_range error\n");
        return -EAGAIN;
    }

    return 0;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = _read,
    .write = _write,
    .mmap = _mmap,
};

static int __init simple_init(void) {
    pr_info("simple_init\n");

    // 保证分配的内存,在物理上是连续的
    my_data = kzalloc(PAGE_SIZE, GFP_DMA);
    if (!my_data)
        return -ENOMEM;

    int status = register_chrdev(MAJOR_NUM, DEV_NAME, &fops);
    if (status < 0) {
        pr_err("register_chrdev error\n");
        kfree(my_data);
        return status;
    }

    return 0;
}

static void __exit simple_exit(void) {
    pr_info("simple_exit\n");

    if (my_data)
        kfree(my_data);

    unregister_chrdev(MAJOR_NUM, DEV_NAME);
}

module_init(simple_init);
module_exit(simple_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple kernel module");

客户端代码

// mmap.c

#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    char buf[4096] = {0};
    strcpy(buf, argv[1]);

    int fd = open("/dev/simpledev", O_RDWR);
    assert(fd >= 0);

    void *mapped = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    assert(mapped != MAP_FAILED);
    printf("papped address: %p\n", mapped);

    memcpy(mapped, buf, 4096);
    printf("write: %s\n", buf);

    memset(buf, 0, 4096);
    memcpy(buf, mapped, 4096);

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

    munmap(mapped, 4094);
    close(fd);

    return 0;
}

编译脚本

#!/bin/sh

top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build

obj-m += simple.o

all: mmap driver

driver:
        make -C $(kernel-dir) modules M=$(top-dir)

mmap:
        gcc -g -o mmap mmap.c

clean:
        rm -f *.o *.ko *.mod *.mod.c *.order *.symvers *.dtbo
        make -C $(kernel-dir) clean m=$(top-dir)

测试

  • 编译程序:make

  • 安装驱动:insmod simple.ko

  • 创建字符设备:mknod /dev/simpledev c 54 0

  • 写入数据:echo "hello" > /dev/simpledev

  • 读取数据:head -n 1 /dev/simpledev

  • 进行mmap./mmap "good"

papped address: 0x78a108322000
write: good
read: good
  • 移除驱动:rmmod simple.ko

  • 查看输出:dmesg

[  654.359557] simple_init
[  666.944670] simple_write
[  673.003836] simple_read
[  680.630390] simple_mmap
[  688.208013] simple_exit