天天看點

小張學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;
}
           

繼續閱讀