近日,一直忙于android的研究。好久没有记录点什么了
今天在分析binder的时候,看到了一个函数,mmap, 这个函数以前看过很多遍,但是没有
一次真正去深入了解它, 今天就开始深入了解一下了。
以下来自于网络:
-------------------------
Linux提供了内存映射函数mmap, 它把文件内容映射到一段内存上(准确说是虚拟内存上), 通过对这段内存的读取和修改, 实现对文件的读取和修改, 先来看一下mmap的函数声明:
- 头文件:
- <unistd.h>
- <sys/mman.h>
- 原型: void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offsize);
- 返回值: 成功则返回映射区起始地址, 失败则返回MAP_FAILED(-1).
- 参数:
- addr: 指定映射的起始地址, 通常设为NULL, 由系统指定.
- length: 将文件的多大长度映射到内存.
- prot: 映射区的保护方式, 可以是:
- PROT_EXEC: 映射区可被执行.
- PROT_READ: 映射区可被读取.
- PROT_WRITE: 映射区可被写入.
- PROT_NONE: 映射区不能存取.
- flags: 映射区的特性, 可以是:
- MAP_SHARED: 对映射区域的写入数据会复制回文件, 且允许其他映射该文件的进程共享.
- MAP_PRIVATE: 对映射区域的写入操作会产生一个映射的复制(copy-on-write), 对此区域所做的修改不会写回原文件.
- 此外还有其他几个flags不很常用, 具体查看linux C函数说明.
- fd: 由open返回的文件描述符, 代表要映射的文件.
- offset: 以文件开始处的偏移量, 必须是分页大小的整数倍, 通常为0, 表示从文件头开始映射.
下面说一下内存映射的步骤:
- 用open系统调用打开文件, 并返回描述符fd.
- 用mmap建立内存映射, 并返回映射首地址指针start.
- 对映射(文件)进行各种操作, 显示(printf), 修改(sprintf).
- 用munmap(void *start, size_t lenght)关闭内存映射.
- 用close系统调用关闭文件fd.
一切还是用程序来说明会比较直观:
// Linux Device Driver Template/Skeleton with mmap
// Kernel Module
#include <linux/module.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/vmalloc.h>
#include <linux/mman.h>
#include <linux/slab.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
#include <linux/wrapper.h>
#endif
#define SKELETON_MAJOR 240
#define SKELETON_NAME "skeleton"
#define CASE1 1
#define CASE2 2
static unsigned int counter = 0;
static char string [128];
static int data;
//#define USEASCII
#ifdef USEASCII
static char *kmalloc_area = NULL;
static char *kmalloc_ptr = NULL;
#else
static unsigned int *kmalloc_area = NULL;
static unsigned int *kmalloc_ptr = NULL;
#endif
#define LEN (64*1024)
unsigned long virt_addr;
DECLARE_WAIT_QUEUE_HEAD(skeleton_wait);
static int data_not_ready = 0;
// open function - called when the "file" /dev/skeleton is opened in userspace
static int skeleton_open (struct inode *inode, struct file *file) {
printk("skeleton_openn");
// we could do some checking on the flags supplied by "open"
// i.e. O_NONBLOCK
// -> set some flag to disable interruptible_sleep_on in skeleton_read
return 0;
}
// close function - called when the "file" /dev/skeleton is closed in userspace
static int skeleton_release (struct inode *inode, struct file *file) {
printk("skeleton_releasen");
return 0;
}
// read function called when from /dev/skeleton is read
static ssize_t skeleton_read (struct file *file, char *buf,
size_t count, loff_t *ppos) {
int len, err;
// check if we have data - if not, sleep
// wake up in interrupt_handler
while (data_not_ready) {
interruptible_sleep_on(&skeleton_wait);
}
//data_not_ready = 1;
if( counter <= 0 )
return 0;
err = copy_to_user(buf,string,counter);
if (err != 0)
return -EFAULT;
len = counter;
counter = 0;
return len;
}
// write function called when to /dev/skeleton is written
static ssize_t skeleton_write (struct file *file, const char *buf,
size_t count, loff_t *ppos) {
int err;
err = copy_from_user(string,buf,count);
if (err != 0)
return -EFAULT;
counter += count;
return count;
}
// ioctl - I/O control
static int skeleton_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg) {
int retval = 0;
switch ( cmd ) {
case CASE1:/* for writing data to arg */
if (copy_from_user(&data, (int *)arg, sizeof(int)))
return -EFAULT;
break;
case CASE2:/* for reading data from arg */
if (copy_to_user((int *)arg, &data, sizeof(int)))
return -EFAULT;
break;
default:
retval = -EINVAL;
}
return retval;
}
#ifndef VMALLOC_VMADDR
#define VMALLOC_VMADDR(x) ((unsigned long)(x))
#endif
// From: http://www.scs.ch/~frey/linux/memorymap.html
volatile void *virt_to_kseg(volatile void *address) {
pgd_t *pgd; pmd_t *pmd; pte_t *ptep, pte;
unsigned long va, ret = 0UL;
va=VMALLOC_VMADDR((unsigned long)address);
/* get the page directory. Use the kernel memory map. */
pgd = pgd_offset_k(va);
/* check whether we found an entry */
if (!pgd_none(*pgd)) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
/* get the page middle directory */
pmd = pmd_offset(pgd, va);
#else
// I'm not sure if we need this, or the line for 2.4
// above will work reliably too
// If you know, please email me :-)
pud_t *pud = pud_offset(pgd, va);
pmd = pmd_offset(pud, va);
#endif
/* check whether we found an entry */
if (!pmd_none(*pmd)) {
/* get a pointer to the page table entry */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
ptep = pte_offset(pmd, va);
#else
ptep = pte_offset_map(pmd, va);
#endif
pte = *ptep;
/* check for a valid page */
if (pte_present(pte)) {
/* get the address the page is refering to */
ret = (unsigned long)page_address(pte_page(pte));
/* add the offset within the page to the page address */
ret |= (va & (PAGE_SIZE -1));
}
}
}
return((volatile void *)ret);
}
static int skeleton_mmap(struct file * filp, struct vm_area_struct * vma) {
int ret;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
ret = remap_page_range(vma->vm_start,
virt_to_phys((void*)((unsigned long)kmalloc_area)),
vma->vm_end-vma->vm_start,
PAGE_SHARED);
// vma->vm_page_prot);
#else
ret = remap_pfn_range(vma,
vma->vm_start,
virt_to_phys((void*)((unsigned long)kmalloc_area)) >> PAGE_SHIFT,
vma->vm_end-vma->vm_start,
PAGE_SHARED);
// vma->vm_page_prot);
#endif
if(ret != 0) {
return -EAGAIN;
}
return 0;
}
// define which file operations are supported
struct file_operations skeleton_fops = {
.owner = THIS_MODULE,
.llseek = NULL,
.read = skeleton_read,
.write = skeleton_write,
.readdir = NULL,
.poll = NULL,
.ioctl = skeleton_ioctl,
.mmap = skeleton_mmap,
.open = skeleton_open,
.flush = NULL,
.release = skeleton_release,
.fsync = NULL,
.fasync = NULL,
.lock = NULL,
//.readv = NULL,
//.writev = NULL,
};
// initialize module
static int __init skeleton_init_module (void) {
int i;
#ifndef USEASCII
int tmp, tmp2;
#endif
printk("initializing modulen\n");
i = register_chrdev (SKELETON_MAJOR, SKELETON_NAME, &skeleton_fops);
if (i != 0) return - EIO;
// reserve memory with kmalloc - Allocating Memory in the Kernel
kmalloc_ptr = kmalloc(LEN + 2 * PAGE_SIZE, GFP_KERNEL);
if (!kmalloc_ptr) {
printk("kmalloc failedn\n");
return 0;
}
#ifdef USEASCII
kmalloc_area = (char *)(((unsigned long)kmalloc_ptr + PAGE_SIZE -1) & PAGE_MASK);
#else
kmalloc_area = (unsigned int *)(((unsigned long)kmalloc_ptr + PAGE_SIZE -1) & PAGE_MASK);
#endif
for (virt_addr=(unsigned long)kmalloc_area; virt_addr < (unsigned long)kmalloc_area + LEN;
virt_addr+=PAGE_SIZE) {
// reserve all pages to make them remapable
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
mem_map_reserve(virt_to_page(virt_addr));
#else
SetPageReserved(virt_to_page(virt_addr));
#endif
}
printk("kmalloc_area at 0x%p (phys 0x%lx)\n", kmalloc_area,
virt_to_phys((void *)virt_to_kseg(kmalloc_area)));
#ifdef USEASCII
// fill allocated memory with 0123456789 ascii
for( i = 48; i < 58; i++) {
kmalloc_ptr[i-48] = (char)i;
}
i = 0;
kmalloc_ptr[58-48] = (char)i;
#else
// fill allocated memory with integers
tmp = sizeof(int);
for( i = 0; i < (10 * tmp); i = i + tmp) {
kmalloc_ptr[i] = (unsigned int)i;
tmp2 = (unsigned int)kmalloc_ptr[i];
printk("kmalloc_ptr[%d]=%d\n", i, tmp2);
}
#endif
return 0;
}
// close and cleanup module
static void __exit skeleton_cleanup_module (void) {
printk("cleaning up modulen");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
for (virt_addr=(unsigned long)kmalloc_area; virt_addr < (unsigned long)kmalloc_area + LEN;
virt_addr+=PAGE_SIZE) {
// clear all pages
ClearPageReserved(virt_to_page(virt_addr));
}
#endif
kfree(kmalloc_ptr);
unregister_chrdev (SKELETON_MAJOR, SKELETON_NAME);
}
module_init(skeleton_init_module);
module_exit(skeleton_cleanup_module);
MODULE_AUTHOR("www.captain.at");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Linux Device Driver Template with MMAP");
---------------Makefile----------------------
obj-m:=skeleton.o