mmap内存映射实现
实验内容:在内核下面申请4K大小的内存(kmalloc申请的物理连续内存),在应用程序中通过mmap把内核态下申请的内存块映射到用户空间,然后在应用程序中通过read,write操作读写内核空间数据,最后读取用户态映射的地址空间,看内容是否和内核数据空间一致。
我的编译工具:
/opt/gcc-linaro-6.2.1-2016.11-x86_64_aarch64-linux-gun/bin/arrch64-linux-gun-gcc
内核实现:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_net.h>
#include <linux/netdevice.h>
#include <linux/uio_driver.h>
#include <linux/syscalls.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
/* variable declare */
static struct proc_dir_entry *mmap_dir = NULL;
static struct proc_dir_entry *mmap_file = NULL;
struct self_data {
int arry[1024];
};
int alloc_size = 0;
struct self_data *p_mem = NULL;
/* function declare */
static int mmap_open(struct inode *p_node, struct file *p_file);
static ssize_t mmap_read(struct file *p_file, char __user *p_buf, size_t size, loff_t * p_loff);
static ssize_t mmap_write(struct file *p_file, const char __user *p_buf, size_t size, loff_t *p_loff);
static int mmap_mmap(struct file *p_file, struct vm_area_struct *vma);
static int mmap_release(struct inode *p_node, struct file *p_file);
static const struct file_operations mmap_opts = {
.owner = THIS_MODULE,
.open = mmap_open,
.read = mmap_read,
.write = mmap_write,
.mmap = mmap_mmap,
.release = mmap_release,
};
int mmap_release(struct inode *p_node, struct file *p_file)
{
return 0;
}
static int mmap_open(struct inode *p_node, struct file *p_file)
{
return simple_open(p_node, p_file);
}
static ssize_t mmap_read(struct file *p_file, char __user *p_buf, size_t size, loff_t * p_loff)
{
copy_to_user(p_buf, p_mem, size);
return size;
}
static ssize_t mmap_write(struct file *p_file, const char __user *p_buf, size_t size, loff_t *p_loff)
{
copy_from_user(p_mem, p_buf, size);
return size;
}
static int mmap_mmap(struct file *p_file, struct vm_area_struct *vma)
{
unsigned long phyaddr = 0;
unsigned long size = vma->vm_end - vma->vm_start;
int ret;
if (p_mem) {
phyaddr = __pa(p_mem);
}
if (phyaddr == 0) {
pr_err("get phy addr failed!\n");
}
ret = remap_pfn_range(vma, vma->vm_start, phyaddr >> PAGE_SHIFT, size, PAGE_SHARED);
if (ret != 0) {
pr_err("remap_pfn_range failed!\n");
}
printk(KERN_EMERG"phyaddr = %p, remap size = %d\n", phyaddr, size);
return ret;
}
static int __init my_mmap_init(void)
{
mmap_dir= proc_mkdir("module_mmap", NULL);
if (mmap_dir == NULL) {
pr_err("proc_mkdie failed!\n");
}
mmap_file = proc_create("self_mmap", 0666, mmap_dir, &mmap_opts);
if (mmap_file == NULL) {
pr_err("proc_data failed!\n");
}
alloc_size = PAGE_ALIGN(sizeof(struct self_data));
p_mem = kmalloc(alloc_size, GFP_KERNEL);
if (p_mem == NULL) {
pr_err("kmalloc failed!\n");
return -1;
}
SetPageReserved(virt_to_page(p_mem));
memset(p_mem, 0, alloc_size);
printk(KERN_EMERG"alloc size = %d\n", alloc_size);
return 0;
}
static void __exit my_mmap_exit(void)
{
if (p_mem) {
kfree(p_mem);
}
if (mmap_dir) {
}
}
module_init(my_mmap_init);
module_exit(my_mmap_exit);
MODULE_LICENSE("GPL v2");
Makefile 文件:
#指定内核编译结果路径
KERNDIR ?=
ARCH ?=
PWD := $(shell pwd)
#指定交叉编译工具和GCC,LD,不然会使用本地的gcc
CROSS_COMPILE ?=
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
obj-m := module_mmap.o
all:
$(MAKE) ARCH=$(ARCH) -C $(KERNDIR) M=${PWD} modules
用户态实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
char *p_file = NULL;
char buf[1024];
int main (void)
{
int fd = 0;
int i;
void *p_viraddr = NULL;
ssize_t len;
for (i = 0; i < 1024; i++) {
buf[i] = 'r';
}
fd = open("/proc/module_mmap/self_mmap", O_RDWR);
if (fd < 0) {
printf("file open failed\n");
return 0;
}
/* 将内核空间内存,映射到用户空间,并返回虚拟地址的首地址 */
p_viraddr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p_viraddr == NULL) {
printf("mmap failed!\n");
close(fd);
return 0;
}
/* 往内核地址空间写数据---最终调用的是我们实现的mmap_write函数 */
len = write(fd, buf, 1024);
if (len < 0) {
printf("write error\n");
close(fd);
return 0;
}
sleep(5);
memset(buf, 0, 1024);
len = read(fd, buf, 1024);
if (len < 0) {
printf("read error\n");
close(fd);
return 0;
}
printf("kernal addr content: \n");
for (i = 0; i < 1024; i++) {
printf("%c", buf[i]);
}
printf ("\nuser addr content: \n");
for (i = 0; i < 1024; i++) {
printf("%c",((char *)p_viraddr)[i]);
}
close(fd);
return 0;
}
用户态文件编译:
/opt/gcc-linaro-6.2.1-2016.11-x86_64_aarch64-linux-gun/bin/arrch64-linux-gun-gcc -g mmap_main.c -o mmap_main
最后:将编译出的module_mmap.ko 和mmap_main文件,拷贝到内核编译结果目录:rootf, 然后image打包。
实验现象:
kernal addr content:
rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr …
user addr content:
rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr…
static int hah_mmap(struct file *filp, struct vm_area_struct *vma)
{
size_t size = vma->vm_end - vma->vm_start;
phys_addr_t offset = (phys_addr_t)vma->vm_pgoff << PAGE_SHIFT;
/* Does it even fit in phys_addr_t? */
if ((offset >> PAGE_SHIFT) != vma->vm_pgoff) {
return -EINVAL;
}
/* It's illegal to wrap around the end of the physical address space. */
if ((offset + (phys_addr_t)size - 1) < offset) {
return -EINVAL;
}
#ifndef SOC_SIM_MODE
if (!pfn_valid(vma->vm_pgoff)) {
vma->vm_page_prot = phys_mem_access_prot(filp, vma->vm_pgoff, size, vma->vm_page_prot);
}
#endif
/* Remap-pfn-range will mark the range VM_IO */
if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, size, vma->vm_page_prot) != 0) {
return -EAGAIN;
}
return 0;
}