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:映射区域的保护方式,可以是以下值的组合:
flags:映射区域的标志,常用的有:
fd:文件描述符,指定要映射的文件。如果是匿名映射,可以设为 -1。
offset:文件中的偏移量,通常设为 0。
返回值
// 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