读书笔记(6)

11.内存与I/O访问

内存管理单元MMU,提供虚拟地址和物理地址的映射,内存访问权限保护,Cache缓存控制等硬件支持。它是一个硬件。有两个重要概念:TLB块表,TTW慢表

Linux内核有三级页表PGD,PMD,PTE。一个task_struct它包含了mm_struct的首地址,每一个进程用一个mm_struct来定义它的虚拟内存用户区。用户空间和内核空间分别为3G和1G,1G的内核地址空间被划分为物理内存映射区,虚拟内存分配区,高端内存页面映射区,专用页面映射区,系统保留映射区。一般情况下,物理内存映射区为896M,当系统物理内存大于896M,超出的那部分称为高端内存,看P219比较容易理解。内核空间不会随着进程改变,拥有自己页表,内核虚拟地址独立于其他程序。

内存存取:

  用户空间动态申请内存的函数为: malloc() 释放为free() 要紧密成对——P220

  malloc函数主要靠brk()和mmap来实现的。在这我觉得是改变栈空间的大小来实现的,具体看本博客其他资料。

  内核空间内存动态申请:kmalloc(),_get_free_pages(),和vmalloc()等。前连个属于物理内存映射区域,连续的,后面的vmalloc()分配的连续的虚拟内存与物理内存之间没有简单的换算关系。kmalloc()的底层实现也是_get_free_pages(). _get_free_pages()是最底层的获取空闲内存的方法。函数用法——P221. vmalloc()与vfree(),实现它要建立新的页表,所以说,分配少量的内存是不妥的,并且不能用在原子上下文,它内部也是使用了GFP_KERNEL的kmalloc()

slab与内存池——P222

 kmem_cache_creat()建立一个slab缓存,基于原理——如果完全使用页为单位申请释放内存很浪费,因为少量字节也需要一页,所以,它的原理是使用合适的方法使得对象在前后两次被使用时分配在同一块内存或者同一类内存空间,且保留了基本的数据结构。

kmem_cache_alloc分配缓存,kmem_cache_free()释放缓存,kmem_cache_destroy收回缓存。slab将一页分为更小的单元来管理,节省内存,提升访问效率。

内存池也是一种分配大量小对象的后备缓存方法。mempool_create()创建一个内存池,mempool_alloc(),mempool_free()分配和回收对象.mempool_destroy()回收内存池。

虚拟地址和物理地址的关系:内核物理内存映射区的虚拟内存:

virt_to_phys()         PHY_OFFSET为系统DRAM内存的基地址。 phys_to_virt() 他们都只适合896M下的地段内存,高端内存不存在如此简单的换算关系。

I/O端口和I/O访问内存

I/O端口: inb() outb() inw()  outw()  inl() outl() insb() outsb() insw() outsw() insl() outsl()

I/O内存: 访问这个内存之前,使用ioremap()函数将设备所处的物理地址映射到虚拟地址。它与vmalloc()很相近,也需要建立新的页表,但是它并不进行vmalloc()中所执行的内存分配行为。建立的虚拟地址应该用iounmap()函数释放。访问IO内存除了直接通过指针访问外,还可以通过读写IO内存函数,比如ioread8(),ioread16(),ioread32()等,它们所依赖的早期版本为readb(),readw(),readl()。iowrite8(),iowrite16(),iowrite32(),它们所依赖的早期版本为writeb(),writew(),writel()。以及一些读一串内存的函数操作。同时如果是把IO端口映射到内存空间,那么先要执行函数ioport_map(),然后再执行ioport_unmap().

IO端口申请与释放:

    request_region()  release_region()

IO内存申请与释放

   request_mem_region()  release_mem_region()

IO端口和内存访问流程 ——P227

设备地址映射到用户空间:

     用户空间是不可能也不应该直接访问设备的。当使用mmap()函数时,用户控件可以直接访问设备的物理地址。它将用户空间的一段内存与设备内存关联,当用户访问用户空间的这段地址的时候,实际上会转换为对设备的访问。——函数mmap见P228。当用户调用mmap()的时候,进程的虚拟空间查找一块VMA,将这块VMA进行映射,调用文件系统或者设备驱动的mmap()操作,并且将这个VMA加入到进程的VMA链表中。它的解除机制是munmap()。mmap()用来建立页表,并且填充结构体中vm_operations_struct指针。VMA的vm_area_struct(它用来描述一个虚拟内存区域).remap_pfn_range()创建页表——P230,它其中涉及到页帧号的概念,在博客其他资料中有介绍。 pfn为页帧号,而PAGE为页描述符。pgprot_noncached()禁止了相关页的Cache和写缓冲(write buffer),pgprot_writecombine()则没有禁止写缓冲。ARM下的写缓冲器是一个非常小的FIFO存储器,位于处理器核与主存之间,其目的将处理器核和Cache从较慢的主存写操作中解脱出来。

      驱动函数中是VMA实现的nopage()函数,当访问的页不在内存的时候,即发生缺页异常时,nopage()会被内核自动调用。系统会做如下步骤:找到缺页的虚拟地址所在的VMA,如有必要,分配中间页目录表和页表。如果页表项对应的物理页面不存在,则调用这个VMA的nopage()方法,它返回物理页面的页描述符。最后将物理页面的地址填充到页表中。

IO内存静态映射

当Linux被移植到目标电路板上的时候,通常会建立外设IO内存物理地址到虚拟地址的静态映射,这个映射通过在电路板对应的map_desc结构体数组中增加新的成员来完成。——P233其中注意iotable_init()是最终建立页表映射的函数,它被赋值给map_io()函数,完成IO内存的静态映射。

 在map_desc数组中映射后的IO内存,直接在map_desc中该段的虚拟地址上加上相应的偏移即可,不再需要使用ioremap().

DMA

DMA是是一种无需CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制,使用DMA可以使系统CPU从实际的IO数据传输过程中摆脱出来。要注意的是DMA与Cache的一致性——P236.解决由于不一致性问题的最简单方法是,直接禁止DMA目标地址范围内内存的Cache功能。DMA是外设和内存交互数据的一种方式。内存中用来与外设交互数据的一块区域被称作DMA缓冲区。若设备不支持SG的情况下,DMA缓冲区必须物理上连续。比如申请DMA缓冲区函数__get_dma_pages(), dma_mem_alloc()等——P237.对大多数嵌入式处理器,DMA操作可以在整个常规范围内存区域进行,因此DMA ZONE就直接覆盖了常规内存。

一致性DMA缓冲区分配函数:dma_alloc_coherent()返回值为申请到的DMA缓冲区的虚拟地址,它的释放函数为dma_free_coherent().以下函数dma_alloc_writecombine()分配了一个写合并的DMA缓冲区,它的释放函数是dma_free_writecombine()——P239

流式DMA缓冲区——P239

申请释放DMA通道

在使用DMA之前,设备驱动程序需首先向系统申请DMA通道,申请DMA通道的函数如下:request_dma(),中间的device_id的最佳传递参数为设备结构体指针.使用完了就用free_dma()释放该通道。





 

你可能感兴趣的:(数据结构,struct,cache,IO,读书,linux内核)