CPU与I/O内存空间

一.内存空间与I/O空间
     在x86处理器中I/O空间通过指令in,out来访问内存空间,端口号标示了外设的寄存器地址.
Inter语法的in,out指令格式:
     IN 累加器 {端口号|DX}
     OUT {端口号|DX},累加器
     大多数嵌入式微控制器如ARM、PowerPC等中并不提供I/O空间,而仅存在内存空间。内存空间可以直接通过地址、指针来访问,程序和程序运行中使用的变量和其他数据都存在于内存

空间中。
内存地址可以直接由C语言指针操作
如:unsigned char *p = (unsigned char *)0xF000FF00;  *p=11;
     即便是在X86处理器中,虽然提供了I/O 空间,如果由我们自己设计电路板,外设仍然可以只挂接在内存空间。此时,CPU可以像访问一个内存单元那样访问外设I/O端口,而不需要设

立专门的I/O 指令。因此,内存空间是必须的,而I/O 空间是可选的。

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

统内核借助MMU,可以让用户感觉到好像程序可以使用非常大的内存空间,从而使得编程人员在写程序时不用考虑计算机中的物理内存的实际容量。
几个基本概念:
(1)TLB:Translation Lookaside Buffer,即转换旁路缓存,TLB 是MMU 的核心部件,它缓存少量的虚拟地址与物理地址的转换关系,是转换表的
Cache,因此也经常被称为“快表”。
(2)TTW:Translation Table walk,即转换表漫游,当TLB中没有缓冲对应的地址转换关系时,需要通过对内存中转换表(大多数处理器的转换表为多级页表)的访问来获得虚拟地址和

物理地址的对应关系。TTW成功后,结果应写入TLB。
MMU具有虚拟地址和物理地址转换、内存访问权限保护等功能,这将使得Linux操作系统能单独为系统的每个用户进程分配独立的内存空间并保证用户空间不能访问内核空间的地址,为操作

系统的虚拟内存管理模块提供硬件基础。

三.Linxu内存管理
在Linux系统中,进程的4GB内存空间被分为两个部分——用户空间与内核空间。用户空间地址一般分布为0~3GB,剩下的3~4GB为内核空间,用户进程通常情况下只能访问用户空间的虚拟

地址,不能访问内核空间的虚拟地址。用户进程只能通过系统 调用来访问内核空间;
每个进程的用户空间都是完全独立,互不相干的,用户进程各自有不同的页表,而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。内核空间地址有自己对应的页表,内核

的虚拟空间独立于其他程序;
Linux中1GB的内核地址空间又被划分为物理内存映射区,虚拟内存分配去,高端页面映射区,专用页面映射区和系统保留映射区,在3~4GB 之间的内核空间中,从低地址到高地址依次为

:物理内存映射区—隔离带—vmalloc 虚拟内存分配器—隔离带—高端内存映射区—专用页面映射区—保留区。

四.内存存取
(1)用户空间内存动态申请
在用户空间动态申请内存的函数为malloc(),,malloc()申请的内存的释放函数为free()。malloc()的内存一定要被free(),否则会造成内存泄露。
char * function(void)
{
 char *p;
 p = (char *)malloc(…);
 if(p==NULL)
 ...;
 ... /* 一系列针对p的操作*/
 free(p);
}
(2)在Linux内核空间申请内存涉及的函数主要包括kmalloc(),__get_free_pages()和vmalloc()等。kmalloc()和__get_free_pages()申请的内存位于物理映射区在物理上是连续的,

vmalloc()在虚拟内存空间给出一块连续的内存区
【1】kmalloc()
void *kmalloc(size_t size,int flags); 给kmalloc()的第一个参数是要分配的块的大小,第二个参数为分配标志,用于控制kmalloc()的行为。最常用的分配标志是GFP_KERNEL其含义是

在内核空间的进程中申请内存。使用kmalloc()申请的应使用kfree()释放内存。
【2】__get_free_pages()
get_zeroed_page(unsigned int flags);该函数返回一个指向新页的指针并且将该页清零。
_ _get_free_page(unsigned int flags);该宏返回一个指向新页的指针但是该页不清零.
_ _get_free_pages(unsigned int flags, unsigned int order);该函数可分配多个页并返回分配内存的首地址,分配的页数为2order,分配的页也不清零。order 允许的最大值是10(即

1024 页)或者11(即2048 页),依赖于具体的硬件平台。
struct page * alloc_pages(int gfp_mask, unsigned long order);_ _get_free_pages ()和get_zeroed_page ()的实现中调用了alloc_pages()函数,alloc_pages()既可以在内核空间

分配,也可以在用户空间分配.
内存释放函数:void free_page(unsigned long addr);
void free_pages(unsigned long addr, unsigned long order);
_ _get_free_pages 等函数在使用时,其申请标志的值与kmalloc()完全一样,各标志
的含义也与kmalloc()完全一致,最常用的是GFP_KERNEL和GFP_ATOMIC。
【3】vmalloc()
vmalloc()一般为软件中的较大的顺序缓冲区分配内存
vmalloc()申请的内存应使用vfree()释放
void *vmalloc(unsigned long size);
void vfree(void * addr);

五.设备I/O端口和I/O内存访问
设备通常会提供一组寄存器来用于控制设备、读写设备和获取设备状态,即控制寄存器、数据寄存器和状态寄存器。这些寄存器可能位于I/O 空间,也可能位于内存空间。当位于I/O 空间

时,通常被称为I/O 端口,位于内存空间时,对应的内存空间被称为I/O 内存
(1)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 longword, unsigned port);
【4】读写一串字节。
void outsb(unsigned port, void *addr, unsigned long count);
【5】insb()从端口port 开始读count 个字节端口,并将读取结果写入addr 指向的
内存;outsb()将addr指向的内存的count个字节连续地写入port开始的端口。
【6】读写一串字。
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
【7】 读写一串长字。
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
(2)I/O内存
在内核中访问I/O 内存之前,需首先使用ioremap()函数将设备所处的物理地址映射到虚拟地址。ioremap()的原型如下:
void *ioremap(unsigned long offset, unsigned long size);
通过ioremap()获得的虚拟地址应该被iounmap()函数释放,其原型如下:
void iounmap(void * addr);
可以使用Linux内核的如下一组函数来完成设备内存映射的虚拟地址的读写
【读I/O内存】
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
【写I/O内存】
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
【读一串I/O 内存】
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);
【写一串I/O 内存】
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);
【复制 I/O 内存】
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);
【设置I/O 内存】
void memset_io(void *addr, u8 value, unsigned int count);
六.申请与释放设备I/O端口和I/O内存
1.I/O端口申请
Linux 内核提供了一组函数用于申请和释放I/O 端口。
struct resource *request_region(unsigned long first, unsigned long n,
const char *name);
这个函数向内核申请n 个端口,这些端口从first开始,name参数为设备的名称。
如果分配成功返回值是非NULL,如果返回NULL,则意味着申请端口失败。
当用request_region()申请的I/O 端口使用完成后,应当使用release_region()函数将
它们归还给系统,这个函数的原型如下:
void release_region(unsigned long start, unsigned long n);
2.I/O内存申请
同样,Linux 内核也提供了一组函数用于申请和释放I/O 内存的范围。
struct resource *request_mem_region(unsigned long start, unsigned long
len, char *name);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值