内存与端口映射

上传人:ba****u6 文档编号:164504209 上传时间:2022-10-24 格式:DOCX 页数:11 大小:304.85KB
收藏 版权申诉 举报 下载
内存与端口映射_第1页
第1页 / 共11页
内存与端口映射_第2页
第2页 / 共11页
内存与端口映射_第3页
第3页 / 共11页
资源描述:

《内存与端口映射》由会员分享,可在线阅读,更多相关《内存与端口映射(11页珍藏版)》请在装配图网上搜索。

1、A 可重入函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错 误编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。若对所使用的全局变量不加以保护,则此函数就不具有可重入性, 即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。B.寄存器和内存的区别寄存器和内存都是可以用来读写的,但寄存器的操作时有副作用,称之为side effect(边际效果) 读取一个寄存器可能导致寄存器中的内容发生变化,比如在一些设备的中断状态寄存器中,读取了寄存器后会自动清零IO

2、 空间和内存空间并不是所有的体系结构都有IO空间这个定义的,我所了解的只有X86体系上有,而ARM体系结构就没有这种区别, 在X86 上, IO空间和内存是独立的,他们各自有各自的总线,并且IO空间一般是64K,即16位,内存空间为4G 可见他们的差别是很大IO 端口和 IO 内存有了 IO空间的概念后,就有IO端口和IO内存当一个寄存器或内存位于IO空间时候,称之为IO端口 当一个寄存器或内存位于内存空间时候,称之为IO内存 这种映射是通过OS的MMU来把虚拟地址映射为物理地址的C.Linux在kernel/resource.c文件中定义了全局变量ioport_resource和iomem_

3、resource,来分别描述基于IO映射方式的整个IO端口空间和基于内存映射方式的IO内存资源 空间(包括IO端口和外设内存)。资源根节点的定义在kernel/Resource.c中定义了全局变量ioport_resource和iomem_resource,分别描述基于I/O映射方式的I/O端口空间和基于内存映射方式的I/O内存资源空间(包括I/O 端口和外设内存)。其定义如下:struct resource ioport_resource = PCI IO, 0x0000, IO_SPACE_LIMIT, IORESOURCE_IO struct resource iomem_resourc

4、e = PCI mem, 0x00000000, 0xffffffff, IORESOURCE_MEM 其中,宏IO_SPACE_LIMIT表示整个I/O空间的大小,对于X86平台而言,它是Oxffff (定义在include/asm-i386/io.h头文件中)。显然,I/O内存空间的大小是4GB。 IO端口空间的申请、检测、释放操作基于I/O Region的操作函数_XXX_region(),Linux在头文件include/linux/ioport.h中定义了三个对I/O端口空间进行操作的宏:request_region()宏,请求在I/O端口空间 中分配指定范围的I/O端口资源。che

5、ck_region()宏,检查I/O端口空间中的指定I/O端口资源是否已被占用。release_region()宏,释放I/O端口空间中的指定I/O端口资源。 这三个宏的定义如下:1 #define request_region(start,n,name) _request_region(&ioport_resource, (start), (n), (name)2 #define check_region(start,n)_check_region(&ioport_resource, (start), (n)3 #define release_region(start,n)_release_

6、region(&ioport_resource, (start), (n)其中,宏参数start指定I/O端口资源的起始物理地址(是I/O端口空间中的物理地址),宏参数n指定I/O端口资源的大小。读写 I/O 端口空间在驱动程序请求了 I/O端口空间中的端口资源后,它就可以通过CPU的IO指定来读写这些I/O端口了。在读写I/O端口时要注意的一点就是,大多数平台都区分8位、 16位和32位的端口,也即要注意I/O端口的宽度。Linux 中访问 IO 端口函数:inb()4nw(),inl(),outb(),outw(),outl0,“b” “w” “l” 分别代表 8 位,16 位,32位。I

7、/O 内存资源的申请、检测、释放操作基于I/O Region的操作函数_XXX_region(),Linux在头文件include/linux/ioport.h中定义了三个对I/O内存资源进行操作的宏:#define request_mem_region(start,n,name) _request_region(&iomem_resource, (start), (n), (name)#define check_mem_region(start,n)_check_region(&iomem_resource, (start), (n)#define release_mem_region(st

8、art,n)_release_region(&iomem_resource, (start), (n)1) request_mem_region(),请求分配指定的IO内存资源2) check_mem_region(),检查指定的IO内存资源是否已被占用3) release_mem_region(),释放指定的IO内存资源其中,参数start是IO内存区的物理地址(是CPU的RAM物理地址空间中的物理地址),参数n指定I/O内存资源的大小。驱动开发人员可以将内存映射方式的IO端口和外设内存统一看作是IO内存资源。 ioremap()用来将IO资源的物理地址映射到内核虚地址空间(3GB - 4G

9、B)中,参数addr是指向内核虚地址的指针。读写IO内存资源Linux 中访问 IO 内存资函数:readb(),readw(),readl(),writeb(),writew(),writel()oRISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地址空间,外设I/O端口成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O 端口,而不需要设立专门的外设I/O指令。I/O端口空间曾一度在x86平台上被广泛使用,但由于它非常小,故大多数现代总线的设备都以内存映射方式(Memory-mapped)来映射它的I/O端口(指I/O寄存器) 和外设内存。基于内存映

10、射方式的I/O端口(指I/O寄存器)和外设内存可以通称为“I/O内存”资源(I/O Memory)因为这两者在硬件实现上的差异对于软件来说是完全透 明的,所以驱动程序开发人员可以将内存映射方式的I/O端口和外设内存统一看作是I/O内存资源。I/O内存资源是在CPU的单一内存物理地址空间内进行编址的,也即它和系统RAM同处在一个物理地址空间内。因此通过CPU的访内指令就可以访问I/O内存资源。注意:linux内核给供了完全对IO Port和IO Mem的支持,然而具体去看看driver目录下的驱动程序,很少按照这个规范去组织IO Port和IO Mem资源。对这二者访 问最关键问题就是地址的定位

11、,在C语言中,使用volatile就可以实现。很多的代码访问IO Port中的寄存器时,就使用volatile关键字,虽然功能可以实现,我们还是不推 荐使用。就像最简单的延时莫过于while,可是在多任务的系统中是坚决避免的!一般来说,系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定:通过系统固件(如BIOS)在启动时分配得到,或者通过设备的硬连线(hardwired) 得到。比如,PCI卡的I/O内存资源的物理地址就是在系统启动时由PCI BIOS分配并写到PCI卡的配置空间中的BAR中的。而ISA卡的I/O内存资源的物理地址则是通过设 备硬连线映射到640KB -1MB

12、范围之内的。但CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,因为它们是在系统启动后才已知的(某种意义上讲是动态的)所以驱动程序并不能 直接通过物理地址访问I/O内存资源,而必须将它们映射到内核虚拟地址空间内(通过页表),然后才能根据映射所得到的内核虚拟地址范围,通过访内指令访问这些I/O内 存资源。Linux在io.h头文件中声明了函数ioremap()将I/O内存资源的物理地址映射到内核虚拟地址空间(3GB4GB)中,原型如下:void * ioremap(unsigned long phys_addr, unsigned long size, unsigned

13、 long flags)void iounmap(void * addr) /取消 ioremap()所做的映射这两个函数都是实现在mm/ioremap.c文件中。将I/O内存资源的物理地址映射成内核虚拟地址后,理论上讲我们就可以象读写RAM那样直接读写I/O内存资源了。为了保证驱动程序的跨平台的可移植性,我们应该 使用Linux中特定的函数来访问I/O内存资源,而不应该通过指向内核虚拟地址的指针来访问。如在x86平台上,读写I/O的函数如下所示:#define readb(addr) (*(volatile unsigned char *) _io_virt(addr)#define rea

14、dw(addr) (*(volatile unsigned short *) _io_virt(addr)#define readl(addr) (*(volatile unsigned int *) _io_virt(addr)#define writeb(b,addr) (*(volatile unsigned char *) _io_virt(addr) = (b)#define writew(b,addr) (*(volatile unsigned short *) _io_virt(addr) = (b)#define writel(b,addr) (*(volatile unsig

15、ned int *) _io_virt(addr) = (b)#define memset_io(a,b,c) memset(_io_virt(a),(b),(c)#define memcpy_fromio(a,b,c) memcpy(a),_io_virt(b),(c)#define memcpy_toio(a,b,c) memcpy(_io_virt(a),(b),(c)最后,我们要特别强调驱动程序中mmap函数的实现方法。用mmap映射一个设备,意味着使用户空间的一段地址关联到设备内存上,这使得只要程序在分配的地址范围 内进行读取或者写入,实际上就是对设备的访问。笔者在Linux源代码中

16、进行包含ioremap文本的搜索,发现真正出现的ioremap的地方相当少。所以笔者追根索源地寻找I/O操作的物理地址转换到虚拟地址的真实所在, 发现Linux有替代ioremap的语句,但是这个转换过程却是不可或缺的。D.Ch1、linux中的IO端口映射和IO内存映射(一)地址的概念1) 物理地址:CPU地址总线传来的地址,由硬件电路控制其具体含义。物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、BIOS等)。 在程序指令中的虚拟地址经过段映射和页面映射后,就生成了物理地址,这个物理地址被放到CPU的地址线上。物理地址空间,一部分给物理RAM (内存)用,一

17、部分给总线用,这是由硬件设计来决定的,因此在32 bits地址线的x86处理器中,物理地址空间是2的32次方,即 4GB,但物理RAM 一般不能上到4GB,因为还有一部分要给总线用(总线上还挂着别的许多设备)。在PC机中,一般是把低端物理地址给RAM用,高端物理地址给总线 用。2) 总线地址:总线的地址线或在地址周期上产生的信号。外设使用的是总线地址,CPU使用的是物理地址。物理地址与总线地址之间的关系由系统的设计决定的。在x86平台上,物理地址就是总线地址,这是因为它们共享相同的地址空间一一这句话有点难理解,详见下面的 “独立编址”在其他平台上,可能需要转换/映射。比如:CPU需要访问物理地

18、址是OxfaOOO的单元,那么在x86平台上,会产生一个PCI总线上对OxfaOOO地址的访问。 因为物理地址和总线地址相同,所以凭眼睛看是不能确定这个地址是用在哪儿的,它或者在内存中,或者是某个卡上的存储单元, 甚至可能这个地址上没有对应的存储器。3) 虚拟地址:现代操作系统普遍采用虚拟内存管理(Virtual Memory Management)机制,这需要MMU(Memory Management Unit)的支持。MMU通常是CPU的一部分, 如果处理器没有MMU,或者有MMU但没有启用,CPU执行单元发出的内存地址将直接传到芯片引脚上,被 内存芯片(物理内存)接收,这称为物理地址(P

19、hysical Address), 如果处理器启用了 MMU,CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address),而MMU将这个地址翻译成另一个地址 发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址。Linux中,进程的4GB (虚拟)内存分为用户空间、内核空间。用户空间分布为03GB (即PAGE_OFFSET,在0X86中它等于OxCOOOOOOO),剩下的1G为内核空间。 程序员只能使用虚拟地址。系统中每个进程有各自的私有用户空间(03G),这个空间对系统中的其他进程是不可见的。CPU发出取指令请求时的地址是当前

20、上下文的虚拟地址,MMU再从页表中找到这个虚拟地址的物理地址,完成取指。同样读取数据的也是虚拟地址,比如mov ax, var.编 译时var就是一个虚拟地址,也是通过MMU从也表中来找到物理地址,再产生总线时序,完成取数据的。(二)编址方式1) 外设都是通过读写设备上的寄存器来进行的,外设寄存器也称为“I/O端口”而IO端口有两种编址方式:独立编址和统一编制。统一编址:外设接口中的IO寄存器(即IO端口)与主存单元一样看待,每个端口占用一个存储单元的地址,将主存的一部分划出来用作IO地址空间,如,在PDP-11 中,把最高的4K主存作为IO设备寄存器地址。端口占用了存储器的地址空间,使存储量

21、容量减小。统一编址也称为“I/O内存”方式,夕卜设寄存器位于“内存空间”(很多外设有自己的内存、缓冲区,夕卜设的寄存器和内存统称“I/O空间”。女口,Samsung的S3C2440,是32位ARM处理器,它的4GB地址空间被外设、RAM等瓜分:0x8000 1000 LED 8*8点阵的地址0x4800 00000x6000 0000 SFR (特殊暂存器)地址空间0x3800 1002 键盘地址0x3000 0000 0x3400 0000 SDRAM 空间0x2000 0020 0x2000 002e IDE0x1900 0300 CS8900独立编址(单独编址):IO地址与存储地址分开独

22、立编址,I/0端口地址不占用存储空间的地址范围,这样,在系统中就存在了另一种与存储地址无关的IO地 址,CPU 也必须具有专用与输入输出操作的IO指令(IN、OUT等)和控制逻辑。独立编址下,地址总线上过来一个地址,设备不知道是给IO端口的、还是给存储器的,于是处理器 通过MEMR/MEMW和IOR/IOW两组控制信号来实现对I/O端口和存储器的不同寻址。如,intel 80x86就采用单独编址,CPU内存和I/O是一起编址的,就是说内存一部分 的地址和I/O地址是重叠的。独立编址也称为“I/O端口 ”方式,外设寄存器位于“I/O (地址)空间”对于x86架构来说,通过IN/OUT指令访问。P

23、C架构一共有65536个8bit的I/O端口,组成64K个I/O地址空间,编号从00xFFFF,有16位,80x86用低16位地址 线A0-A15来寻址。连续两个8bit的端口可以组成一个16bit的端口,连续4个组成一个32bit的端口。I/O地址空间和CPU的物理地址空间是两个不同的概念,例如I/O地 址空间为64K,一个32bit的CPU物理地址空间是4G。女口,在Intel 8086+Redhat9.0下用“more /proc/ioports”可看到:0000-001f : dma10020-003f : pic10040-005f : timer0060-006f : keyboa

24、rd0070-007f : rtc0080-008f : dma page reg00a0-00bf : pic200c0-00df : dma200f0-00ff : fpu0170-0177 : ide1不过Intel x86平台普通使用了名为内存映射(MMIO)的技术,该技术是PCI规范的一部分,IO设备端口被映射到内存空间,映射后,CPU访问IO端口就如同访问内 存一样。看Intel TA 719文档给出的x86/x64系统典型内存地址分配表:系统资源占用BIOS1M本地 APIC4K芯片组保留2MIO APIC4KPCI 设备256MPCI Express 设备256MPCI 设备(

25、可选)256M显示帧缓存16MTSEG1M对于某一既定的系统,它要么是独立编址、要么是统一编址,具体采用哪一种则取决于CPU的体系结构。女口,PowerPC、m68k等采用统一编址,而X86等则采用独立 编址,存在IO空间的概念。目前,大多数嵌入式微控制器如ARM、PowerPC等并不提供I/O空间,仅有内存空间,可直接用地址、指针访问。但对于Linux内核而言,它 可能用于不同的CPU,所以它必须都要考虑这两种方式,于是它采用一种新的方法,将基于I/O映射方式的或内存映射方式的I/O端口通称为“I/O区域”(I/O region),不 论你采用哪种方式,都要先申请IO区域:request_r

26、esource(),结束时释放它:release_resource()。2) 对外设的访问1、访问 I/O 内存的流程是: request_mem_region()-ioremap() -ioread8()/iowrite8() -iounmap()-release_mem_region() 。前面说过,IO内存是统一编址下的概念,对于统一编址,IO地址空间是物理主存的一部分,对于编程而言,我们只能操作虚拟内存,所以,访问的第一步就是要把设备 所处的物理地址映射到虚拟地址,Linux2.6下用ioremap():void *ioremap(unsigned long offset, unsig

27、ned long size); 然后,我们可以直接通过指针来访问这些地址,但是也可以用Linux内核的一组函数来读写: ioread8(), iowrite16(), ioread8_rep(), iowrite8_rep()2、访问 I/O 端口访问IO端口有2种途径:I/O映射方式(I/Omapped)、内存映射方式(Memory mapped)前一种途径不映射到内存空间,直接使用intb()/outb()之类的函数来读写 IO端口;后一种MMIO是先把IO端口映射到IO内存(“内存空间”,再使用访问IO内存的函数来访问IO端口。void ioport_map(unsigned long

28、port, unsigned int count); 通过这个函数,可以把port开始的count个连续的IO端口映射为一段“内存空间”然后就可以在其返回的地址是像访问IO内存一样访问这些IO端口。程序运行在保护模式下,这样程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指 段选择器。系统使用基于分页机制的虚拟内存。每个进程有4GB的虚拟地址空间。基于分页机制,这4GB地址空间的一些部分被映射了物理内存,一些部分映射硬盘上的交 换文件,一些部分什么也没有映射。程序中使用的都是4GB地址空间中的虚拟地址。而访问物理内存,

29、需要使用物理地址。 下面我们看看什么是物理地址,什么是虚拟地址。物理地址 (physical address): 放在寻址总线上的地址。放在寻址总线上,如果是读,电路根据这个地址每位的值就将相应地址的物理内存中的数据放到数据总线中传输。如 果是写,电路根据这个地址每位的值就在相应地址的物理内存中放入数据总线上的内容。物理内存是以字节(8位)为单位编址的。虚拟地址(virtual address): CPU启动保护模式后,程序运行在虚拟地址空间中。注意,并不是所有的“程序”都是运行在虚拟地址中OCPU在启动的时候是运行在实模式的, Bootloader以及内核在初始化页表之前并不使用虚拟地址,而

30、是直接使用物理地址的。如果CPU寄存器中的分页标志位被设置,那么执行内存操作的机器指令时,CPU (准确来说,是MMU,即Memory Management Unit,内存管理单元)会自动根据页目录和 页表中的信息,把虚拟地址转换成物理地址,完成该指令。比如mov eax,004227b8h,这是把地址004227b8h处的值赋给寄存器的汇编代码,004227b8这个地址就是虚拟址。CPU在执行这行代码时,发现寄存器中的分页标志位已经 被设定,就自动完成虚拟地址到物理地址的转换,使用物理地址取出值,完成指令。对于Intel CPU来说,分页标志位是寄存器CR0的第31位,为1表示使用分页,为0

31、表 示不使用分页。对于初始化之后的Win2k我们观察CR0,发现第31位为1。表明Win2k是使用分页的。使用了分页机制之后,4G的地址空间被分成了固定大小的页,每一页或者被映射到物理内存,或者被映射到硬盘上的交换文件中,或者没有映射任何东西。对于一般程序来 说,4G的地址空间,只有一小部分映射了物理内存,大片大片的部分是没有映射任何东西。物理内存也被分页,来映射地址空间。对于2bit的Win2k,页的大小是4K字节。 CPU用来把虚拟地址转换成物理地址的信息存放在叫做页目录和页表的结构里。物理内存分页,一个物理页的大小为4K字节,第0个物理页从物理地址0x00000000处开始。由于页的大小

32、为4KB,就是0x1000字节,所以第1页从物理地址0x00001000 处开始。第2页从物理地址0x00002000处开始。可以看到由于页的大小是4KB,所以只需要32bit的地址中高20bit来寻址物理页。页表(也叫页目录),一个页表的大小为4K字节,放在一个物理页中。由1024个4字节的页表项组成。页表项的大小为4个字节(32bit),所以一个页表中有1024个页表项。 页表中的每一项的内容(每项4个字节,32bit)高20bit用来放一个物理页的物理地址,低12bit放着一些标志。对于x86系统,页目录的物理地址放在CPU的CR3寄存器中。CPU 把虚拟地址转换成物理地址:一个虚拟地址

33、,大小4个字节(32bit),包含着找到物理地址的信息,分为3个部分:第22位到第31 位这10 位(最高10 位)是页目录中的索引,第12位到第21 位这10 位是页表中的索引, 第0位到第11位这12位(低12位)是页内偏移。对于一个要转换成物理地址的虚拟地址,CPU首先根据CR3中的值,找到页目录所在的物理页。然后根据虚拟地址的第22位到第31位这10位(最高的10bit)的值作为索引, 找到相应的页目录项(PDE,page directory entry),页目录项中有这个虚拟地址所对应页表的物理地址。有了页表的物理地址,根据虚拟地址的第12位到第21位这10位的值作为 索引,找到该页

34、表中相应的页表项(PTE,page table entry),页表项中就有这个虚拟地址所对应物理页的物理地址。最后用虚拟地址的最低12位,也就是页内偏移,加上这个物理 页的物理地址,就得到了该虚拟地址所对应的物理地址。一个页目录有1024项,虚拟地址最高的10bit刚好可以索引1024项(2的10次方等于1024)。一个页表也有1024项,虚拟地址中间部分的10bit,刚好索引1024项。虚拟地 址最低的12bit(2的12次方等于4096),作为页内偏移,刚好可以索引4KB,也就是一个物理页中的每个字节。一个虚拟地址转换成物理地址的计算过程就是,处理器通过CR3找到当前页目录所在物理页,取虚

35、拟地址的高10bit,然后把这10bit左移2bit (因为每个页目录项4个字节长, 左移2bit相当于乘4)得到在该页中的地址,取出该地址处PDE(4个字节),就找到了该虚拟地址对应页表所在物理页,取虚拟地址第12位到第21位这10位,然后把这10bit 左移2bit(因为每个页表项4个字节长,左移2bit相当于乘4)得到在该页中的地址,取出该地址处的PTE(4个字节),就找到了该虚拟地址对应物理页的地址,最后加上 12bit的页内偏移得到了物理地址。32bit的一个指针,可以寻址范围0x00000000-0xFFFFFFFF,4GB大小。也就是说一个32bit的指针可以寻址整个4GB地址空

36、间的每一个字节。一个页表项负责4K的地址空间 和物理内存的映射,一个页表1024项,也就是负责1024*4k=4M的地址空间的映射。一个页目录项,对应一个页表。一个页目录有1024项,也就对应着1024个页表,每个 页表负责4M地址空间的映射。1024个页表负责1024*4M=4G的地址空间映射。一个进程有一个页目录。所以以页为单位,页目录和页表可以保证4G的地址空间中的每页 和物理内存的映射。每个进程都有自己的 4G 地址空间,从 0x00000000-0xFFFFFFFF 。通过每个进程自己的一套页目录和页表来实现。由于每个进程有自己的页目录和页表,所以每个进程的地 址空间映射的物理内存是

37、不一样的。两个进程的同一个虚拟地址处(如果都有物理内存映射)的值一般是不同的,因为他们往往对应不同的物理页。在windows下4G地址空间中低2G,0x00000000-0x7FFFFFFF是用户地址空间,4G地址空间中高2G,0x80000000-0xFFFFFFFF是系统地址空间。访问系统地址空间需要程序有ring0的权限。而Linux对4G空间的划分不同与windows。linux将最高的1G字节(从虚拟地址 0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间

38、” 在LINUX系统下,0xC00000000-0XFFFFFFFF为系统空间,为所有的系统进程所共享,0X00000000-0XBFFFFFFF为用户空间。Ch2、Linux下的IO端口和IO内存CPU对外设端口物理地址的编址方式有两种:IO映射方式、内存映射方式o Linux将基于IO映射方式的IO端口和基于内存映射方式的IO端口统称为IO区域(IO region)。 IO region 仍然是一种 IO 资源,故它仍可用 resource 结构类型来描述。1. IO Region 的分配在函数request_resource()的基础上,Linux实现了用于分配I/O区域的函数_requ

39、est_regionO,如下:struct resource * _request_region(struct resource *parent, unsigned long start, unsigned long n, const char *name) struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);if (res) memset(res, 0, sizeof(*res); res-name = name;res-start = start;res-end = start + n - 1;res-flags = IORES

40、OURCE_BUSY;write_lock(&resource_lock);for (;) struct resource *conflict; conflict = _request_resource(parent, res); if (!conflict)break;if (conflict != parent) parent = conflict; if (!(conflict-flags & IORESOURCE_BUSY) continue;/* Uhhuh, that didnt work out. */kfree(res); res = NULL; break;write_unl

41、ock(&resource_lock);return res;1) 首先,调用kmalloc ()函数在SLAB分配器缓存中分配一个resource结构。2) 然后,相应的根据参数来填充resource结构。注意! flags成员被初始化为IORESOURCE_BUSY。3) 接下来,用一个for循环开始进行资源分配,循环体的步骤如下:a首先,调用_request_resource()函数进行资源分配。如果返回NULL,说明分配成功,因此就执行break语句推出for循环,返回所分配的resource结构的指针,函数成 功地结束。b如果_request_resource()函数分配不成功,则进

42、一步判断所返回的冲突资源节点是否就是父资源节点parent。如果不是,则将分配行为下降一个层次,即试图在当前冲突 的资源节点中进行分配(只有在冲突的资源节点没有设置IORESOURCE_BUSY的情况下才可以),于是让parent指针等于conflict,并在conflict-flags&IORESOURCE_BUSY 为0的情况下执行continue语句继续for循环。c否则如果相冲突的资源节点就是父节点parent,或者相冲突资源节点设置了 IORESOURCE_BUSY标志位,则宣告分配失败。于是调用kfree ()函数释放所分配的resource 结构,并将res指针置为NULL,最后

43、用break语句推出for循环。4) 最后,返回所分配的 resource 结构的指针2. IO Region 的释放函数_release_region()实现在一个父资源节点parent中释放给定范围的I/O Region。实际上该函数的实现思想与release_resource相类似。其源代码如下:1 void _release_region(struct resource *parent,2 unsigned long start, unsigned long n)3 4 struct resource *p;5 unsigned long end;66 p = &parent-chil

44、d;7 end = start + n - 1;98 for (;) 9 struct resource *res = *p;1210 if (!res)11 break;12 if (res-start end = end) 13 if (!(res-flags & IORESOURCE_BUSY) 14 p = &res-child;15 continue;16 17 if (res-start != start res-end != end)18 break;19 *p = res-sibling;20 kfree(res);21 return;22 23 p = &res-siblin

45、g;24 25 printk(Trying to free nonexistent resource 26 , start, end);27 类似地,该函数也是通过一个 for 循环来遍历父资源 parent 的 child 链表。为此,它让指针 res 指向当前正被扫描的子资源节点,指针 p 指向前一个子资源节点的 sibling 成员变量,p的初始值为指向parent-child。For循环体的步骤如下: 让res指针指向当前被扫描的子资源节点(res = *p)。 如果res指针为NULL,说明已经扫描完整个child链表,所以退出for循环。 如果res指针不为NULL,则继续看看所指

46、定的I/O区域范围是否完全包含在当前资源节点中,也即看看start,start+n-1是否包含在res-start,end冲。如果不属于,则 让p指向当前资源节点的sibling成员,然后继续for循环。如果属于,则执行下列步骤:l先看看当前资源节点是否设置了 IORESOURCE_BUSY标志位。如果没有设置该标志位,则说明该资源节点下面可能还会有子节点,因此将扫描过程下降一个层次, 于是修改p指针,使它指向res-child,然后执行continue语句继续for循环。l如果设置了 IORESOURCE_BUSY标志位。则一定要确保当前资源节点就是所指定的I/O区域,然后将当前资源节点从其

47、父资源的child链表中去除。这可以通过让前 一个兄弟资源节点的sibling指针指向当前资源节点的下一个兄弟资源节点来实现(即让*p=res-sibling),最后调用kfree()函数释放当前资源节点的resource结构。然后函 数就可以成功返回了。3. 检查指定的 I/O Region 是否已被占用函数check_region()检查指定的I/O Region是否已被占用。其源代码如下:1 int _check_region(struct resource *parent, unsigned long start, unsigned long n)2 3 struct resource

48、 * res;44 res = _request_region(parent, start, n, check-region);5 if (!res)6 return -EBUSY;87 release_resource(res);8 kfree(res);9 return 0;10 该函数的实现与check_resource()的实现思想类似。首先,它通过调用request_region()函数试图在父资源parent中分配指定的I/O Region。如果分配不成功,将返回NULL, 因此此时函数返回错误值一EBUSY表示所指定的I/O Region已被占用。如果res指针不为空则说明所指定

49、的I/O Region没有被占用。于是调用release_resource()函数将刚刚 分配的资源释放掉(实际上是将res结构从parent的child链表去除),然后调用kfree ()函数释放res结构所占用的内存。最后,返回0值表示指定的I/O Region没有被占用。采用I/O映射方式的X86处理器为外设实现了一个单独的地址空间,也即“I/O空间”(I/O Space)或称为“I/O端口空间”,其大小是64KB (0x00000xffff)。Linux在 其所支持的所有平台上都实现了 “I/O端口空间”这一概念。由于I/O空间非常小,因此即使外设总线有一个单独的I/O端口空间,却也不

50、是所有的外设都将其I/O端口(指寄存器)映射到“I/O端口空间”中。比如,大多数PCI 卡都通过内存映射方式来将其I/O端口或外设内存映射到CPU的RAM物理地址空间中。而老式的ISA卡通常将其I/O端口映射到I/O端口空间中。Linux是基于“I/O Region”这一概念来实现对I/O端口资源(I/Omapped或Memorymapped)的管理的。在ARM ,Linux里面,全部都会做phy-virt的映射。映射方式中的一种是静态映射,ioremap是动态映射。在静态映射之后,仍然可以通过ioremap动态映射,也就是一 个IO物理地址可以映射到多个虚拟地址。(1)关于IO与内存空间:在

51、X86处理器中存在着I/O空间的概念,I/O空间是相对于内存空间而言的,它通过特定的指令in、out来访问。端口号标识了外设的寄存器地址。Intel语法的in、out指 令格式为:IN累加器,端口号| DXOUT 端口号| DX,累加器目前,大多数嵌入式微控制器如ARM、PowerPC等中并不提供I/O空间,而仅存在内存空间。内存空间可以直接通过地址、指针来访问,程序和程序运行中使用的变量和 其他数据都存在于内存空间中。即便是在X86处理器中,虽然提供了 I/O空间,如果由我们自己设计电路板,外设仍然可以只挂接在内存空间。此时,CPU可以像访问一个内存单元那样访问外设I/O端 口,而不需要设立

52、专门的I/O指令。因此,内存空间是必须的,而I/O空间是可选的。( 2) inb 和 outb:在Linux设备驱动中,宜使用Linux内核提供的函数来访问定位于I/O空间的端口,这些函数包括:读写字节端口(8位宽) unsigned inb(unsigned port); void outb(unsigned char byte, unsigned port);读写字端口(16位宽)unsigned inw(unsigned port);void outw(unsigned short word, unsigned port);读写长字端口(32位宽)unsigned inl(unsigne

53、d port);void outl(unsigned longword, unsigned port);读写一串字节void insb(unsigned port, void *addr, unsigned long count); void outsb(unsigned port, void *addr, unsigned long count); insb()从端口 port开始读count个字节端口,并将读取结果写入addr指向的内存;outsb()将addr指向的内存的count个字节连续地写入port开始的端口。 读写一串字void insw(unsigned port, void

54、*addr, unsigned long count); void outsw(unsigned port, void *addr, unsigned long count); 读写一串长字void insl(unsigned port, void *addr, unsigned long count); void outsl(unsigned port, void *addr, unsigned long count); 上述各函数中I/O端口号port的类型高度依赖于具体的硬件平台,因此,只是写出了 unsigned。( 3) readb 和 writeb:在设备的物理地址被映射到虚拟地址

55、之后,尽管可以直接通过指针访问这些地址,但是工程师宜使用Linux内核的如下一组函数来完成设备内存映射的虚拟地址的读写,这些 函数包括: 读 I/O 内存unsigned int ioread8(void *addr);unsigned int ioread16(void *addr);unsigned int ioread32(void *addr); 与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持): unsigned readb(address);unsigned readw(address);unsigned readl(address); 写 I/O 内存

56、void iowrite8(u8 value, void *addr); void iowrite16(u16 value, void *addr); void iowrite32(u32 value, void *addr);与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):void writeb(unsigned value, address); void writew(unsigned value, address); void writel(unsigned value, address);(4)把I/O端口映射到“内存空间”:void *ioport_ma

57、p(unsigned long port, unsigned int count);通过这个函数,可以把port开始的count个连续的I/O端口重映射为一段“内存空间”。然后就可以在其返回的地址上像访问I/O内存一样访问这些I/O端口。当不再需要这种 映射时,需要调用下面的函数来撤消:void ioport_unmap(void *addr);实际上,分析ioport_map()的源代码可发现,所谓的映射到内存空间行为实际上是给开发人员制造的一个“假象”并没有映射到内核虚拟地址,仅仅是为了让工程师可使用 统一的I/O内存访问接口访问I/O端口。一、I/O 端口端口(port)是接口电路中能被

58、CPU直接访问的寄存器的地址。几乎每一种外设都是通过读写设备上的寄存器来进行的。CPU通过这些地址即端口向接口电路中的寄存器 发送命令,读取状态和传送数据。外设寄存器也称为“I/O端口”通常包括:控制寄存器、状态寄存器和数据寄存器三大类,而且一个外设的寄存器通常被连续地编址。二、IO 内存例如,在PC上可以插上一块图形卡,有2MB的存储空间,甚至可能还带有ROM,其中装有可执行代码。三、 IO 端口和 IO 内存的区分及联系这两者如何区分就涉及到硬件知识,X86体系中,具有两个地址空间:IO空间和内存空间,而RISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地 址空间,即

59、内存空间。内存空间:内存地址寻址范围,32位操作系统内存空间为2的32次幕,即4G。IO空间:X86特有的一个空间,与内存空间彼此独立的地址空间,32位X86有64K的IO空间。IO端口:当寄存器或内存位于IO空间时,称为IO端口。一般寄存器也俗称I/O端口,或者说I/O ports,这个I/O端口可以被映射在Memory Space,也可以被映射在I/O Space。 IO内存:当寄存器或内存位于内存空间时,称为IO内存。四、外设IO端口物理地址的编址方式CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/O mapped),另一种是内存映射方式(Memorymapped

60、)。而具体采用哪一种则取决于CPU 的体系结构。1、统一编址RISC指令系统的CPU (如,PowerPC、m68k、ARM等)通常只实现一个物理地址空间(RAM)。在这种情况下,外设I/O端口的物理地址就被映射到CPU的单一物理 地址空间中,而成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。统一编址也称为“I/O内存”方式,夕卜设寄存器位于“内存空间”(很多外设有自己的内存、缓冲区,夕卜设的寄存器和内存统称“I/O空间”。2、独立编址而另外一些体系结构的CPU (典型地如X86)则为外设专门实现了一个单独地地址空间,称为“I/O地

61、址空间”或者“I/O端口空间”这是一个与CPU地RAM物理地 址空间不同的地址空间,所有外设的I/O端口均在这一空间中进行编址。CPU通过设立专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元(也即I/O 端口)。与RAM物理地址空间相比,I/O地址空间通常都比较小,如x86 CPU的I/O空间就只有64KB (0Oxffff)。这是“I/O映射方式”的一个主要缺点。独立编址也称为“I/O端口 ”方式,外设寄存器位于“I/O (地址)空间”3、优缺点 独立编址主要优点是:1) 、I/O端口地址不占用存储器空间;使用专门的I/O指令对端口进行操作,I/O指令短,执行速度快。

62、2) 、并且由于专门I/O指令与存储器访问指令有明显的区别,使程序中I/O操作和存储器操作层次清晰,程序的可读性强。3) 、同时,由于使用专门的I/O指令访问端口,并且I/O端口地址和存储器地址是分开的,故I/O端口地址和存储器地址可以重叠,而不会相互混淆。4) 、译码电路比较简单(因为I/0端口的地址空间一般较小,所用地址线也就较少)。 其缺点是:只能用专门的I/0指令,访问端口的方法不如访问存储器的方法多。 统一编址优点:1) 、由于对I/O设备的访问是使用访问存储器的指令,所以指令类型多,功能齐全,这不仅使访问I/O端口可实现输入/输出操作,而且还可对端口内容进行算术逻辑运算,移 位等等

63、;2) 、另外,能给端口有较大的编址空间,这对大型控制系统和数据通信系统是很有意义的。这种方式的缺点是端口占用了存储器的地址空间,使存储器容量减小,另外指令长度比专门I/O指令要长,因而执行速度较慢。究竟采用哪一种取决于系统的总体设计。在一个系统中也可以同时使用两种方式,前提是首先要支持I/O独立编址。Intel的x86微处理器都支持I/O独立编址,因为它 们的指令系统中都有I/O指令,并设置了可以区分I/O访问和存储器访问的控制信号引脚。而一些微处理器或单片机,为了减少引脚,从而减少芯片占用面积,不支持I/O 独立编址,只能采用存储器统一编址。五、Linux 下访问 IO 端口对于某一既定的

64、系统,它要么是独立编址、要么是统一编址,具体采用哪一种则取决于CPU的体系结构。女口,PowerPC、m68k等采用统一编址,而X86等则采用独 立编址,存在IO空间的概念。目前,大多数嵌入式微控制器如ARM、PowerPC等并不提供I/O空间,仅有内存空间,可直接用地址、指针访问。但对于Linux内核而言,它 可能用于不同的CPU,所以它必须都要考虑这两种方式,于是它采用一种新的方法,将基于I/O映射方式的或内存映射方式的I/O端口通称为“I/O区域(I/O region),不论 你采用哪种方式,都要先申请IO区域:request_resource(),结束时释放它:release_resource()。

展开阅读全文
温馨提示:
1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
2: 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
3.本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
关于我们 - 网站声明 - 网站地图 - 资源地图 - 友情链接 - 网站客服 - 联系我们

copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!