第11章--内存与IO访问

一、CPU与内存、I/O

1. 内存空间与I/O空间

目前,大多数嵌入式微控制器中并不提供I/O空间,而仅存在内存空间。
内存空间可以直接通过地址、指针来访问,程序及在程序运行中使用的变量和其他数据都存在于内存空间中。

2. 内存管理单元

高性能处理器一般会提供一个内存管理单元(MMU),该单元辅助操作系统进行内存管理,提供虚拟地址和物理地址的映射、内存访问权限保护和Cache缓存控制等硬件支持。
操作系统内核借助MMU可以让用户感觉到程序好像可以使用非常大的内存空间,从而使得编程人员在写程序时不用考虑计算机中物理内存的实际容量。

二、Linux内存管理

对于包含MMU的处理器而言,Linux系统提供了复杂的存储管理系统,使得进程所能访问的内存达到4GB。
在Linux系统中,进程的4GB内存空间被分为两个部分–用户空间与内核空间。用户空间的地址一般分布为0~3GB,这样剩下的3~4GB为内核空间。
用户进程通常只能访问用户空间的虚拟地址,不能访问内核空间的虚拟地址。用户进程只有通过系统调用等方式才可以访问到内核空间。
每个进程的用户空间都是完全独立的、互不相干的,用户进程各自有不同的页表。而内核空间是由内核负责映射,他并不会跟着进程改变,是固定的。

三、内存存取

1. 用户空间内存动态申请

在用户空间中动态申请内存的函数为malloc(),这个函数在各种操作系统上的使用都是一致的,malloc()申请的内存的释放函数为free()。
对于Linux而言,C库的malloc()函数一般通过brk()和mmap()两个系统调用从内核申请内存。

2. 内核空间内存动态申请

在Linux内核空间中申请内存涉及的函数主要包括kmalloc()、__get_free_pages()和vmalloc()等。
kmalloc()和__get_free_pages()申请的内存位于DMA和常规区域的映射区,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移。
vmalloc()在虚拟内存空间给出一块连续的内存区,实际上,这片连续的虚拟内存在物理内存中并不一定连续。
① kmalloc()

void *kmalloc(size_t size, int flags);

第一个参数是要分配的块的大小;第二个参数为分配标志,用于控制kmalloc()的行为。
最常用的分配标志是GFP_KERNEL,其含义是在内核空间的进程中申请内存。该标志可能会导致进程休眠。由于在中断处理函数、tasklet和内核定时器等进程上下文中不能阻塞,所以此时驱动应当使用GFP_ATOMIC标志来申请内存。
当使用GFP_ATOMIC标志申请内存时,若不存在空闲页,则不等待,直接返回。
使用kmalloc申请的内存应使用kfree()释放,这个函数的用法和用户空间的free()类似。
② __get_free_pages()
__get_free_pages()系列函数(宏)本质上是Linux内核最底层用于获取空闲内存的方法,包括以下几个函数(宏):

get_zeroed_page(unsigned int flags);  //返回一个指向新页的指针并且将该页清零
__get_free_page(unsigned int flags);  //返回一个指向新页的指针但是该页不清零
__get_free_pages(unsigned int flags, unsigned int order);  //分配多个页并返回分配内存的首地址,不清零

③ vmalloc()
vmalloc()一般只为存在于软件中(没有对应的硬件意义)的较大的顺序缓冲区分配内存,
vmalloc()远大于__get_free_pages()的开销,为了完成vmalloc(),新的页表项需要被建立。因此用vmalloc()来分配少量的内存是不妥的。
vmalloc()申请的内存应使用vfree()释放,函数原型如下:

void *vmalloc(unsigned long size);
void vfree(void *addr);

④ 内存池
内存池技术是一种非常典型的用于分配大量小对象的后备缓存技术。
在Linux内核中,与内存池相关的操作包括如下几种。
(1)创建内存池

mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn, mempool_free_t *free_fn, void *pool_data);

(2)分配和回收对象

void *mempool_alloc(mempool_t *pool, int gfp_mask);
void mempool_free(void *element, mempool_t *pool);

(3)回收内存池

void mempool_destroy(mempool_t *pool);

四、设备I/O内存的访问

设备通常会提供一组寄存器来控制设备、读写设备和获取设备状态,即控制寄存器、数据寄存器和状态寄存器。这些寄存器一般位于内存空间中,对应的内存空间被称为I/O内存。

  1. LinuxI/O内存访问接口

    void *ioremap(unsigned long offset, unsigned long size);  //将设备所处的物理地址映射到虚拟地址上,返回该地址
    void iounmap(void *addr);  //释放通过ioremap()获得的虚拟地址
    
  2. I/O内存空间申请与释放

    struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);  //申请一片I/O内存空间
    void release_mem_region(unsigned long start, unsigned long len);  //释放申请的I/O内存空间
    
  3. 将设备地址映射到用户空间
    一般情况下,用户空间是不可能也不应该直接访问设备的,但是,设备驱动程序中可实现mmap()函数,这个函数可使得用户空间能直接访问设备的物理地址。实际上,mmap()实现了这样的一个映射过程:它将用户空间的一段内存与设备内存关联,当用户访问用户空间的这段地址范围时,实际上会转化为对设备的访问。这种能力对于显示适配器一类的设备非常有意义,如果用户空间可直接通过内存映射访问显存的话,屏幕帧的各点像素将不再需要一个从用户空间到内核空间的复制的过程。从file_operations文件操作结构体可以看出,驱动中mmap()函数的原型如下:

    int (*mmap)(struct file *, struct vm_area_struct *);
    

五、DMA

DMA是一种无需CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率。
DMA方式的数据传输由DMA控制器(DMAC)控制,在传输期间,CPU可以并发的执行其他任务。当DMA结束后,DMAC通过中断通知CPU数据传输已经结束,然后由CPU执行相应的中断服务程序进行处理。

  1. DMA与Cache一致性
    如果DMA的目的地址与Cache所缓存的内存地址访问有重叠,经过DMA操作,与Cache缓存对应的内存中的数据已经被修改,而CPU本身并不知道,
    他仍然认为Cache中的数据就是内存中的数据,那在以后访问Cache映射的内存时,它仍然使用陈旧的Cache数据。这样就会发生Cache与内存之间数据“不一致性”的错误。

  2. Linux下的DMA编程
    ① 申请一片DMA缓存区

    void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);  //可以保证缓冲区的Cache一致性
    

    ② 释放DMA缓存区

    void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t *handle);
    
  3. dmaengine标准API
    Linux内核目前推荐使用dmaengine的驱动架构来编写DMA控制器的驱动,同时外设的驱动使用标准的dmaengine API进行DMA的准备、发起和完成时的回调工作。和中断一样,在使用DMA之前,设备驱动程序需首先向dmaengine系统申请DMA通道,申请DMA通道的函数如下:

    struct dma_chan *dma_request_slave_channel(struct device *dev, const char *name);
    struct dma_chan *__dma_request_channel(const dma_cap_mask_t *mask, dma_filter_fn fn, void *fn_param);
    

    使用完DMA通道后,应该利用如下函数释放该通道:

    void dma_release_channel(struct dma_chan *chan);
    

    通过dmaengine_prep_slave_single()准备好一些DMA描述符,并填充其完成回调为xxx_dma_fini_callback(),之后通过dmaengine_submit()把这个描述符插入队列,在通过dma_async_issue_pending()发起这次DMA动作。DMA完成后,xxx_dma_fini_callback()函数会被dmaengine驱动自动调用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值