Linux Kernel编程 --- 内存与IO访问

本文档参考宋宝华老师的《linux设备驱动开发详解》

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

1 CPU与内存、I/O

  • 嵌入式处理器,一般不提供专门的I/O空间,而仅存在内存空间;各种外设寄存器都直接映射到内存空间,可以通过指针直接访问

  • x86一般提供专门的I/O空间, 用特殊指令访问这些空间;
  • CPU访问虚拟地址(VA),MMU把虚拟地址转换为物理地址(PA),同时提供内存访问权限保护和Cache缓存控制等硬件支持。MMU的知识点还是比较多,也比较复杂,可以看看ARM的介绍性文档,例如“DEN0013*_cortex_a_series_PG_Programmer’s Guide”,对cache和MMU的介绍比较详细,可以作为入门资料,此处不展开了。

2 linux内存管理

MMU使得进程所能访问的内存空间达到4GB。包含2各部分:用户空间和内核空间。

  • 共4GB空间,0~3G为用户空间,3G~4G为内核空间;
  • 用户空间和内核空间都是虚拟地址
  • 用户空间每个进程单独映射,互不干扰
  • 用户进程只能访问用户空间的虚拟地址,不能访问访问内核空间的虚拟地址。用户只能通过系统调用等方式才能访问到内核空间;
  • 内核空间的虚拟地址到物理地址的映射是被所有进程共享的,内核的虚拟地址空间独立于其他程序;

  

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

3 内存存取

3.1 用户空间内存申请

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

extern void *malloc (size_t __size);
返回值:
  NULL:出错

3.2 内核空间内存申请

3.2.1 kmalloc  

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

/**
 * 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.
 *  etc.
static __always_inline void *kmalloc(size_t size, gfp_t flags);
void kfree(const void *);  // 注意不要忘记释放

3.2.2 __get_free_pages

   linux底层分配内存的方法,buddy算法,2^n为单位

3.2.3 vmalloc

  大量内存映射,申请的内存一般不连续,开销较大, 实际调用GFP_KERNEL和kmalloc实现,可能引起阻塞。

#include <linux/vmalloc.h>

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

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

    概念:

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

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

4.1.1 I/O端口

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

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); })

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

4.2.1 I/O端口申请

嵌入式中很少使用

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)

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

 

 

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

   显卡、显示器那类的驱动比较有用

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值