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