用户态经常有调用mmap系统调用,将文件映射到用户空间,当作一个buf来操作,当然这个操作对应于驱动就是file_operation的mmap成员。将内核空间一个物理地址映射到用户空间,准去来说是内核的物理页面,也就是一个page,我们知道linux是按页来管理物理内存的,也就是一个page,通常是4K大小,而mmap映射也是按页来映射的。这个要注意,所以在内核中如果要使用mmap,请使用page对其的地址,或者直接alloc a page用来映射。注意使用kmalloc分配出来的内存,mmap后可能得不到你想要的结果,因为kmalloc使用的是slab分配器,而不是伙伴系统,它分配的内存不是页对齐的,而你映射又映射一页,将kmalloc分配的内存所在那一页映射到用户空间,实际操作的可能不是你分配的那块内存,这个是笔者跌过的坑哈,不过跌一坑长一记性哈哈,希望大家也多跌跌坑hh《《。
废话不多说上代码:
我们使用misc驱动框架,顺便提一句,misc设备,是字符设备驱动的一个子类,比如adc啊,led等,我们不想创建class,就可以直接使用misc设备驱动框架,会省很多事。
另外说一下,file结构中的private_data用于传递我们自己创建的结构,open是设置,read/write时就可以使用了。
kernel侧(只是个demo,没有考虑多进程同步):
#include <linux/module.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#include <linux/gfp.h>
#include <linux/highmem.h>
#include <asm/page.h>
static int mapdev_open(struct inode *inode, struct file *filp)
{
/*malloc a page,and map to kernel virt addr*/
struct page *p_page = NULL;
char *virt_addr = NULL;
p_page = alloc_page(GFP_KERNEL);
if (p_page == NULL)
{
printk("mapdev_open: alloc_page failed!\n");
goto err;
}
printk("mapdev_open: alloc page success.\n");
virt_addr = (char *)kmap(p_page);
if (virt_addr == NULL)
{
printk("mapdev_open: kmap failed!\n");
goto err;
}
printk("mapdev_open: kmap success virt(%p).\n", virt_addr);
filp->private_data = virt_addr;
return 0;
err:
if (virt_addr != 0)
{
kunmap(p_page);
}
if (p_page != 0)
{
__free_page(p_page);
}
return -1;
}
static int mapdev_release(struct inode *inode, struct file *filp)
{
char* virt_addr = filp->private_data;
struct page* p_page = virt_to_page(virt_addr);
if (virt_addr != 0)
{
kunmap(p_page);
}
if (p_page != 0)
{
__free_page(p_page);
}
filp->private_data = NULL;
return 0;
}
static ssize_t mapdev_read(struct file *filp, char __user *buf, size_t count, loff_t *offp)
{
/*copy_to_user: boundry check*/
char* virt_addr = filp->private_data;
int ret = 0;
if (count > PAGE_SIZE)
{
printk("mapdev_read: count is bigger than PAGE SIZE!!\n");
return -1;
}
if (buf == NULL)
{
printk("mapdev_read: input buf is NULL!!\n");
return -1;
}
if (*offp + count > PAGE_SIZE)
{
printk("mapdev_read: read out of range off(%d), count(%u) all(%lu)", (int)*offp, count, PAGE_SIZE);
return -1;
}
ret = copy_to_user(buf, &virt_addr[*offp], count);
if (ret != 0)
{
printk("mapdev_write: copy_from_user failed(%d)!!\n", ret);
return -1;
}
return 0;
}
static ssize_t mapdev_write(struct file *filp, const char __user *buf, size_t count, loff_t *offp)
{
/*copy_from_user: boundry check*/
char* virt_addr = filp->private_data;
int ret = 0;
if (count > PAGE_SIZE)
{
printk("count is bigger than PAGE SIZE!!\n");
return -1;
}
if (buf == NULL)
{
printk(" input buf is NULL!!\n");
return -1;
}
if (*offp + count > PAGE_SIZE)
{
printk("write out of range off(%d), count(%u) all(%lu)", (int)*offp, count, PAGE_SIZE);
return -1;
}
ret = copy_from_user(&virt_addr[*offp], buf, count);
if (ret != 0)
{
printk("mapdev_write: copy_from_user failed(%d)!!\n", ret);
return -1;
}
return 0;
}
static long mapdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
return 0;
}
int mapdev_mmap (struct file *filp, struct vm_area_struct *vma)
{
int ret = 0;
char* virt_addr = filp->private_data;
vma->vm_flags |= VM_IO;
ret = remap_pfn_range(vma, vma->vm_start, virt_to_pfn(virt_addr), PAGE_SIZE, vma->vm_page_prot);
if (ret != 0)
{
printk("mapdev_mmap: remap_pfn_range failed!\n");
return -1;
}
return 0;
}
static const struct file_operations map_fops = {
.open = mapdev_open,
.release = mapdev_release,
.read = mapdev_read,
.write = mapdev_write,
.unlocked_ioctl = mapdev_ioctl,
.mmap = mapdev_mmap,
};
static struct miscdevice map_device = {
.minor = 131,
.name = "mapdev",
.fops = &map_fops,
};
static int __init mapdev_init(void)
{
return misc_register(&map_device);
};
static void __exit mapdev_exit(void)
{
misc_deregister(&map_device);
};
module_init(mapdev_init);
module_exit(mapdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhangfj");
用户侧测试程序
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/mman.h>
#define MAP_NODE "/dev/mapdev"
#define PAGE_SIZE 4096
int main()
{
char *map_buf = NULL;
char read_buf[PAGE_SIZE] = {0};
char test_buf[PAGE_SIZE] = {0};
int ret = 0;
for (int i = 0; i < PAGE_SIZE; i++)
{
test_buf[i] = PAGE_SIZE - i;
}
int fd = open(MAP_NODE, O_RDWR);
if (fd < 0)
{
printf("open node failed! ret = %d errno(%d)\n", fd, errno);
return -1;
}
/*when addr is NUOO, os to chose a addr return*/
map_buf = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (map_buf == NULL)
{
printf("map failed!\n");
goto exit;
}
memcpy(map_buf, test_buf, PAGE_SIZE);
ret = read(fd, read_buf, PAGE_SIZE);
if (ret != 0)
{
printf("read buf failed! errno(%d)\n", errno);
goto exit;
}
ret = memcmp(read_buf, test_buf, PAGE_SIZE);
if (ret != 0)
{
printf("comp failed!!!!\n");
}
else
{
printf("comp success!!\n");
}
exit:
if (map_buf != NULL)
{
munmap(map_buf, PAGE_SIZE);
}
if (fd >= 0)
{
close(fd);
}
return ret;
}