zoukankan      html  css  js  c++  java
  • 《linux设备驱动开发详解》笔记——11内存与IO访问

      内存访问与映射是linux驱动常见操作,操作硬件时离不开内存的映射,本章比较重要。

     11.1 CPU与内存、I/O

    • 目前的嵌入式处理器,都不提供专门的I/O空间,而仅存在内存空间;各种外设寄存器都直接映射到内存空间,可以通过指针直接访问
    • x86一般提供专门的I/O空间, 用特殊指令访问这些空间;
    • CPU访问虚拟地址,MMU把虚拟地址转换为物理地址。MMU的知识点还是比较多,也比较复杂,可以看看ARM的介绍性文档,例如“DEN0013*_cortex_a_series_PG_Programmer’s Guide”,对cache和MMU的介绍比较详细,可以作为入门资料,此处不展开了。

     11.2 linux内存管理

    • 共4GB空间,0~3G为用户空间,3G~4G为内核空间;
    • 用户空间和内核空间都是虚拟地址
    • 用户空间每个进程单独映射,互不干扰
    • 内核空间内核自己映射,所有进程共享,独立于

      

      linux使用buddy算法进行这些区域的管理,以2^n为单位进行管理,文件/proc/buddyinfo显示空闲的页数,依次为2^0,2^1,2^2的剩余页面个数。

    $ cat /proc/buddyinfo 
    Node 0, zone      DMA     10      9      5      5      2      2      1      2      2      2      0 
    Node 0, zone    DMA32   2521   1174    822    561    206     97     33     13      5      0      0 
                  2^0 2^1 2^2 2^3 ......对应的空闲页面数

     !!! 11.3 内存存取

      11.3.1 用户空间内存申请

    • C库实现的,malloc()与free()成对儿使用,C库的malloc一般通过brk和mmap两个系统调用来实现的;
    • malloc是按需分配,也叫写时分配,写时,会发生MMU页错误,然后进入中断进行内存分配。
    #include <malloc.h>
    
    extern void *malloc (size_t __size);
    返回值:
      NULL:出错

      11.3.2 内核空间内存申请

        11.3.2.1 kmalloc  

    实现是调用了更底层的__get_free_pages()函数,flags的前导GFP_也是这个函数的缩写。 kmalloc申请的是连续内存

    #include <linux/slab.h>

    /*
    * * kmalloc - allocate memory * @size: how many bytes of memory are required. * @flags: the type of memory to allocate. * * kmalloc is the normal method of allocating memory * for objects smaller than page size in the kernel. * * The @flags argument may be one of: * * %GFP_USER - Allocate memory on behalf of user. May sleep. * * %GFP_KERNEL - Allocate normal kernel ram. May sleep. 可能阻塞 * * %GFP_ATOMIC - Allocation will not sleep. May use emergency pools. * For example, use this inside interrupt handlers.
    * 在中断处理函数、tasklet和内核定时器等非进程上下文不能阻塞,或者进程上下文但持有自旋锁时也不能阻塞,就需要用这个flag申请内存了。 * * %GFP_HIGHUSER - Allocate pages from high memory. * * %GFP_NOIO - Do not do any I/O at all while trying to get memory. * * %GFP_NOFS - Do not make any fs calls while trying to get memory. * * %GFP_NOWAIT - Allocation will not sleep. * * %__GFP_THISNODE - Allocate node-local memory only. * * %GFP_DMA - Allocation suitable for DMA. * Should only be used for kmalloc() caches. Otherwise, use a * slab created with SLAB_DMA. * * Also it is possible to set different flags by OR'ing * in one or more of the following additional @flags: * * %__GFP_COLD - Request cache-cold pages instead of * trying to return cache-warm pages. * * %__GFP_HIGH - This allocation has high priority and may use emergency pools. * * %__GFP_NOFAIL - Indicate that this allocation is in no way allowed to fail * (think twice before using). * * %__GFP_NORETRY - If memory is not immediately available, * then give up at once. * * %__GFP_NOWARN - If allocation fails, don't issue any warnings. * * %__GFP_REPEAT - If allocation fails initially, try once more before failing. * * There are other flags available as well, but these are not intended * for general use, and so are not documented here. For a full list of * potential flags, always refer to linux/gfp.h.
    */ static __always_inline void *kmalloc(size_t size, gfp_t flags);
    void kfree(const void *);  // 注意不要忘记释放

      11.3.2.2 __get_free_pages

       linux底层分配内存的方法,buddy算法,2^n为单位。用的比较少,不写了

      11.3.2.3 vmalloc

      大量内存映射,开销较大, 实际调用GFP_KERNEL和kmalloc实现,可能引起阻塞。

    #include <linux/vmalloc.h>
    
    void * vmalloc( unsigned long size );  
    void * vfree( const void * addr );

      11.3.2.4 slab

      用的少,不说了。

     !!! 11.4 设备I/O端口和I/O内存的访问

        概念:

    • x86:设备的多个寄存器如果位于I/O空间,则被称为I/O端口
    • ARM/PPC等主流嵌入式:设备的多个寄存器如果位于内存空间,则被称为I/O内存;  

        11.4.1  I/O端口与I/O内存访问

       11.4.1.1 I/O端口

        inb/outb/inw/outw等,嵌入式基本没用。

         11.4.1.2 I/O内存

        在使用前,先要通过ioremap()函数,将物理地址转换为虚拟地址上,才能访问。转换的虚拟地址位于vmalloc区。

        ioremap()函数不进行内存分配,但需要建立新的页表(vmalloc同时进行内存分配和新建页表)。

    #include <asm/io.h>
    
    /* ioremap映射后必须使用iounmap解除映射 */ #define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE) #define ioremap_nocache(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE) #define ioremap_cache(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE_CACHED) #define ioremap_wc(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE_WC) #define iounmap __arm_iounmap void __iomem *__arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype);   void __arm_iounmap(volatile void __iomem *addr);

    /* 有一个ioremap的变体,退出或出错不需要解除映射 */
    void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,unsigned long size)

      带devm的跟dev挂钩,设备(device)被detached或者驱动(driver)卸载(unloaded)时,内存会被自释放

        映射以后,理论上可以直接通过虚拟地址的指针操作访问IO内存了,不过linux提供了一套更好的API,应使用这套API访问IO内存。

     【注】没有_relaxed后缀的接口,会进行额外的内存屏障操作

    #include <asm/io.h>
    #define
    readb_relaxed(c) ({ u8 __r = __raw_readb(c); __r; }) #define readw_relaxed(c) ({ u16 __r = le16_to_cpu((__force __le16) __raw_readw(c)); __r; }) #define readl_relaxed(c) ({ u32 __r = le32_to_cpu((__force __le32) __raw_readl(c)); __r; }) #define writeb_relaxed(v,c) __raw_writeb(v,c) #define writew_relaxed(v,c) __raw_writew((__force u16) cpu_to_le16(v),c) #define writel_relaxed(v,c) __raw_writel((__force u32) cpu_to_le32(v),c) #define readb(c) ({ u8 __v = readb_relaxed(c); __iormb(); __v; }) #define readw(c) ({ u16 __v = readw_relaxed(c); __iormb(); __v; }) #define readl(c) ({ u32 __v = readl_relaxed(c); __iormb(); __v; }) #define writeb(v,c) ({ __iowmb(); writeb_relaxed(v,c); }) #define writew(v,c) ({ __iowmb(); writew_relaxed(v,c); }) #define writel(v,c) ({ __iowmb(); writel_relaxed(v,c); })

      11.4.2  申请与释放I/O端口与I/O内存

       11.4.2.1 I/O端口申请

       11.4.2.2 I/O内存申请

      【注】如果request相关函数返回NULL,则说明申请失败。 request并进行页表映射,只是告诉内核我占用了这段物理地址,别人不能再占用了。  相当于内核提供了一种类似互斥的操作,驱动可以充分利用此特性,自然的实现共享资源的互斥访问。理论上,不进行request申请操作,直接ioremap应该也是可以的。

    #include <linux/ioport.h>
    
    #define request_region(start,n,name)        __request_region(&ioport_resource, (start), (n), (name), 0)  // IO端口
    #define release_region(start,n)            __release_region(&ioport_resource, (start), (n))
    
    #define request_mem_region(start,n,name)     __request_region(&iomem_resource, (start), (n), (name), 0)  // IO内存
    #define release_mem_region(start,n)        __release_region(&iomem_resource, (start), (n))
        
    struct resource * __request_region(struct resource *,resource_size_t start,resource_size_t n,const char *name, int flags);
    void __release_region(struct resource *parent, resource_size_t start,resource_size_t n)

      11.4.3  I/O端口与I/O内存访问流程

     

      11.4.4  将设备地址映射到用户空间

       显卡、显示器那类的驱动比较有用,后续添加。

     11.5 I/O内存静态映射

        不太重要

     11.6 DMA

        用时添加

  • 相关阅读:
    较全的ASCII码对照表
    关于.NET Framework 3.5 SP1 bootstrapper 包(安装和部署)的解决方案
    C#中DllImport用法和路径问题
    在Winform中给的button等控件添加快捷键的几种方法。
    DataGridView之为每行前面添加序号
    【软件设计过程PowerDesigner v12简介】
    死锁与活锁的区别,死锁与饥饿的区别
    性能优化之 — AS3.0对象池运用
    wc之“HelloWorld”
    php之memcache缓存技术
  • 原文地址:https://www.cnblogs.com/liuwanpeng/p/6774733.html
Copyright © 2011-2022 走看看