ok6410学习笔记(8.mmap地址映射之mmap驱动方法)

本节知识点:

重点函数:

1.mmap系统调用: void *mmap(void *addr,size_t len,int prot ,int flags , int fd , off_t offset) 将文件映射到进程空间的虚拟内存空间,直接对内存进行赋值,可以越过read,write对文件进行操作
参数详解:addr为映射的起始地址,一般设置成NULL,让系统分配地址,系统会自动找一个能用的虚拟内存,并创建一个vm_area_struct。
                   length为映射文件的长度
     port映射区的保护方式,PROT_EXEC映射区可被执行,PROT_READ映射区可被读,PROT_WRITE映射区可被写
   flags映射区特性,MAP_SHARED可以更改文件,MAP_PRIVATE不会更改文件 
   fd代表映射的文件
   offset代表映射文件的偏移量
                   void*  返回任意类型的指针
ok6410学习笔记(8.mmap地址映射之mmap驱动方法)_第1张图片
2.munmap系统调用:int munmap(void *start , size_t length)解除映射   start就是mmap返回的void *  ,成功返回0,失败返回1.
3.虚拟内存区域:就是在进程空间的虚拟内存中的程序段,数据段,bss段,堆,栈。可以用/proc/pid/maps来查看。
4.vm_area_struct结构,linux用vm_area_struct描述虚拟内存,unsigned long vm_start,unsigned long vm_end,unsigned long vm_flags(VM_IO,VM_RESERVED)
5.remap_pfn_range(struct vm_area_struct *vma,unsigned long addr,unsigned long pfn,unsigned long size , pgprot_t prot)
参数详解:vma  内核找到的有效的虚拟内存
   virt_addr  虚拟地址的起始地址
   pfn   要映射的物理地址的物理页的页桢号,物理地址>>PAGE_SHIFT就是物理地址除以2的12次方
   size   要映射的大小
   prot     VMA的保护属性
6.virt_to_phys(内核虚拟地址)   将虚拟地址转换成物理地址   貌似是将内核空间的虚拟地址转换成物理地址
7.remap_pfn_range将一次建立所有的页表,nopage_VMA每次建立一个页表

驱动结构:

1.在应用层调用mmap的时候,内核分配了一个可以用的虚拟地址vm_area_struct
2.在驱动层mmap(struct *file,struct vm_area_struct *vma)  建立虚拟地址和物理地址的映射,即创建两者之间的所有页表
3.本实验是利用内核的一块内存当作驱动设备的,所有还将这个内存的虚拟地址转换成了物理地址。

本节代码:

memdev.c

/**************************************************************************
 文件名:            memdev.c
 日期:              2013/06/03   
 头文件:            memdev.h
 功能:              简单字符驱动(开辟一块内存当做字符驱动进行读写) 此为驱动部分
 环境:              Redhat企业版5  内核版本2.6.18-53.el5 
 作者:              Hao 
 流程:              1.分配设备号(a.静态申请  b.动态分配)
						 		     2.创建设备文件(a.手动创建mknod(需要设备号)注“设备名和设备文件名”  b.自动创建)
								     3.设备注册(a.设备注册分配  b.设备注册初始化  c.设备注册添加)
								     4.实现file_operation中的函数
								     5.设备注销(cdev_del)
								     6.设备号注销
***************************************************************************/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include "memdev.h"
MODULE_AUTHOR("Hao");
MODULE_LICENSE("GPL");

static int mem_major=MEMDEV_MAJOR; //定义主设备号(定义一个全局变量在read write memdev_exit中都能用 )
module_param(mem_major, int, S_IRUGO);//接收模块参数主设备号
struct mem_dev *mem_devp; //定义mem设备描述结构体指针
struct cdev c_dev;         //设备注册分配 替换了struct cdev *cdev_alloc() 全局变量都可以使用

struct class *mem_class;  /*设备的类*/

/**************************************************************************
函数名:                     mem_open
函数功能:                   文件打开函数 使private_data指向 字符驱动模块
函数参数:                   inode存储文件物理信息的结构(包含设备号)   file结构
函数返回值:                 返回0为正常执行   
***************************************************************************/
int mem_open(struct inode *inode,struct file *filp)
{
	struct mem_dev *dev;
	int num = MINOR(inode->i_rdev); //获得次设备号
	if (num >= MEMDEV_NR_DEVS) 
        return -ENODEV;   //判断是否次设备号  不正确
	dev = &mem_devp[num];
	filp->private_data = dev; /*不是很了解private_data  
	宋宝华的linux设备驱动开发详解,93页写到私有数据指针private_data在设备驱动中背广泛使用,大多数指向设备驱动自定义用于描述设备的结构体。*/
	return 0; 
}
/**************************************************************************
函数名:                     mem_release
函数功能:                   文件关闭函数 
函数参数:                   inode存储文件物理信息的结构(包含设备号)   file结构
函数返回值:                 返回0为正常执行 
***************************************************************************/
int mem_release(struct inode *inode, struct file *filp)
{
    return 0;
}
/**************************************************************************
函数名:                     mem_read
函数功能:                   文件读取函数 
函数参数:                   struct file *filp(open的时候系统产生的结构,根据inode来的)
			      char __user *buf  存储读取数据的空间
			      size_t size   读取的大小  size_t应该就是typedef  unsigned int的类型
			      loff_t *ppos   当前文件指针的位置  
函数返回值:                 返回ret  正常应该是size的值 
***************************************************************************/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
	unsigned long p = *ppos; //保存下文件指针的当前位置
	unsigned int count=size; 
	int ret = 0;
    	struct mem_dev *dev = filp->private_data; //private_data是一个空指针用来接收各种数据的传递
	if (p >= MEMDEV_SIZE) //判断文件指针是否有效
        return 0;
   	if (count > MEMDEV_SIZE - p)//判断读取的大小是否比剩余的内存大小还大
        count = MEMDEV_SIZE - p;
        
        if (copy_to_user(buf, (void*)(dev->data + p), count))//把字符设备模块从内核空间拷贝到用户空间
        {
        	ret =  - EFAULT;
    	} 
    	else 
    	{
        	*ppos += count;
        	ret = count;
        	printk(KERN_EMERG "read %d bytes(s) from %ld\n", count, p);
    	}
    return ret;
}
/**************************************************************************
函数名:                     mem_write
函数功能:                   文件写函数 
函数参数:                   参数同read函数
			      注意:struct file *filp,char __user *buf和size_t size是内核从应用层api-fread中传递下来的
			      具体是怎么传递的不详  看内核代码 Read_write.c
函数返回值:                 返回size为正常执行 
***************************************************************************/
static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
    unsigned long p =  *ppos;
    unsigned int count = size;
    int ret = 0;
    struct mem_dev *dev = filp->private_data;
    if (p >= MEMDEV_SIZE)//MEMDEV_SIZE为内存最大值
        return 0;
    if (count > MEMDEV_SIZE - p)
        count = MEMDEV_SIZE - p;
    if (copy_from_user(dev->data + p, buf, count))  //从应用程序的角度看写是从用户到内核
        ret =  - EFAULT;
    else {
        *ppos += count;// 不知道这里用filp->f_pos+=count行不行  因为我觉得filp->f_pos和ppos是一个地址 
        ret = count;

        printk(KERN_EMERG "written %d bytes(s) from %ld\n", count, p);
    }
    return ret;
}
/**************************************************************************
函数名:                     mem_llseek
函数功能:                   文件位置指针定位函数 
函数参数:                   struct file *filp
			      loff_t offset偏移量
			      int whence 三种可能SEEK_SET   SEEK_CUR  SEEK_END
函数返回值:                 返回0为正常执行 
***************************************************************************/
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{ 
    loff_t newpos;  //定义中间变量
    switch(whence) {
        case 0: /* SEEK_SET */
            newpos = offset;
            break;
        case 1: /* SEEK_CUR */
            newpos = filp->f_pos + offset;
            break;
        case 2: /* SEEK_END */
            newpos = MEMDEV_SIZE -1 + offset;
            break;
        default: /* can't happen */
            return -EINVAL;
    }
    if ((newpos<0) || (newpos>MEMDEV_SIZE))  //判断newpos是否有效
        return -EINVAL;
    filp->f_pos = newpos;  //赋值 定位
    return newpos;
}

/**************************************************************************
函数名:                     mem_map
函数功能:                   映射函数   将物理地址和虚拟地址建立页表 
函数参数:                   
函数返回值:                 返回0为正常执行 
***************************************************************************/
static int mem_mmap(struct file *filp, struct vm_area_struct *vma)
{
		struct mem_dev *dev = filp->private_data;
		vma->vm_flags|=VM_IO;
		vma->vm_flags|=VM_RESERVED;
		if(remap_pfn_range(vma, vma->vm_start, virt_to_phys(dev->data)>>PAGE_SHIFT,vma->vm_end-vma->vm_start, vma->vm_page_prot))
					return -EAGAIN;
						printk(KERN_EMERG "NOW is in kernel mmap!!!\n");
		return 0;	
}


static const struct file_operations mem_fops =  //定义此字符设备的file_operations
{						//这里是对结构体整体赋值的方式
    .owner = THIS_MODULE,
    .llseek = mem_llseek,  //函数名都可以自己定义  都是函数指针
    .mmap = mem_mmap,
    .read = mem_read,
    .write = mem_write,
    .open = mem_open,
    .release = mem_release,
};

/**************************************************************************
函数名:                     memdev_init
函数功能:                   内核模块入口函数
函数参数:                   无
函数返回值:                 返回0为正常执行   返回result为设备号获取不成功
			      返回- ENOMEM为内存分配不成功
***************************************************************************/
static int memdev_init()
{
	int i;
	int result;
	dev_t dev_num=MKDEV(mem_major,0);//将主设备号和次设备号转换成32位的设备号给   静态申请设备号用的
	if(mem_major)
		result=register_chrdev_region(dev_num,2,"newmemdev");//静态申请设备号为dev_num 2个设备  设备名为“newmemdev” 
	else
	{
		result=alloc_chrdev_region(&dev_num,0,2,"newmemdev");//动态分配设备号   设备名可以在/proc/devices文件中找   与/dev路径找文件不同  因为一个设备文件  一个次设备号
		mem_major = MAJOR(dev_num);//获得主设备号
	}
	 	if (result < 0)
	 	{
      		  return result;   //判断动态分配是否成功  不成功则退出函数
      		}
      	/*设备注册 分配已经在前面完成了 为全局变量*/
      	cdev_init(&c_dev,&mem_fops);//设备注册初始化  将设备号dev_num和file_operation建立联系
      	c_dev.owner = THIS_MODULE;//*****************貌似可以屏蔽吧*********************
      	cdev_add(&c_dev,dev_num,2);//设备注册  添加   设备号为dev_num 设备数为2个
      	mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);//开辟2个字符设备模块大小的空间
      	if (!mem_devp)    /*判断是否分配内存成功*/
    	{
	        result =  - ENOMEM;
	        goto fail_malloc;
    	}
    	memset(mem_devp, 0, MEMDEV_NR_DEVS * sizeof(struct mem_dev)); //将初始化2个字符设备模块大小的空间清零
       
      	 mem_class = class_create(THIS_MODULE, "mem_class_name");//创建设备类
      	 device_create(mem_class, NULL, MKDEV(mem_major, 0), NULL, "memdev0"); //创建设备文件 文件名为memdev0
        for (i=0; i < MEMDEV_NR_DEVS; i++) //对字符设备模块结构赋值
    	{
	        mem_devp[i].size = MEMDEV_SIZE;
	        mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);//因为data是指针所以继续对其开辟4k空间了
	        memset(mem_devp[i].data, 0, MEMDEV_SIZE);
    	}
    return 0;
    	
fail_malloc:   //如果内存分配不成功直接注销设备号  
    unregister_chrdev_region(dev_num, 1);/*但是不明白的是 1.为什么只注销一个次设备的设备号  2.为什么不先注销cdev_dev*/
    return result;//虽然这里不懂 但不深究了 因为很少出现内存分配不成功的情况(在学习练习的时候)
}
/**************************************************************************
函数名:                     memdev_exit
函数功能:                   内核模块退出函数
函数参数:                   无
函数返回值:                 无
***************************************************************************/
static void memdev_exit(void)
{
	cdev_del(&c_dev);//设备注销
	kfree(mem_devp);//释放开辟的内存空间
	/*这里貌似没有释放mem_devp[i].data的指针  是不是应该kfree(mem_devp[1].data)*/
	/*这里有一个问题就是那两个内存分配 为什么要分配两次 为什么释放一次 我试过貌似第二次不分配也可以*/
	device_destroy(mem_class, MKDEV(mem_major, 0));/*注销设备*/
	class_destroy(mem_class);  /*删除类*/
	unregister_chrdev_region(MKDEV(mem_major, 0), 2);//设备号注销
}

module_init(memdev_init);
module_exit(memdev_exit);


appmem.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
int main()
{
			int fd;
			char *dev_buf;
			char buf[100];
			if(-1==(fd=open ("/dev/memdev0", O_RDWR)))
					 {
	       	 		printf("open dev0 error\n");
	        		_exit(EXIT_FAILURE);
	    		}
	    dev_buf = (char*)mmap(NULL,100, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);	
	    read(fd, buf, strlen(buf));	
	    printf("now buf is %s\n",buf);
      strcpy(dev_buf,"hello world hao!\0");
      printf("now is reading!\n");
      munmap(dev_buf,100);               
	    close(fd);
}

在app的函数里面可以先用mmap写一个值,再munmap断开映射,再映射mmap去读,如果读出数据 说明mmap测试成功。

你可能感兴趣的:(ok6410学习笔记(8.mmap地址映射之mmap驱动方法))