Linux驱动mmap内存映射 您所在的位置:网站首页 mmap内存映射 Linux驱动mmap内存映射

Linux驱动mmap内存映射

2023-08-17 18:32| 来源: 网络整理| 查看: 265

mmap在linux哪里?

什么是mmap?

上图说了,mmap是操作这些设备的一种方法,所谓操作设备,比如IO端口(点亮一个LED)、LCD控制器、磁盘控制器,实际上就是往设备的物理地址读写数据。

但是,由于应用程序不能直接操作设备硬件地址,所以操作系统提供了这样的一种机制——内存映射,把设备地址映射到进程虚拟地址,mmap就是实现内存映射的接口。

操作设备还有很多方法,如ioctl、ioremap

mmap的好处是,mmap把设备内存映射到虚拟内存,则用户操作虚拟内存相当于直接操作设备了,省去了用户空间到内核空间的复制过程,相对IO操作来说,增加了数据的吞吐量。

什么是内存映射?

既然mmap是实现内存映射的接口,那么内存映射是什么呢?看下图

每个进程都有独立的进程地址空间,通过页表和MMU,可将虚拟地址转换为物理地址,每个进程都有独立的页表数据,这可解释为什么两个不同进程相同的虚拟地址,却对应不同的物理地址。

什么是虚拟地址空间?

每个进程都有4G的虚拟地址空间,其中3G用户空间,1G内核空间(linux),每个进程共享内核空间,独立的用户空间,下图形象地表达了这点

驱动程序运行在内核空间,所以驱动程序是面向所有进程的。

用户空间切换到内核空间有两种方法:

(1)系统调用,即软中断

(2)硬件中断

虚拟地址空间里面是什么?

了解了什么是虚拟地址空间,那么虚拟地址空间里面装的是什么?看下图

虚拟空间装的大概是上面那些数据了,内存映射大概就是把设备地址映射到上图的红色段了,暂且称其为“内存映射段”,至于映射到哪个地址,是由操作系统分配的,操作系统会把进程空间划分为三个部分:

(1)未分配的,即进程还未使用的地址

(2)缓存的,缓存在ram中的页

(3)未缓存的,没有缓存在ram中

操作系统会在未分配的地址空间分配一段虚拟地址,用来和设备地址建立映射,至于怎么建立映射,后面再揭晓。

现在大概明白了“内存映射”是什么了,那么内核是怎么管理这些地址空间的呢?任何复杂的理论最终也是通过各种数据结构体现出来的,而这里这个数据结构就是进程描述符。从内核看,进程是分配系统资源(CPU、内存)的载体,为了管理进程,内核必须对每个进程所做的事情进行清楚的描述,这就是进程描述符,内核用task_struct结构体来表示进程,并且维护一个该结构体链表来管理所有进程。该结构体包含一些进程状态、调度信息等上千个成员,我们这里主要关注进程描述符里面的内存描述符(struct mm_struct mm)

内存描述符

具体的结构,请参考下图

现在已经知道了内存映射是把设备地址映射到进程空间地址(注意:并不是所有内存映射都是映射到进程地址空间的,ioremap是映射到内核虚拟空间的,mmap是映射到进程虚拟地址的),实质上是分配了一个vm_area_struct结构体加入到进程的地址空间,也就是说,把设备地址映射到这个结构体,映射过程就是驱动程序要做的事了。

内存映射的实现

以字符设备驱动为例,一般对字符设备的操作都如下框图

而内存映射的主要任务就是实现内核空间中的mmap()函数,先来了解一下字符设备驱动程序的框架

以下是mmap_driver.c的源代码

[cpp] view plain copy //所有的模块代码都包含下面两个头文件   #include    #include       #include  //定义dev_t类型   #include  //定义struct cdev结构体及相关操作   #include  //定义kmalloc接口   #include //定义virt_to_phys接口   #include //remap_pfn_range   #include       #define MAJOR_NUM 990   #define MM_SIZE 4096      static char driver_name[] = "mmap_driver1";//驱动模块名字   static int dev_major = MAJOR_NUM;   static int dev_minor = 0;   char *buf = NULL;   struct cdev *cdev = NULL;      static int device_open(struct inode *inode, struct file *file)   {       printk(KERN_ALERT"device open\n");       buf = (char *)kmalloc(MM_SIZE, GFP_KERNEL);//内核申请内存只能按页申请,申请该内存以便后面把它当作虚拟设备       return 0;   }      static int device_close(struct inode *indoe, struct file *file)   {       printk("device close\n");       if(buf)       {           kfree(buf);       }       return 0;   }      static int device_mmap(struct file *file, struct vm_area_struct *vma)   {       vma->vm_flags |= VM_IO;//表示对设备IO空间的映射       vma->vm_flags |= VM_RESERVED;//标志该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出       if(remap_pfn_range(vma,//虚拟内存区域,即设备地址将要映射到这里                          vma->vm_start,//虚拟空间的起始地址                          virt_to_phys(buf)>>PAGE_SHIFT,//与物理内存对应的页帧号,物理地址右移12位                          vma->vm_end - vma->vm_start,//映射区域大小,一般是页大小的整数倍                          vma->vm_page_prot))//保护属性,       {           return -EAGAIN;       }       return 0;   }      static struct file_operations device_fops =   {       .owner = THIS_MODULE,       .open  = device_open,       .release = device_close,       .mmap = device_mmap,   };      static int __init char_device_init( void )   {       int result;       dev_t dev;//高12位表示主设备号,低20位表示次设备号       printk(KERN_ALERT"module init2323\n");       printk("dev=%d", dev);       dev = MKDEV(dev_major, dev_minor);       cdev = cdev_alloc();//为字符设备cdev分配空间       printk(KERN_ALERT"module init\n");       if(dev_major)       {           result = register_chrdev_region(dev, 1, driver_name);//静态分配设备号           printk("result = %d\n", result);       }       else       {           result = alloc_chrdev_region(&dev, 0, 1, driver_name);//动态分配设备号           dev_major = MAJOR(dev);       }              if(result ops = &device_fops;       cdev->owner = THIS_MODULE;              result = cdev_add(cdev, dev, 1);//向内核注册字符设备       printk("dffd = %d\n", result);       return 0;   }      static void __exit char_device_exit( void )   {       printk(KERN_ALERT"module exit\n");       cdev_del(cdev);       unregister_chrdev_region(MKDEV(dev_major, dev_minor), 1);   }      module_init(char_device_init);//模块加载   module_exit(char_device_exit);//模块退出      MODULE_LICENSE("GPL");   MODULE_AUTHOR("ChenShengfa");  

下面是测试代码test_mmap.c

[cpp] view plain copy #include    #include    #include    #include    #include       int main( void )   {       int fd;       char *buffer;       char *mapBuf;       fd = open("/dev/mmap_driver", O_RDWR);//打开设备文件,内核就能获取设备文件的索引节点,填充inode结构       if(fd


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有