linux 内存和I/O访问

2012-08-14 19:22

内存空间和I/O空间       

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

        内存地址可以直接由C指针操作,例如:(186处理器中):

 unsigned  char * p = (unsigned char *)0xF000FF00;

*p = 11;

      代码意义为:在绝对地址0xF0000 + 0xFF00 写入11。

      即便在X86处理器中,虽然提供了I/O空间,如果由我们自己设计电路板,外设仍然可以只挂载在内存空间。此时,CPU像访问一个内存单元一样访问外设I/O接口,而不需要设立专门的I/O指令。因此,内存空间是必须的,而I/O空间是可选的。

 

内存管理单元MMU

          能的处理器会提供一个MMU ,该单元辅助操作系统进行内存管理,提供虚拟地址和物理地址的映射,内存访问权限保护和Cache 缓存控制等硬件支持。

 

  linux内存管理

              nux提供了复杂的存储管理系统,使得进程能够访问的内存达到4GB。

            inux 的4GB内存空间中,0-3GB是用户空间,3-4GB是内核空间。用户只能访问用户空间的虚拟地址,用户进程通过系统调用(代表用户进程在内核态执行)等方式才能访问到内核空间。

     每个进程的用户空间都是独立的,用户进程有不同的页表,而内核空间由内核负责映射,他并不会跟着进程改变,是固定的。内核空间地址有自己对应的页表,内核的虚拟空间独立于其他程序。

    1GB的内核空间又被划分为:物理内存映射区,虚拟内存分配区(vmalloc分配区),高端页面映射区,专用页面映射区和系统保留映射区。

vmalloc虚拟内存分配区(VMALLOC_START ~ VMALLOC_END),用于vmalloc()函数。这个区域定义如下:

  #define VMALLOC_OFFSET (8*1024*1024)

  #define  VMALLOC_START (((unsigned long) high_memory +

                       vmalloc_earlyeserve + 2*VMALLOC_OFFSET-1) & ~(VMALLOC_OFFSET-1))

  #ifdef  CONFIG_HIGHMEM   //支持高端内存

 #define  VMALLOC_END  (PKMAP_BASE -2*PAGE_SIZE)

#else      //不支持高端内存

#define   VMALLOC_END    (FIXADDR_START -2*PAGE_SIZE)

#endif  

在3BG~4GB的内核空间中,从地地址到高地址依次为:物理内存映射区-----隔离带-----vmalloc 虚拟内存分配区-------隔离带-------高端内存映射区------专用页面映射区------保留区

用户空间内存的动态申请和释放

malloc() 和 free()

char *p = malloc (...);

if(p == NULL)

...;

function (p);

...;

free(p);

p=NULL;

对linux 内核而言,C库的malloc函数通常是通过brk() 和mmap() 两个系统调用实现。

 

内核空间内存的申请

  在linux内核空间申请和内存的函数主要有:kmalloc() ,__get_free_pagws() 和vmalloc()等

void *kmalloc (size_t size,int flags );

使用kfree()函数释放

 

__get_free_pages()是linux内核本质上最底层的获取空闲内存的方法,因为底层的伙伴算法是以page的2的N次幂为单位管理空闲内存的,所以最底层的内存神奇总是以页为单位的。

get_zeroed_page(unsigned int flags);

get_free_page(unsigned int flags);

__get_free_pages(unsigned int flags,unsigned int order);

使用下列的函数释放空间

void free_page(unsigned int addr);

void free_pages( unsigned long addr,unsigned long  order);

 

申请的内存位于物理内存映射区域,而且在物理上是连续的,他与真实的物理地址只有一个固定的偏移。

vmalloc()函数

void *vmalloc (unsigned long size);

void vfree (void *addr );

注:vmalloc 不能用在原子的上下文中,因为他的内部实现使用了标识为GFP_KERNEL 的kmalloc()。

 

slab 缓存

 创建slab 缓存

struct kmem_cache *kmem_cache_create(const char *name ,size_t  size,

  size_t   align, unsigned long flags,

void (*ctor)(void struct kmem_cache *,unsigned long),

void (*dtor)(void struct kmem_cache *,unsigned long));

分配slab缓存

void *kmem_cache_alloc(struct  kmem_cache *cachep ,gfp_t  flags);

释放slab 缓存

void kmem_cache_free(struct  kmem_cache *cachep ,void  *objp);

回收slab 缓存

int kmem_cache_destroy(struct kmem_cache *cachep );

 

内存池

创建内存池

mempool_t  *mempool_create(int min_nr,mempool_alloc_t *alloc_fn,

                                                   mempool_free_t  *free_fn,void  *pool_data );

分配和回收对象

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

void mempool_free(void *element,mempool_t *pool );

回收内存池

void  mempool_destroy(mempool_t  *pool );

 

虚拟地址与物理地址关系

          内核物理内存映射区的虚拟内存,使用virt_to_phys()可以实现内核虚拟地址转化为物理地址,virt_to_phys()的实现是与体系相关的,对于ARM 而言,virt_to_phys() 的定义如下:

static inline unsigned long virt_to_phys(void *x)

{

     return  __virt_to_phys((unsigned  long )(x ));

}

#define   __virt_to_phys(x)   ((x) -PAGE_OFFSET  +   PHYS_OFFSET)

与之对应的函数为:phys_to_virt(),它将物理地址转化为虚拟地址,其定义如下:

static inline unsigned long *phys_to_virt(unsigned  long  x)

{

     return  (void  *)(__phys_to_virt((unsigned  long )(x )));

}

#define   __phys_to_virt(x)   ((x)   -   PHYS_OFFSET     +     PAGE_OFFSET )

 

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

            通常会提供一组寄存器用来控制设备,读写设备和获取设备状态,即:控制寄存器,数据寄存器,状态寄存器。这些寄存器可能会位于I/O空间,可能位于内存空间。当位于I/O空间时,通常被称为I/O端口,位于内存空间时,被称为I/O内存。

      linux设备驱动中,应使用linux内核提供的函数来访问位于I/O空间的端口,这些函数包括如下几种:

1  读写字节端口(8):

unsigned inb(unsigned  port );

void outb(unsigned char  byte,unsigned port );

2   读写字端口(16):

unsigned inw(unsigned  port );

void outw(unsigned short word,unsigned port );

3  读写长字端口(32):

unsigned inl(unsigned  port );

void outl(unsigned short longword,unsigned port );

4   读写一串字节:

void  insb(unsigned  port ,void  *addr, unsigned  long  count);

void outsb(unsigned  port ,void  *addr, unsigned  long  count);

5  读写一串字

void  insw(unsigned  port ,void  *addr, unsigned  long  count);

void outsw(unsigned  port ,void  *addr, unsigned  long  count);

6  读写一长串字

void  insl(unsigned  port ,void  *addr, unsigned  long  count);

void outsl(unsigned  port ,void  *addr, unsigned  long  count);

 

I/O内存

在内核访问I/O内存之前,需首先使用ioremap函数将设备所处的物理地址映射到虚拟地址

void  * ioremap( unsigned  long  offset,unsigned long size );

使用iounmap函数释放

void   iounmap(void  *addr);

在设备的物理地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地址,但是可以使用linux下的一组函数来完成设备内存映射的虚拟地址的读写。如下:

1   读I/O内存

unsigned  int  ioread8(void  *addr);

unsigned  int  ioread16(void  *addr);

unsigned  int  ioread32(void  *addr);

2    写I/O内存

unsigned  int  iowrite8(u8   value ,void  *addr);

unsigned  int  iowrite16(u16   value ,void  *addr);

unsigned  int  iowrite32(u32   value ,void  *addr);

3   读一串I/O内存

void   ioread8_rep(void *addr,const  void *buf,unsigned  long   const);

void   ioread16_rep(void *addr,const  void *buf,unsigned  long   const);

void   ioread32_rep(void *addr,const  void *buf,unsigned  long   const);

4  写一串I/O内存

void   iowrite8_rep(void *addr,const  void *buf,unsigned  long   const);

void   iowrite16_rep(void *addr,const  void *buf,unsigned  long   const);

void   iowrite32_rep(void *addr,const  void *buf,unsigned  long   const);

5   复制I/O内存

void  memcpy_fromio(void  *dest,void *source,unsigned  int const);

void  memcpy_toio(void  *dest,void *source,unsigned  int const);

6  设置I/O内存

void  *ioport_map(unsigned  long  port,unsigned  int  const );

 

申请和释放设备I/O端口和I/O内存

linux提供了一组函数用于申请和释放I/O端口

struct  resource *request_region ( unsigned long first,unsigned  long n ,const char *name);

void  release_region ( unsigned  long start, unsigned  long n);

linux提供了一组函数用于申请和释放I/O内存

struct resource *request_mem_region( unsigned  long start ,unsigned  long len,char  * name);

void  release_mem_region ( unsigned long start , unsigned long  len );

         I/O 端口访问的一种途径是直接使用I/O端口操作函数:在设备打卡或者驱动模块被加载时申请I/O端口区域,之后使用inb() outb()等进行访问,最后,在设备关闭或者驱动模块卸载时释放I/O端口范围。

         I/O端口访问的另一种途径是I/O端口映射到内存进行访问:在设备打卡或者驱动模块加载时,申请I/O端口并使用ioport_map ()映射到内存,之后使用I/O内存的函数函数进行访问,最后,在设备关闭或者驱动模块卸载的时候,释放I/O端口并释放映射。

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

       一般情况下,用户空间是不可能直接访问设备的,但是设备驱动程序中可以实现mmap()函数,这个函数可以使得用户空间直接能够访问设备的物理地址。mmap 实现的映射过程如下:

它将用户空间的一般内存与设备内存相关联,当用户访问用户空间的这段地址范围时,实际上会转化为对设备的访问。

 mmap 函数原型如下:

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

int munmap(caddr_t , size_t len);

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值