内存空间和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);