一、引a
嵌入式系l?Embedded Systems)是根据应用的要求Q将操作pȝ和功能Y仉成于计算机硬件系l之中,从而实现Y件与g一体化的计机pȝ。嵌入式pȝ出现?0q代晚期Q它最初被用于控制机电(sh)?sh)话交换机,如今已被q泛的应用于工业刉、过E控制、通讯、A器、A表、汽车、船舶、航I、航天、军事装备、消费类产品{众多领域。嵌入式pȝ在数量上q远过?jin)各U通用计算机系l:(x)计算机系l核?j)CPUQ每q在全球范围内的产量大概在二十亿颗左叻I其中过80Q应用于各类专用性很强的嵌入式系l?/p>
一般的_(d)凡是带有微处理器的专用Ygpȝ都可以称为嵌入式pȝ。和通用的计^台相比,嵌入式系l往往h功能单一、体U小、功耗低、可靠性高、剪裁性好、Yg集成度高、计能力相对较低等特点。多q来Q嵌入式讑֤中没有操作系l,其主要原因有二:(x)首先Q诸如洗衣机、微波炉、电(sh)冰箱q样的设备仅仅需要一道简单的控制E序Q以理数量有限的按钮和指示灯,没有使用操作pȝ的必要;其次Q它往往只具有有限的g资源Q不以支持一个操作系l?/p>
然而,随着g的发展,嵌入式系l变得越来越复杂Q最初的控制E序中逐步的加入了(jin)许多功能Q而这些功能中有很多可以由操作pȝ提供。于是,?0q代末期出现?jin)嵌入式操作pȝ(Embedded Operating Systems)Q它的出现大大简化了(jin)应用E序设计Qƈ可以有效的保障Y件质量和~短开发周期。简单的ES一般ƈ不用操作系l,只包含一些控制流E,但是随着嵌入式操作系l在复杂性上的增长,单的程控制׃能满系l的要求Q这是就必须考虑使用操作pȝ做系lY件。因此,嵌入式操作系l就应运而生?/p>
随着EOS的广泛应用,业界已推Z些应用比较成功的EOS产品。归Uv来EOS应该h以下几个特点Q小巧、实时性、可装卸、固化代码、弱交互性、强E_性和l一的接口。目前用最多的EOS产品包括有:(x)Vxwork、QNX、PalmOS、WindowsCE、pSOS、Hopen OS(国内凯思集团公司自ȝ制开?{。其中,Vxwork使用最为广泛、市(jng)场占有率最高,其突出特Ҏ(gu)实时性强(采用优先U抢占和轮{调度{机?Q除此之外,其可靠性和可剪裁性也相当不错。QNX是一U~性极佳的pȝQ其核心(j)加上实时POSIX环境和一个完整的H口pȝq(sh)C兆。相比之下,Microsoft WinCE的核?j)体U庞大,实时性能也差Zh意,但由于Windowspd友好的用L(fng)面和为程序员所熟?zhn)的APIQƈ捆绑IE、Office{应用程序,正逐渐获得更大的市(jng)Z额。而与q些商业化的操作pȝ相比QLinux已经来受Ch们的注意?/p>
二、嵌入式Linux概述
Linux是一个成熟而稳定的|络操作pȝ。将Linux植入嵌入式设备具有众多的优点。首先,Linux的源代码是开攄QQ何h都可以获取ƈ修改Q用之开发自q产品。其ơ,Lirmx是可以定制的Q其pȝ内核最只有约134kB。一个带有中文系l和囑Ş用户界面的核?j)程序也可以做到不?MBQƈ且同L(fng)定。另外,它和多数Unixpȝ兼容Q应用程序的开发和UL相当Ҏ(gu)。同Ӟ׃h良好的可UL性,Z已成功Linuxq行于数癄gq_之上?/p>
然而,Linuxq专门为实时性应用而设计,因此如果惛_对实时性要求较高的嵌入式系l中q行LinuxQ就必须Zd实时软g模块。这些模块运行的内核I间正是操作pȝ实现q程调度、中断处理和E序执行的部分,因此错误的代码可能会(x)破坏操作pȝQ进而媄(jing)响整个系l的可靠性和E_性。Linux的众多优点还是它在嵌入式领域获得了(jin)q泛的应用,q出C(jin)数量可观的嵌入式Linuxpȝ。其中有代表性的包括QuClinux、ETLinux、ThinLinux、LOAF{。ETLinux通常用于在小型工业计机Q尤其是PCQ?04模块。ThinLinux面向专用的照相机服务器、X-10控制器、MP3播放器和其它cM的嵌入式应用。LOAF是Linux On A Floppy的羃略语Q它q行?86q_上?/p>
三、Linux作ؓ(f)嵌入式操作系l的优势
Linux作ؓ(f)嵌入式操作系l的优势主要有以下几点:(x)
1?可应用于多种gq_。Linux已经被移植到多种gq_Q这对于l费Q时间受限制的研I与开发项目是很有吸引力的。原型可以在标准q_上开发后UL到具体的g上,加快?jin)Y件与g的开发过E。Linux采用一个统一的框架对gq行理Q从一个硬件^台到另一个硬件^台的改动与上层应用无兟뀂Linux可以随意地配|,不需要Q何的许可证或商家的合作关p,源代码可以免费得到。这使得采用Linux作ؓ(f)操作pȝ不会(x)遇到M关于版权的纠U毫无疑问,q会(x)节省大量的开发费用。本w内|网l支持,而目前嵌入式pȝ对网l支持要求越来越高。Linux的高度模块化使添加部仉常容易?/p>
2?Linux是一个和Unix怼、以内核为基的、具有完全的内存讉K控制Q支持大量硬?包括X86QAlpha、ARM和Motorola{现有的大部分芯?{特性的一U通用操作pȝ。其E序源码全部公开QQ何h可以修改q在GUN通用公共许可?GNU General Public License)下发行。这P开发h员可以对操作pȝq行定制Q适应其特D需要?/p>
3?Linux带有Unix用户熟?zhn)的完善的开发工P几乎所有的Unixpȝ的应用Y仉已移植到?jin)Linux上。Linuxq提供了(jin)强大的网l功能,有多U可选择H口理?X Windows)。其强大的语a~译器GCCQC++{也可以很容易得刎ͼ不但成熟完善Q而且使用方便?/p>
四、嵌入式Linux的徏?/strong>
完整的嵌入式Linux解决Ҏ(gu)应包括嵌入式Linux操作pȝ内核、运行环境、图形化界面和应用Y件等。由于嵌入式讑֤的特D要求,嵌入式Linux解决Ҏ(gu)中的内核、环境、GUI{都与标准Linux有很大不同,其主要挑(xi)战是如何在狭的FLASH、ROM和内存(sh)实现高质量的d实时调度、图形化昄、网l通信{功能?/p>
1?_内核
Linux内核有自ql构体系Q其中进E管理、内存管理和文gpȝ是其最基本?个子pȝ。图1单表CZ(jin)它的框架。用戯E可直接通过pȝ调用或者函数库来访问内核资源。正因ؓ(f)Linux内核hq样的结构,因此修改内核时必L意各个子pȝ之间的协调?img height="407" alt="" hspace="1" src="http://www.21ic.com/info/images/tougao/050725/qrsLinux-a.gif" width="292" align="right" vspace="1" border="0" />
嵌入式Linux内核一般由标准Linux内核裁剪而来。用户可Ҏ(gu)需求配|系l,剔除不需的服务功能、文件系l和讑֤驱动。经q裁剪、压~后的系l内怸般只?00k左右Q十分适合嵌入式设备。同标准Linux不同的是嵌入式Linux必须要实CFLASH或ROM的启动。标准Linux启动代码实现?jin)系l初始化和从软盘、硬盘O盘区引导内核。嵌入式Linux一般保存在FLASH或ROM中,标准LILO无法引导。在支持直接从FLASH讑֤引导的系l中Q如华恒公司的uClinuxQ引导程序主要完成对gpȝ的初始化工作和操作系l的解压、移位工作。在不支持直接从FLASH引导的系l中QFLASH讑֤只能作ؓ(f)非引导磁盘(sh)用。此Ӟ可采用先从硬盘或软盘加蝲一个小操作pȝQ如嵌入式DOSQ然后再执行"Loadlin"加蝲E序从FLASH引导嵌入式Linux?/p>
Ҏ(gu)准Linux的修改主要是虚拟内存和调度程序部分的改动。因为标准Linuxpȝ使用虚拟内存理的目的是Z(jin)能同时运行多个进E,但是q样每个待运行的q程所能分配的CPU旉片就受限Ӟ资源的用效率就低。这样对于实时性要求较高的嵌入式系l来_(d)实时d往往要求CPUh很高的突发处理能力,卛_有些时候需要极高的处理效率Q因此需要屏蔽内核的虚拟内存理机制。对于无盘讑֤的嵌入式pȝQ不必采用虚存管理。强实时需求的嵌入式应用可以通过修改d调度模块实现Q主要是在内核和讑֤驱动E序中加入了(jin)许多切换炏V在该点处,pȝ(g)是否存在未处理的紧急中断,有则剥夺内核的运行,?qing)时处理中断。实现实时性服务的一个较好的Ҏ(gu)是在标准的Linux内核上增加一个实时内核,标准Linux内核作ؓ(f)一个Q务运行于实时内核上,强实时性Q务也直接q行在实时内怸Q如RT-Linux{?/p>
文gpȝ是嵌入式Linux操作pȝ必不可少的。但标准Linux支持大量的文件系l,因此除了(jin)满pȝ的正常运行需要而保留一U外Q其它的全部可以删除Q利用原有的讄选项可以U除。一般嵌入式讑֤文gpȝ主要使用RamDisk技术和|络文gpȝ技术。RamDisk可驻留于FlashQ运行时加蝲到内存(sh)?/p>
2?_q行环境
Linux通常的运行环境指用户q行M应用的基设施Q主要包括函数库和基本命令集{。标准Linuxpȝ同时向用h供了(jin)?rn)态和动态函数库。静(rn)态函数库在生成应用时直接链接到用户应用中。动态库在应用运行时才链接。由于嵌入式pȝ应用一般都是在开发^C预先生成的,因此嵌入式系l只需向应用提供动态函数库。Linux应用q行所需的函数库主要有C库、数学库、线E库、加密库、网l通信库等。其中最基本的是C语言的运行库glib。这个库主要完成基本的输入输出,内存讉KQ文件处理。一个标准的glib库大U要1200kB存储I间Q考虑到嵌入式Linux内核往往很小Q这U运行库实在太大Q我们做?jin)一些精的工作,Ҏ(gu)有两U:(x)(1)、用静(rn)态连接的Ҏ(gu)Q完全不使用q行库动态连接;(2)、对q个库的函数q行_?/p>
在一个桌面系l上Q用动态连接可以带来许多好处。用动态连接库Q可以让应用E序跟函数库的更新、升U分,便于l护Q可以让同时q行的多个程序共享一D代码。但是,在嵌入式pȝ中,很少有多个程序ƈ行的可能Q程序的l护Q尤其是库函数的l护更新是不常见的。这Ӟ使用?rn)态连接的优势极为明显。因为静(rn)态连接可以只库中用到的部分q接q程序。在应用E序较少(于5)的情况下Q静(rn)态连接可以达到较好的l果。ؓ(f)?jin)便于将来扩充的需要,我们也采用第二种Ҏ(gu)Q针Ҏ(gu)们的需要,对库函数的内容进行精Q只保留一些基本功能,q有一U方法是采用其它的C语言q行库。但是这些库对兼Ҏ(gu)媄(jing)响很大?/p>
基本命o(h)集同hq行用户应用的基Q主要包括初始化q程initQ终端获取getty、Shell和基本命令等。嵌入式pȝ的启动过E可能与标准Linux不同Q例如蟩q登录过E直接启动GUI{。这p求修改initQgetty{。标准Linux命o(h)集同L(fng)于体U问题无法直接应用于嵌入式环境。目前,命令集的解x(chng)法主要有集成Ҏ(gu)和汇~方法两U。集成方法采用集成公共部分减命令集整体体积Q用C实现Q有较好的^台移植性;汇编Ҏ(gu)则采用汇~编E减每个命令的体积Q这样可使体U很但其^台移植性较差?/p>
3?嵌入式Linux下的GUI
GUI在嵌入式pȝ或者实时系l中的地位越来越重要Q比如PDA、DVD播放机、WAP手机{,都需要一个完_(d)漂亮的图形用L(fng)面。这些系l对GUI的基本要求包括:(x)(1)、轻型、占用资源少Q?2)、高性能Q?3)、高可靠性;(4)、可配置。这些也成ؓ(f)评h(hun)嵌入式系l的重要指标。目前,嵌入式Linux上的GUI主要有winCE、Micro Window、紧~的X Window、MiniGUI(国内做得较好的自pY件之一)。标准Linux的Xfree86׃体积庞大Q运行环境要求高Q无法运行于嵌入式环境。嵌入式GUI主要通过削减功能Q降低性能来实CU小和占用资源少。目前嵌入式Linux上的GUI环境主要有两c:(x)Xcdwin32cRXcGUI分ؓ(f)服务方和客户方两斏V服务器Ҏ(gu)供鼠标、键盘处理和昄功能Q客h是用户应用,服务方和客户斚w过socket接口和X协议通信。采用该方式十分有利于远E网l图形化服务Q客h和服务方可通过|络实现X协议和图形显C。典型的XcGUI有Micro Window、紧~的X Window{。win32cȝGUI不存在客h和服务方Q每个Q务都自成一体,Md间的切换、事件分发由专门的管理Q务负责。如wiCE、MiniGUI是cM于win32cȝGUI?/p>
五、当前流行的几种嵌入式Linuxpȝ
除了(jin)数字l端领域以外QLinux在移动计^台、智能工业控制、金融业l端pȝQ甚臛_事领域都有着q泛的应用前景。这些Linux被统UCؓ(f)"嵌入式Linux"?/p>
1、RT-Linux
q是q国墨西哥理工学院开发的嵌入式Linux操作pȝ。到目前为止QRT-Linux已经成功地应用于航天飞机的空间数据采集、科学A器测控和?sh)?jing)Ҏ(gu)囑փ处理{广泛领域。RT-Linux开发者ƈ没有针对实时操作pȝ的特性而重写Linux的内核,因ؓ(f)q样做的工作量非常大Q而且要保证兼Ҏ(gu)也非常困难。ؓ(f)此,RT-Linux提出?jin)精巧的内核Qƈ把标准的Linux核心(j)作ؓ(f)实时核心(j)的一个进E,同用L(fng)实时q程一赯度。这样对Linux内核的改动非常小Qƈ且充分利用了(jin)Linux下现有的丰富的Y件资源?/p>
2、uClinux
uCLinux是Lineo公司的主打品,同时也是开放源码的嵌入式Linux的典范之作。uCLinux主要是针对目标处理器没有存储理单元MMU(Memory Management Unit) 的嵌入式pȝ而设计的。它已经被成功地ULC(jin)很多q_上。由于没有MMUQ其多Q务的实现需要一定技巧。uCLinux是一U优U的嵌入式Linux版本Q是micro-Conrol-Linux的羃写。它U承?jin)标准Linux的优良特性,l过各方面的型化改造,形成?jin)一个高度优化的、代码紧凑的嵌入式Linux。虽然它的体U很,却仍然保留了(jin)Linux的大多数的优点:(x)E_、良好的UL性、优U的网l功能、对各种文gpȝ完备的支持和标准丰富的API。它专ؓ(f)嵌入式系l做?jin)许多小型化的工作,目前已支持多ƾCPU。其~译后目标文件可控制在几百KB数量U,q已l被成功地移植到很多q_上?/p>
3、Embedix
Embedix是由嵌入式Linux行业主要厂商之一Luneo推出的,是根据嵌入式应用pȝ的特炚w新设计的Linux发行版本。Embedix提供?jin)超q?5U的Linuxpȝ服务Q包括Web服务器等。系l需要最?MB内存Q?MB ROM或快速闪存。EmbedixZLinux 2.2内核Qƈ已经成功地移植到?jin)Intel x86和PowerPC处理器系列上。像其它的Linux版本一PEmbedix可以免费获得。Luneoq发布了(jin)另一个重要的软g产品Q它可以让在Windows CE上运行的E序能够在Embedix上运行。Luneoq将计划推出Embedix的开发调试工具包、基于图形界面的览器等。可以说QEmbedix是一U完整的嵌入式Linux解决Ҏ(gu)?/p>
4、Xlinux
XLinux是由国|虎公司推出Q主要开发者是陈盈豪。他在加盟网虎几个月后便开发出?jin)基于XLinux的、号U是世界上最的嵌入式LinuxpȝQ内核只?43KBQ而且q在不断减小。XLinux核心(j)采用?字元集"专利技术,让Linux核心(j)不仅可能与标准字W集相容Q还含盖? 2个国家和地区的字W集。因此,XLinux在推qLinux的国际应用方面有独特的优ѝ?/p>
5、PoketLinux
由Agenda公司采用、作为其C?VR3PDA"的嵌入式Linux操作pȝ。它可以提供跨操作系l构造统一的、标准化的和开攄信息通信基础l构Q在此结构上实现端到端方案的完整q_。PoketLinux资源框架开放,使普通的软gl构可以为所有用h供一致的服务。PoketLinuxq_使用L(fng)视线从设备、^台和|络上移开Q由此引发了(jin)信息技术新时代的生。在PoketLinux中,UC为用户化信息交换(CIE)Q也是提供和访问ؓ(f)每个用户需求而定制的"主题"信息的能力,而不正在用的讑֤是什么?/p>
6、MidoriLinux
由Transmeta公司推出的MidoriLinux操作pȝ代码开放,在GUN普通公p?GPL)下发布,可以在httpQ?/midori.transmeta.com上立卌得。该公司有个名ؓ(f)"MidoriLinux计划"?MidoriLinux"q个名字来源于日本的"l色"---MidoriQ用来反映其Linux操作pȝ的环保外观?/p>
7、红旗嵌入式Linux
由北京中U院U旗软g公司推出的嵌入式Linux是国内做得较好的一Ƒֵ入式操作pȝ。目前,中科院计所自行开发的开放源码的嵌入式操作系l?--Easy Embedded OS(EEOS)也已l开始进入实用阶D了(jin)。该Ƒֵ入式操作pȝ重点支持p-Java。系l目标一斚w是小型化Q另一斚w能重用Linux的驱动和其它模块。由于有中科院计所的强大科研力量做后盾QEEOS有望发展成ؓ(f)功能完善、稳定、可靠的国嵌入式操作系l^台?/p>
六、结束语
׃Linux是一个内核源代码开放、具备一整套工具链、有强大的网l支持及(qing)成本低廉的操作系l,因此嵌入式Linux自诞生vq承了(jin)q众多独特优势,q它正在ƈ来多地受Ch们的x(chng)。据Even Data数据昄Q期望用嵌入式Linux的用户从2001q的11Q增?002q?7Q,而同期Vxwork只是?6Q到18Q,W(xu)in CE?Q到14Q。另外,在嵌入式Linux的各U应用市(jng)ZQ通信(语音和数?名列W一Q?000q的销售额?300万美元,?005q预计将辑ֈ1.26亿美元,可以预见Q嵌入式Linux在未来的通信用嵌入式操作pȝ中占据强有力的地位?
有些体系l构的CPUQ如QPowerPC、m68k{)(j)通常只实C个物理地址I间QRAMQ。在q种情况下,外设I/O端口的物理地址p映射到CPU的单一物理地址I间中,而成为内存的一部分。此ӞCPU可以象访问一个内存单元那栯问外?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口Q而不需要设立专门的外设I/O指o(h)。这是所谓的“内存映方式”(MemoryQmappedQ?/p>
而另外一些体pȝ构的CPUQ典型地如X86Q则为外设专门实C(jin)一个单独地地址I间Q称为?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O地址I间”或者?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口I间”。这是一个与CPU地RAM物理地址I间不同的地址I间Q所有外讄I/O端口均在q一I间中进行编址。CPU通过讄专门?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O指o(h)Q如X86的IN和OUT指o(h)Q来讉Kq一I间中的地址单元Q也?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口Q。这是所谓的?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O映射方式”(I/OQmappedQ。与RAM物理地址I间相比Q?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O地址I间通常都比较小Q如x86 CPU?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/OI间只?4KBQ?Q?xffffQ。这是?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O映射方式”的一个主要缺炏V?/p>
Linux基?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O映射方式的或内存映射方式?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口通称为?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O区域”(I/O regionQ。在讨论?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O区域的管理之前,我们首先来分析一下Linux是如何实现?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O资源”这一抽象概念的?/p>
3Q? Linux?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O资源的描q?/p>
Linux设计?jin)一个通用的数据结构resource来描q各U?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O资源Q如Q?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口、外讑ֆ存、DMA和IRQ{)(j)。该l构定义在include/linux/ioport.h头文件中Q?/p>
struct resource {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) const char *name;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) unsigned long start, end;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) unsigned long flags;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) struct resource *parent, *sibling, *child;
};
各成员的含义如下Q?/p>
1. name指针Q指向此资源的名U?br /> 2. start和endQ表C源的起始物理地址和终止物理地址。它们确定了(jin)资源的范_(d)也即是一个闭区间[start,end]?br /> 3. flagsQ描q此资源属性的标志Q见下面Q?br /> 4. 指针parent、sibling和childQ分别ؓ(f)指向父亲、兄弟和子资源的指针?/p>
属性flags是一个unsigned longcd?2位标志|用以描述资源的属性。比如:(x)资源的类型、是否只诅R是否可~存Q以?qing)是否已被占用等。下面是一部分常用属性标志位的定义(ioport.hQ:(x)
/*
* IO resources have these defined flags.
*/
#define IORESOURCE_BITS (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x000000ff (tng) (tng) (tng) (tng) (tng) (tng) (tng) /* Bus-specific bits */
#define IORESOURCE_IO (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x00000100 (tng) (tng) (tng) (tng) (tng) (tng) (tng) /* Resource type */
#define IORESOURCE_MEM (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x00000200
#define IORESOURCE_IRQ (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x00000400
#define IORESOURCE_DMA (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x00000800
#define IORESOURCE_PREFETCH (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x00001000 (tng) (tng) (tng) (tng) (tng) (tng) (tng) /* No side effects */
#define IORESOURCE_READONLY (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x00002000
#define IORESOURCE_CACHEABLE (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x00004000
#define IORESOURCE_RANGELENGTH (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x00008000
#define IORESOURCE_SHADOWABLE (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x00010000
#define IORESOURCE_BUS_HAS_VGA (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x00080000
#define IORESOURCE_UNSET (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x20000000
#define IORESOURCE_AUTO (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x40000000
#define IORESOURCE_BUSY (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) 0x80000000
(tng) (tng) (tng) (tng) (tng) (tng) (tng) /* Driver has marked this resource busy */
(tng)
指针parent、sibling和child的设|是Z(jin)以一U树(wi)的Ş式来理各种I/O资源?/p>
3Q? Linux?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O资源的管?/p>
Linux是以一U倒置的树(wi)形结构来理每一c?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O资源Q如Q?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口、外讑ֆ存、DMA和IRQQ的。每一c?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O资源都对应有一颗倒置的资源树(wi)Q树(wi)中的每一个节炚w是一个resourcel构Q而树(wi)的根l点root则描qC(jin)该类资源的整个资源空间?/p>
Z上述q个思想QLinux在kernel/Resource.c文g中实C(jin)对资源的甌、释攑֏(qing)查找{操作?/p>
3Q?Q? I/O资源的申?/p>
假设某类资源有如下这样一颗资源树(wi)Q?/p>
节点root、r1、r2和r3实际上都是一个resourcel构cd。子资源r1、r2和r3通过sibling指针链接成一条单向非循环链表Q其表头由root节点中的child指针定义Q因此也UCؓ(f)父资源的子资源链表。r1、r2和r3的parent指针均指向他们的父资源节点,在这里也是图中的root节点?/p>
假设惛_root节点中分配一D?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O资源Q由图中的阴影区域表C)(j)。函数request_resource()实现q一功能。它有两个参敎ͼ(x)①root指针Q表C在哪个资源根节点中进行分配;②new指针Q指向描q所要分配的资源Q即图中的阴影区域)(j)的resourcel构。该函数的源代码如下Qkernel/resource.cQ?
int request_resource(struct resource *root, struct resource *new)
{
(tng) (tng) (tng) (tng) (tng) (tng) (tng) struct resource *conflict;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) write_lock(&resource_lock);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) conflict = __request_resource(root, new);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) write_unlock(&resource_lock);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) return conflict ? -EBUSY : 0;
}
(tng)
对上q函数的NOTE如下Q?/p>
①资源锁resource_lockҎ(gu)有资源树(wi)q行d保护QQ何代码段在访问某一颗资源树(wi)之前都必d持有该锁。其定义如下Qkernel/Resource.cQ:(x)
static rwlock_t resource_lock = RW_LOCK_UNLOCKED;
②可以看出,函数实际上是通过调用内部?rn)态函数__request_resource()来完成实际的资源分配工作。如果该函数q回非空指针Q则表示有资源冲H;否则Q返回NULLpC分配成功?/p>
③最后,如果conflict指针为NULLQ则request_resource()函数q回q回?Q表C成功;否则q回QEBUSY表示惌分配的资源已被占用?/p>
函数__request_resource()完成实际的资源分配工作。如果参数new所描述的资源中的一部分或全部已l被其它节点所占用Q则函数q回与new相冲H的resourcel构的指针。否则就q回NULL。该函数的源代码如下
Qkernel/Resource.cQ:(x)
/* Return the conflict entry if you can't request it */
static struct resource * __request_resource
(struct resource *root, struct resource *new)
{
(tng) (tng) (tng) (tng) (tng) (tng) (tng) unsigned long start = new->start;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) unsigned long end = new->end;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) struct resource *tmp, **p;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) if (end < start)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) return root;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) if (start < root->start)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) return root;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) if (end > root->end)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) return root;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) p = &root->child;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) for (;;) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) tmp = *p;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (!tmp || tmp->start > end) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) new->sibling = tmp;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) *p = new;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) new->parent = root;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) return NULL;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) p = &tmp->sibling;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (tmp->end < start)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) continue;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) return tmp;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) }
}
(tng)
对函数的NOTEQ?/p>
①前三个if语句判断new所描述的资源范围是否被包含在root内,以及(qing)是否是一D|效的资源Q因为end必须大于startQ。否则就q回root指针Q表CZ根结点相冲突?/p>
②接下来用一个for循环遍历根节点root的child链表Q以便检查是否有资源冲突Qƈnew插入到child链表中的合适位|(child链表是以I/O资源物理地址从低到高的顺序排列的Q。ؓ(f)此,它用tmp指针指向当前正被扫描的resourcel构Q用指针p指向前一个resourcel构的sibling指针成员变量Qp的初始gؓ(f)指向rootQ?gt;sibling。For循环体的执行步骤如下Q?/p>
l 让tmp指向当前正被扫描的resourcel构QtmpQ?pQ?/p>
l 判断tmp指针是否为空Qtmp指针为空说明已经遍历完整个child链表Q,或者当前被扫描节点的v始位|start是否比new的结束位|endq要大。只要这两个条g之一成立的话Q就说明没有资源冲突Q于是就可以把new铑օchild链表中:(x)①设|new的sibling指针指向当前正被扫描的节点tmpQnew->sibling=tmpQ;②当前节点tmp的前一个兄弟节点的sibling指针被修改ؓ(f)指向newq个节点Q?p=newQ;③将new的parent指针讄为指向root。然后函数就可以q回?jin)(q回值NULL表示没有资源冲突Q?/p>
l 如果上述两个条g都不成立Q这说明当前被扫描节点的资源域有可能与new相冲H(实际上就是两个闭区间有交集)(j)Q因此需要进一步判断。ؓ(f)此它首先修改指针pQ让它指向tmp->siblingQ以便于l箋(hu)扫描child链表。然后,判断tmp->end是否于new->startQ如果小于,则说明当前节点tmp和new没有资源冲突Q因此执行continue语句Ql向下扫描child链表。否则,如果tmp->end大于或等于new->startQ则说明tmp->[start,end]和new->[start,end]之间有交集。所以返回当前节点的指针tmpQ表C发生资源冲H?/p>
3Q?Q? 资源的释?/p>
函数release_resource()用于实现I/O资源的释放。该函数只有一个参数——即指针oldQ它指向所要释攄资源。v源代码如下:(x)
int release_resource(struct resource *old)
{
(tng) (tng) (tng) (tng) (tng) (tng) (tng) int retval;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) write_lock(&resource_lock);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) retval = __release_resource(old);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) write_unlock(&resource_lock);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) return retval;
}
(tng)
可以看出Q它实际上通过调用__release_resource()q个内部?rn)态函数来完成实际的资源释攑ַ作。函数__release_resource()的主要Q务就是将资源区域o(w)ldQ如果已l存在的话)(j)从其父资源的child链表重摘除,它的源代码如下:(x)
static int __release_resource(struct resource *old)
{
(tng) (tng) (tng) (tng) (tng) (tng) (tng) struct resource *tmp, **p;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) p = &old->parent->child;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) for (;;) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) tmp = *p;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (!tmp)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) break;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (tmp == old) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) *p = tmp->sibling;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) old->parent = NULL;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) return 0;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) p = &tmp->sibling;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) (tng) return -EINVAL;
}
(tng)
对上q函C码的NOTE如下Q?/p>
同函数__request_resource()相类|该函C是通过一个for循环来遍历父资源的child链表。ؓ(f)此,它让tmp指针指向当前被扫描的资源Q而指针p则指向当前节点的前一个节点的sibling成员Qp的初始gؓ(f)指向父资源的child指针Q。@环体的步骤如下:(x)
①首先,让tmp指针指向当前被扫描的节点QtmpQ?pQ?/p>
②如果tmp指针为空Q说明已l遍历完整个child链表Q因此执行break语句推出for循环。由于在遍历q程中没有在child链表中找到参数old所指定的资源节点,因此最后返回错误|EINVALQ表C参数old是一个无效的倹{?/p>
③接下来Q判断当前被扫描节点是否是参数old所指定的资源节炏V如果是Q那将old从child链表中去除,也即让当前结点tmp的前一个兄弟节点的sibling指针指向tmp的下一个节点,然后oldQ?gt;parent指针讄为NULL。最后返?DC执行成功?/p>
④如果当前被扫描节点不是资源oldQ那ql扫描child链表中的下一个元素。因此将指针p指向tmpQ?gt;sibling成员?/p>
3Q?Q? (g)查资源是否已被占用,
函数check_resource()用于实现(g)查某一D?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O资源是否已被占用。其源代码如下:(x)
int check_resource(struct resource *root, unsigned long start, unsigned long len)
{
(tng) (tng) (tng) (tng) (tng) (tng) (tng) struct resource *conflict, tmp;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) tmp.start = start;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) tmp.end = start + len - 1;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) write_lock(&resource_lock);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) conflict = __request_resource(root, &tmp);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) if (!conflict)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) __release_resource(&tmp);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) write_unlock(&resource_lock);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) return conflict ? -EBUSY : 0;
}
(tng)
对该函数的NOTE如下Q?/p>
①构造一个(f)时资源tmpQ表C所要检查的资源[start,start+end-1]?/p>
②调用__request_resource()函数在根节点root甌tmp所表示的资源。如果tmp所描述的资源还被h使用Q则该函数返回NULLQ否则返回非I指针。因此接下来在conflict为NULL的情况下Q调用__release_resource()刚刚申L(fng)资源释放掉?/p>
③最后根据conflict是否为NULLQ返回-EBUSY?倹{?/p>
3Q?Q? L可用资源
函数find_resource()用于在一颗资源树(wi)中寻找未被用的、且满l定条g的(也即资源长度大小为sizeQ且在[min,max]区间内)(j)的资源。其函数源代码如下:(x)
/*
* Find empty slot in the resource tree given range and alignment.
*/
static int find_resource(struct resource *root, struct resource *new,
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) unsigned long size,
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) unsigned long min, unsigned long max,
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) unsigned long align,
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) void (*alignf)(void *, struct resource *, unsigned long),
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) void *alignf_data)
{
(tng) (tng) (tng) (tng) (tng) (tng) (tng) struct resource *this = root->child;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) new->start = root->start;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) for(;;) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (this)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) new->end = this->start;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) else
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) new->end = root->end;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (new->start < min)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) new->start = min;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (new->end > max)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) new->end = max;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) new->start = (new->start + align - 1) & ~(align - 1);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (alignf)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) alignf(alignf_data, new, size);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (new->start < new->end && new->end - new->start + 1 >= size)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) new->end = new->start + size - 1;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) return 0;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (!this)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) break;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) new->start = this->end + 1;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) this = this->sibling;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) (tng) return -EBUSY;
}
(tng)
对该函数的NOTE如下Q?/p>
同样Q该函数也要遍历root的child链表Q以L未被使用的资源空z。ؓ(f)此,它让this指针表示当前正被扫描的子资源节点Q其初始值等于root->childQ即指向child链表中的W一个节点,q让new->start的初始值等于root->startQ然后用一个for循环开始扫描child链表Q对于每一个被扫描的节点,循环体执行如下操作:(x)
①首先,判断this指针是否为NULL。如果不为空Q就让new->end{于this->startQ也卌资源new表示当前资源节点this前面那一D|使用的资源区间?/p>
②如果this指针为空Q那pnew->end{于root->end。这有两层意思:(x)W一U情况就是根l点的child指针为NULLQ即根节Ҏ(gu)有Q何子资源Q。因此此时先暂时new->end攑ֈ最大。第二种情况是已经遍历完整个child链表Q所以此时就让new表示最后一个子资源后面那一D|使用的资源区间?/p>
③根据参数min和max修正new->[start,end]的|以资源new被包含在[min,max]区域内?/p>
④接下来q行寚w操作?/p>
⑤然后,判断l过上述q些步骤所形成的资源区域new是否是一D|效的资源Qend必须大于或等于startQ,而且资源区域的长度满size参数的要求(endQstartQ?>=sizeQ。如果这两个条g均满I则说明我们已l找C(jin)一D|x(chng)件的资源I洞。因此在对new->end的D行修正后Q然后就可以q回?jin)(q回?表示成功Q?/p>
⑥如果上qC条g不能同时满Q则说明q没有找刎ͼ因此要l扫描链表。在l箋(hu)扫描之前Q我们还是要判断一下this指针是否为空。如果ؓ(f)I,说明已经扫描完整个child链表Q因此就可以推出for循环?jin)。否则就new->start的g改ؓ(f)this->end+1Qƈ让this指向下一个兄弟资源节点,从而l扫描链表中的下一个子资源节点?/p>
3Q?Q? 分配接口allocate_resource()
在find_resource()函数的基上,函数allocate_resource()实现Q在一颗资源树(wi)中分配一条指定大的、且包含在指定区域[min,max]中的、未使用资源区域。其源代码如下:(x)
/*
* Allocate empty slot in the resource tree given range and alignment.
*/
int allocate_resource(struct resource *root, struct resource *new,
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) unsigned long size,
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) unsigned long min, unsigned long max,
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) unsigned long align,
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) void (*alignf)(void *, struct resource *, unsigned long),
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) void *alignf_data)
{
(tng) (tng) (tng) int err;
(tng) (tng) (tng) write_lock(&resource_lock);
(tng) (tng) (tng) err = find_resource(root, new, size, min, max, align, alignf, alignf_data);
(tng) (tng) (tng) if (err >= 0 && __request_resource(root, new))
(tng) (tng) (tng) (tng) (tng) (tng) (tng) err = -EBUSY;
(tng) (tng) (tng) write_unlock(&resource_lock);
(tng) (tng) (tng) return err;
}
(tng)
3Q?Q? 获取资源的名U列?/p>
函数get_resource_list()用于获取根节点root的子资源名字列表。该函数主要用来支持/proc/文gpȝQ比如实现proc/ioports文g?proc/iomem文gQ。其源代码如下:(x)
int get_resource_list(struct resource *root, char *buf, int size)
{
(tng) (tng) (tng) (tng) (tng) (tng) (tng) char *fmt;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) int retval;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) fmt = " (tng) (tng) (tng) (tng) (tng) (tng) (tng) %08lx-%08lx : %s
";
(tng) (tng) (tng) (tng) (tng) (tng) (tng) if (root->end < 0x10000)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) fmt = " (tng) (tng) (tng) (tng) (tng) (tng) (tng) %04lx-%04lx : %s
";
(tng) (tng) (tng) (tng) (tng) (tng) (tng) read_lock(&resource_lock);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) retval = do_resource_list(root->child, fmt, 8, buf, buf + size) - buf;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) read_unlock(&resource_lock);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) return retval;
}
(tng)
可以看出Q该函数主要通过调用内部?rn)态函数do_resource_list()来实现其功能Q其源代码如下:(x)
/*
* This generates reports for /proc/ioports and /proc/iomem
*/
static char * do_resource_list(struct resource *entry, const char *fmt,
int offset, char *buf, char *end)
{
(tng) (tng) (tng) (tng) (tng) (tng) (tng) if (offset < 0)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) offset = 0;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) while (entry) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) const char *name = entry->name;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) unsigned long from, to;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if ((int) (end-buf) < 80)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) return buf;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) from = entry->start;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) to = entry->end;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (!name)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) name = "";
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) buf += sprintf(buf, fmt + offset, from, to, name);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (entry->child)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) buf = do_resource_list(entry->child, fmt, offset-2, buf, end);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) entry = entry->sibling;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) (tng) return buf;
}
(tng)
函数do_resource_list()主要通过一个while{}循环以及(qing)递归嵌套调用来实玎ͼ较ؓ(f)单,q里׃在详l解释了(jin)?/p>
3Q? 理I/O Region资源
Linux基?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O映射方式?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口和基于内存映方式的I/O端口资源l称为?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O区域”(I/O RegionQ?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O Region仍然是一U?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O资源Q因此它仍然可以用resourcel构cd来描q。下面我们就来看看Linux是如何管?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O Region的?/p>
3Q?Q? I/O Region的分?/p>
在函数__request_resource()的基上,Linux实现?jin)用于分?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O区域的函数__request_region()Q如?
struct resource * __request_region(struct resource *parent,
unsigned long start, unsigned long n, const char *name)
{
(tng) (tng) (tng) (tng) (tng) (tng) (tng) struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) if (res) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) memset(res, 0, sizeof(*res));
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) res->name = name;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) res->start = start;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) res->end = start + n - 1;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) res->flags = IORESOURCE_BUSY;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) write_lock(&resource_lock);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) for (;;) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) struct resource *conflict;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) conflict = __request_resource(parent, res);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (!conflict)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) break;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (conflict != parent) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) parent = conflict;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (!(conflict->flags & IORESOURCE_BUSY))
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) continue;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) /* Uhhuh, that didn't work out.. */
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) kfree(res);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) res = NULL;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) break;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) write_unlock(&resource_lock);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) (tng) return res;
}
(tng)
NOTEQ?/p>
①首先,调用kmallocQ)(j)函数在SLAB分配器缓存(sh)分配一个resourcel构?/p>
②然后,相应的根据参数值初始化所分配的resourcel构。注意!flags成员被初始化为IORESOURCE_BUSY?/p>
③接下来Q用一个for循环开始进行资源分配,循环体的步骤如下Q?/p>
l 首先Q调用__request_resource()函数q行资源分配。如果返回NULLQ说明分配成功,因此执行break语句推出for循环Q返回所分配的resourcel构的指针,函数成功地结束?/p>
l 如果__request_resource()函数分配不成功,则进一步判断所q回的冲H资源节Ҏ(gu)否就是父资源节点parent。如果不是,则将分配行ؓ(f)下降一个层ơ,卌囑֜当前冲突的资源节点中q行分配Q只有在冲突的资源节Ҏ(gu)有设|IORESOURCE_BUSY的情况下才可以)(j)Q于是让parent指针{于conflictQƈ在conflict->flags&IORESOURCE_BUSY?的情况下执行continue语句l箋(hu)for循环?/p>
l 否则如果相冲H的资源节点是父节点parentQ或者相冲突资源节点讄?jin)IORESOURCE_BUSY标志位,则宣告分配失败。于是调用kfreeQ)(j)函数释放所分配的resourcel构Qƈres指针|ؓ(f)NULLQ最后用break语句推出for循环?/p>
④最后,q回所分配的resourcel构的指针?/p>
3Q?Q? I/O Region的释?/p>
函数__release_region()实现在一个父资源节点parent中释攄定范围的I/O Region。实际上该函数的实现思想与__release_resource()相类伹{其源代码如下:(x)
void __release_region(struct resource *parent,
unsigned long start, unsigned long n)
{
(tng) (tng) (tng) (tng) (tng) (tng) (tng) struct resource **p;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) unsigned long end;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) p = &parent->child;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) end = start + n - 1;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) for (;;) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) struct resource *res = *p;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (!res)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) break;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (res->start <= start && res->end >= end) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (!(res->flags & IORESOURCE_BUSY)) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) p = &res->child;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) continue;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) if (res->start != start' (tng) 'res->end != end)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) break;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) *p = res->sibling;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) kfree(res);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) return;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) p = &res->sibling;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) }
(tng) (tng) (tng) (tng) (tng) (tng) (tng) printk("Trying to free nonexistent resource <%08lx-%08lx>
", start, end);
}
(tng)
cM圎ͼ该函C是通过一个for循环来遍历父资源parent的child链表。ؓ(f)此,它让指针res指向当前正被扫描的子资源节点Q指针p指向前一个子资源节点的sibling成员变量Qp的初始gؓ(f)指向parent->child。For循环体的步骤如下Q?/p>
①让res指针指向当前被扫描的子资源节点(resQ?pQ?/p>
②如果res指针为NULLQ说明已l扫描完整个child链表Q所以退出for循环?/p>
③如果res指针不ؓ(f)NULLQ则l箋(hu)看看所指定?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O区域范围是否完全包含在当前资源节点中Q也即看看[start,start+n-1]是否包含在res->[start,end]中。如果不属于Q则让p指向当前资源节点的sibling成员Q然后lfor循环。如果属于,则执行下列步骤:(x)
l 先看看当前资源节Ҏ(gu)否设|了(jin)IORESOURCE_BUSY标志位。如果没有设|该标志位,则说明该资源节点下面可能q(sh)(x)有子节点Q因此将扫描q程下降一个层ơ,于是修改p指针Q它指向res->childQ然后执行continue语句l箋(hu)for循环?/p>
l 如果讄?jin)IORESOURCE_BUSY标志位。则一定要保当前资源节点是所指定?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O区域Q然后将当前资源节点从其父资源的child链表中去除。这可以通过让前一个兄弟资源节点的sibling指针指向当前资源节点的下一个兄弟资源节Ҏ(gu)实现Q即?p=res->siblingQ,最后调用kfreeQ)(j)函数释放当前资源节点的resourcel构。然后函数就可以成功q回?jin)?/p>
3Q?Q? (g)查指定的I/O Region是否已被占用
函数__check_region()(g)查指定的I/O Region是否已被占用。其源代码如下:(x)
int __check_region(struct resource *parent, unsigned long start, unsigned long n)
{
(tng) (tng) (tng) (tng) (tng) (tng) (tng) struct resource * res;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) res = __request_region(parent, start, n, "check-region");
(tng) (tng) (tng) (tng) (tng) (tng) (tng) if (!res)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) return -EBUSY;
(tng) (tng) (tng) (tng) (tng) (tng) (tng) release_resource(res);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) kfree(res);
(tng) (tng) (tng) (tng) (tng) (tng) (tng) return 0;
}
(tng)
该函数的实现与__check_resource()的实现思想cM。首先,它通过调用__request_region()函数试图在父资源parent中分配指定的I/O Region。如果分配不成功Q将q回NULLQ因此此时函数返回错误|EBUSY表示所指定?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O Region已被占用。如果res指针不ؓ(f)I则说明所指定?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O Region没有被占用。于是调用__release_resource()函数刚刚分配的资源释放掉(实际上是resl构从parent的child链表去除Q,然后调用kfreeQ)(j)函数释放resl构所占用的内存。最后,q回0DC指定的I/O Region没有被占用?/p>
3Q? 理I/O端口资源
我们都知道,采用I/O映射方式的X86处理器ؓ(f)外设实现?jin)一个单独的地址I间Q也即?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/OI间”(I/O SpaceQ或UCؓ(f)?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口I间”,其大是64KBQ?x0000Q?xffffQ。Linux在其所支持的所有^C都实C(jin)?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口I间”这一概念?/p>
׃I/OI间非常,因此即外设ȝ有一个单独的I/O端口I间Q却也不是所有的外设都将?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口Q指寄存?/b>Q映到?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口I间”中。比如,大多数PCI卡都通过内存映射方式来将?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口或外讑ֆ存映到CPU的RAM物理地址I间中。而老式的ISA卡通常其I/O端口映射?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口I间中?/p>
Linux是基于?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O Region”这一概念来实现对I/O端口资源Q?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/OQmapped ?MemoryQmappedQ的理的?/p>
3Q?Q? 资源根节点的定义
Linux在kernel/Resource.c文g中定义了(jin)全局变量ioport_resource和iomem_resourceQ来分别描述ZI/O映射方式的整?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口I间和基于内存映方式的I/O内存资源I间Q包?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口和外讑ֆ存)(j)。其定义如下Q?/p>
struct resource ioport_resource =
{ "PCI IO", 0x0000, IO_SPACE_LIMIT, IORESOURCE_IO };
struct resource iomem_resource =
{ "PCI mem", 0x00000000, 0xffffffff, IORESOURCE_MEM };
(tng)
其中Q宏IO_SPACE_LIMIT表示整个I/OI间的大,对于X86q_而言Q它?xffffQ定义在include/asm-i386/io.h头文件中Q。显?dng)?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O内存I间的大是4GB?/p>
3Q?Q? ?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口I间的操?/p>
ZI/O Region的操作函数__XXX_region()QLinux在头文ginclude/linux/ioport.h中定义了(jin)三个?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口I间q行操作的宏Q①request_region()宏,h?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口I间中分配指定范围的I/O端口资源。②check_region()宏,(g)?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口I间中的指定I/O端口资源是否已被占用。③release_region()宏,释放I/O端口I间中的指定I/O端口资源。这三个宏的定义如下Q?/p>
#define request_region(start,n,name)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) __request_region(&ioport_resource, (start), (n), (name))
#define check_region(start,n)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) __check_region(&ioport_resource, (start), (n))
#define release_region(start,n)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) __release_region(&ioport_resource, (start), (n))
(tng)
其中Q宏参数start指定I/O端口资源的v始物理地址Q是I/O端口I间中的物理地址Q,宏参数n指定I/O端口资源的大?/p>
3Q?Q? ?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O内存资源的操?/p>
ZI/O Region的操作函数__XXX_region()QLinux在头文ginclude/linux/ioport.h中定义了(jin)三个?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O内存资源q行操作的宏Q①request_mem_region()宏,h分配指定?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O内存资源。②check_ mem_region()宏,(g)查指定的I/O内存资源是否已被占用。③release_ mem_region()宏,释放指定?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O内存资源。这三个宏的定义如下Q?/p>
#define request_mem_region(start,n,name)
__request_region(&iomem_resource, (start), (n), (name))
#define check_mem_region(start,n)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) __check_region(&iomem_resource, (start), (n))
#define release_mem_region(start,n)
(tng) (tng) (tng) (tng) (tng) (tng) (tng) __release_region(&iomem_resource, (start), (n))
(tng)
其中Q参数start?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O内存资源的v始物理地址Q是CPU的RAM物理地址I间中的物理地址Q,参数n指定I/O内存资源的大?/p>
3Q?Q? ?proc/ioports?proc/iomem的支?/p>
Linux在ioport.h头文件中定义?jin)两个宏Q?/p>
get_ioport_list()和get_iomem_list()Q分别用来实?proc/ioports文g?proc/iomem文g。其定义如下Q?/p>
#define get_ioport_list(buf) get_resource_list(&ioport_resource, buf, PAGE_SIZE)
#define get_mem_list(buf) (tng) (tng) (tng) (tng) (tng) (tng) (tng) get_resource_list(&iomem_resource, buf, PAGE_SIZE)
(tng)
3Q? 讉KI/O端口I间
在驱动程序请求了(jin)I/O端口I间中的端口资源后,它就可以通过CPU的IO指定来读写这?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口?jin)。在dI/O端口时要注意的一点就是,大多数^台都区分8位?6位和32位的端口Q也卌注意I/O端口的宽度?/p>
Linux在include/asm/io.h头文Ӟ对于i386q_是include/asm-i386/io.hQ中定义?jin)一pdd不同宽度I/O端口的宏函数。如下所C:(x)
⑴读?位宽?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口
unsigned char inbQunsigned portQ;
void outbQunsigned char valueQunsigned portQ;
(tng)
其中Qport参数指定I/O端口I间中的端口地址。在大多数^CQ如x86Q它都是unsigned shortcd的,其它的一些^C则是unsigned intcd的。显?dng)端口地址的类型是?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口I间的大来军_的?/p>
⑵读?6位宽?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口
unsigned short inwQunsigned portQ;
void outwQunsigned short valueQunsigned portQ;
(tng)
⑶读?2位宽?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口
unsigned int inlQunsigned portQ;
void outlQunsigned int valueQunsigned portQ;
(tng)
3Q?Q? ?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口的字W串操作
除了(jin)上述q些“单发”(singleQshotQ的I/O操作外,某些CPU也支持对某个I/O端口q行q箋(hu)的读写操作,也即对单?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口L写一pd字节、字?2位整敎ͼq就是所谓的“字W串I/O指o(h)”(String InstructionQ。这U指令在速度上显然要比用循环来实现同L(fng)功能要快得多?/p>
Linux同样在io.h文g中定义了(jin)字符?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/Od函数Q?/p>
?位宽的字W串I/O操作
void insbQunsigned portQvoid * addrQunsigned long countQ;
void outsbQunsigned port Qvoid * addrQunsigned long countQ;
(tng)
?6位宽的字W串I/O操作
void inswQunsigned portQvoid * addrQunsigned long countQ;
void outswQunsigned port Qvoid * addrQunsigned long countQ;
(tng)
?2位宽的字W串I/O操作
void inslQunsigned portQvoid * addrQunsigned long countQ;
void outslQunsigned port Qvoid * addrQunsigned long countQ;
(tng)
3Q?Q? Pausing I/O
在一些^CQ典型地如X86Q,对于老式ȝQ如ISAQ上的慢速外设来_(d)如果CPUd?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口的速度太快Q那可能会(x)发生丢失数据的现象。对于这个问题的解决Ҏ(gu)是在两ơ连l的I/O操作之间插入一D微的时gQ以便等待慢速外设。这是所谓的“Pausing I/O”?/p>
对于Pausing I/OQLinux也在io.h头文件中定义?jin)它?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/Od函数Q而且都以XXX_p命名Q比如:(x)inb_p()、outb_p(){等。下面我们就以out_p()Zq行分析?/p>
io.h中的宏定义__OUT(b,”b”char)展开后可得如下定义:(x)
extern inline void outb(unsigned char value, unsigned short port) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) __asm__ __volatile__ ("outb %" "b " "0,%" "w" "1"
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) : : "a" (value), "Nd" (port));
}
extern inline void outb_p(unsigned char value, unsigned short port) {
(tng) (tng) (tng) (tng) (tng) (tng) (tng) __asm__ __volatile__ ("outb %" "b " "0,%" "w" "1"
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) __FULL_SLOW_DOWN_IO
(tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) (tng) : : "a" (value), "Nd" (port));
}
(tng)
可以看出Qoutb_p()函数的实C被插入了(jin)宏__FULL_SLOWN_DOWN_IOQ以实现微小的g时。宏__FULL_SLOWN_DOWN_IO在头文gio.h中一开始就被定义:(x)
#ifdef SLOW_IO_BY_JUMPING
#define __SLOW_DOWN_IO "
jmp 1f
1: (tng) (tng) (tng) (tng) (tng) (tng) (tng) jmp 1f
1:"
#else
#define __SLOW_DOWN_IO "
outb %%al,$0x80"
#endif
#ifdef REALLY_SLOW_IO
#define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
__SLOW_DOWN_IO __SLOW_DOWN_IO __SLOW_DOWN_IO
#else
#define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
#endif
(tng)
昄Q__FULL_SLOW_DOWN_IO是一个或四个__SLOW_DOWN_IOQ根据是否定义了(jin)宏REALLY_SLOW_IO来决定)(j)Q而宏__SLOW_DOWN_IO则被定义成毫无意义的跌{语句或写端口0x80的操作(Ҏ(gu)是否定义?jin)宏SLOW_IO_BY_JUMPING来决定)(j)?/p>
3Q? 讉KI/O内存资源
管I/O端口I间曾一度在x86q_上被q泛使用Q但是由于它非常,因此大多数现代ȝ的设备都以内存映方式(MemoryQmappedQ来映射它的I/O端口Q指I/O寄存?/b>Q和外设内存。基于内存映方式的I/O端口Q指I/O寄存?/b>Q和外设内存可以通称为?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O内存”资源(I/O MemoryQ。因两者在g实现上的差异对于软g来说是完全透明的,所以驱动程序开发h员可以将内存映射方式?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O端口和外讑ֆ存统一看作是?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O内存”资源?/p>
从前几节的阐q我们知道,I/O内存资源是在CPU的单一内存物理地址I间内进行编址的,也即它和pȝRAM同处在一个物理地址I间内。因此通过CPU的访内指令就可以讉KI/O内存资源?/p>
一般来_(d)在系l运行时Q外讄I/O内存资源的物理地址是已知的Q这可以通过pȝZgQ如BIOSQ在启动时分配得刎ͼ或者通过讑֤的硬q线QhardwiredQ得到。比如,PCI卡的I/O内存资源的物理地址是在系l启动时由P(pn)CI BIOS分配q写到PCI卡的配置I间中的BAR中的。而ISA卡的I/O内存资源的物理地址则是通过讑֤连U映到640KBQ?MB范围之内的。但是CPU通常q没有ؓ(f)q些已知的外?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O内存资源的物理地址预定义虚拟地址范围Q因为它们是在系l启动后才已知的Q某U意义上讲是动态的Q,所以驱动程序ƈ不能直接通过物理地址讉KI/O内存资源Q而必d它们映射到核?j)虚地址I间内(通过表Q,然后才能Ҏ(gu)映射所得到的核?j)虚地址范围Q通过访内指o(h)讉Kq些I/O内存资源?/p>
3Q?Q? 映射I/O内存资源
Linux在io.h头文件中声明?jin)函数ioremapQ)(j)Q用来将I/O内存资源的物理地址映射到核?j)虚地址I间Q?GBQ?GBQ中Q如下:(x)
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
void iounmap(void * addr);
(tng)
函数用于取消ioremapQ)(j)所做的映射Q参数addr是指向核?j)虚地址的指针。这两个函数都是实现在mm/ioremap.c文g中。具体实现可参考《情景分析》一书?/p>
3Q?Q? dI/O内存资源
在将I/O内存资源的物理地址映射成核?j)虚地址后,理论上讲我们可以象dRAM那样直接dI/O内存资源?jin)。但是,׃在某些^CQ对I/O内存和系l内存有不同的访问处理,因此Z(jin)保跨^台的兼容性,Linux实现?jin)一pddI/O内存资源的函敎ͼq些函数在不同的q_上有不同的实现。但在x86q_上,dI/O内存?sh)读写RAM无Q何差别。如下所C(include/asm-i386/io.hQ:(x)
#define readb(addr) (*(volatile unsigned char *) __io_virt(addr))
#define readw(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 unsigned int *) __io_virt(addr) = (b))
#define memset_io(a,b,c) (tng) (tng) (tng) (tng) (tng) (tng) (tng) memset(__io_virt(a),(b),(c))
#define memcpy_fromio(a,b,c) memcpy((a),__io_virt(b),(c))
#define memcpy_toio(a,b,c) (tng) (tng) (tng) (tng) (tng) (tng) (tng) memcpy(__io_virt(a),(b),(c))
上述定义中的宏__io_virt()仅仅(g)查虚地址addr是否是核?j)空间中的虚地址。该宏在内核2.4.0中的实现是(f)时性的。具体的实现函数在arch/i386/lib/Iodebug.c文g?/p>
昄Q在x86q_上访?b style="COLOR: black; BACKGROUND-COLOR: #99ff99">I/O内存资源与访问系l主存RAM是无差别的。但是ؓ(f)?jin)保证驱动程序的跨^台的可移植性,我们应该使用上面的函数来讉KI/O内存资源Q而不应该通过指向核心(j)虚地址的指针来讉K?/p>