小张学linux内核之驱动篇:2.mmap的用法

用户态经常有调用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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值