新聞中心

EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > IO端口和IO內(nèi)存的區(qū)別及分別使用的函數(shù)接口

IO端口和IO內(nèi)存的區(qū)別及分別使用的函數(shù)接口

作者: 時(shí)間:2016-11-23 來(lái)源:網(wǎng)絡(luò) 收藏

Linux系統(tǒng)對(duì)IO端口和IO內(nèi)存的管理

http://blog.csdn.net/ce123/article/details/7204458

一、I/O端口

端口(port)是接口電路中能被CPU直接訪問(wèn)的寄存器的地址。幾乎每一種外設(shè)都是通過(guò)讀寫(xiě)設(shè)備上的寄存器來(lái)進(jìn)行的。CPU通過(guò)這些地址即端口向接口電路中的寄存器發(fā)送命令,讀取狀態(tài)和傳送數(shù)據(jù)。外設(shè)寄存器也稱為“I/O端口”,通常包括:控制寄存器、狀態(tài)寄存器和數(shù)據(jù)寄存器三大類(lèi),而且一個(gè)外設(shè)的寄存器通常被連續(xù)地編址。

二、IO內(nèi)存

例如,在PC上可以插上一塊圖形卡,有2MB的存儲(chǔ)空間,甚至可能還帶有ROM,其中裝有可執(zhí)行代碼。

三、IO端口和IO內(nèi)存的區(qū)分及聯(lián)系

這兩者如何區(qū)分就涉及到硬件知識(shí),X86體系中,具有兩個(gè)地址空間:IO空間和內(nèi)存空間,而RISC指令系統(tǒng)的CPU(如ARM、PowerPC等)通常只實(shí)現(xiàn)一個(gè)物理地址空間,即內(nèi)存空間。
內(nèi)存空間:內(nèi)存地址尋址范圍,32位操作系統(tǒng)內(nèi)存空間為2的32次冪,即4G。
IO空間:X86特有的一個(gè)空間,與內(nèi)存空間彼此獨(dú)立的地址空間,32位X86有64K的IO空間。

IO端口:當(dāng)寄存器或內(nèi)存位于IO空間時(shí),稱為IO端口。一般寄存器也俗稱I/O端口,或者說(shuō)I/O ports,這個(gè)I/O端口可以被映射在Memory Space,也可以被映射在I/O Space。

IO內(nèi)存:當(dāng)寄存器或內(nèi)存位于內(nèi)存空間時(shí),稱為IO內(nèi)存。

四、外設(shè)IO端口物理地址的編址方式

CPU對(duì)外設(shè)IO端口物理地址的編址方式有兩種:一種是I/O映射方式(I/O-mapped),另一種是內(nèi)存映射方式(Memory-mapped)。而具體采用哪一種則取決于CPU的體系結(jié)構(gòu)。

1、統(tǒng)一編址

  RISC指令系統(tǒng)的CPU(如,PowerPC、m68k、ARM等)通常只實(shí)現(xiàn)一個(gè)物理地址空間(RAM)。在這種情況下,外設(shè)I/O端口的物理地址就被映射到CPU的單一物理地址空間中,而成為內(nèi)存的一部分。此時(shí),CPU可以象訪問(wèn)一個(gè)內(nèi)存單元那樣訪問(wèn)外設(shè)I/O端口,而不需要設(shè)立專(zhuān)門(mén)的外設(shè)I/O指令。

統(tǒng)一編址也稱為“I/O內(nèi)存”方式,外設(shè)寄存器位于“內(nèi)存空間”(很多外設(shè)有自己的內(nèi)存、緩沖區(qū),外設(shè)的寄存器和內(nèi)存統(tǒng)稱“I/O空間”)。

2、獨(dú)立編址

而另外一些體系結(jié)構(gòu)的CPU(典型地如X86)則為外設(shè)專(zhuān)門(mén)實(shí)現(xiàn)了一個(gè)單獨(dú)地地址空間,稱為“I/O地址空間”或者“I/O端口空間”。這是一個(gè)與CPU地RAM物理地址空間不同的地址空間,所有外設(shè)的I/O端口均在這一空間中進(jìn)行編址。CPU通過(guò)設(shè)立專(zhuān)門(mén)的I/O指令(如X86的IN和OUT指令)來(lái)訪問(wèn)這一空間中的地址單元(也即I/O端口)。與RAM物理地址空間相比,I/O地址空間通常都比較小,如x86 CPU的I/O空間就只有64KB(0-0xffff)。這是“I/O映射方式”的一個(gè)主要缺點(diǎn)。

獨(dú)立編址也稱為“I/O端口”方式,外設(shè)寄存器位于“I/O(地址)空間”。

3、優(yōu)缺點(diǎn)

獨(dú)立編址主要優(yōu)點(diǎn)是:
1)、I/O端口地址不占用存儲(chǔ)器空間;使用專(zhuān)門(mén)的I/O指令對(duì)端口進(jìn)行操作,I/O指令短,執(zhí)行速度快。
2)、并且由于專(zhuān)門(mén)I/O指令與存儲(chǔ)器訪問(wèn)指令有明顯的區(qū)別,使程序中I/O操作和存儲(chǔ)器操作層次清晰,程序的可讀性強(qiáng)。
3)、同時(shí),由于使用專(zhuān)門(mén)的I/O指令訪問(wèn)端口,并且I/O端口地址和存儲(chǔ)器地址是分開(kāi)的,故I/O端口地址和存儲(chǔ)器地址可以重疊,而不會(huì)相互混淆。
4)、譯碼電路比較簡(jiǎn)單(因?yàn)镮/0端口的地址空間一般較小,所用地址線也就較少)。
其缺點(diǎn)是:只能用專(zhuān)門(mén)的I/0指令,訪問(wèn)端口的方法不如訪問(wèn)存儲(chǔ)器的方法多。

統(tǒng)一編址優(yōu)點(diǎn):
1)、由于對(duì)I/O設(shè)備的訪問(wèn)是使用訪問(wèn)存儲(chǔ)器的指令,所以指令類(lèi)型多,功能齊全,這不僅使訪問(wèn)I/O端口可實(shí)現(xiàn)輸入/輸出操作,而且還可對(duì)端口內(nèi)容進(jìn)行算術(shù)邏輯運(yùn)算,移位等等;
2)、另外,能給端口有較大的編址空間,這對(duì)大型控制系統(tǒng)和數(shù)據(jù)通信系統(tǒng)是很有意義的。
這種方式的缺點(diǎn)是端口占用了存儲(chǔ)器的地址空間,使存儲(chǔ)器容量減小,另外指令長(zhǎng)度比專(zhuān)門(mén)I/O指令要長(zhǎng),因而執(zhí)行速度較慢。
究竟采用哪一種取決于系統(tǒng)的總體設(shè)計(jì)。在一個(gè)系統(tǒng)中也可以同時(shí)使用兩種方式,前提是首先要支持I/O獨(dú)立編址。Intel的x86微處理器都支持I/O 獨(dú)立編址,因?yàn)樗鼈兊闹噶钕到y(tǒng)中都有I/O指令,并設(shè)置了可以區(qū)分I/O訪問(wèn)和存儲(chǔ)器訪問(wèn)的控制信號(hào)引腳。而一些微處理器或單片機(jī),為了減少引腳,從而減 少芯片占用面積,不支持I/O獨(dú)立編址,只能采用存儲(chǔ)器統(tǒng)一編址。

五、Linux下訪問(wèn)IO端口

對(duì)于某一既定的系統(tǒng),它要么是獨(dú)立編址、要么是統(tǒng)一編址,具體采用哪一種則取決于CPU的體系結(jié)構(gòu)。 如,PowerPC、m68k等采用統(tǒng)一編址,而X86等則采用獨(dú)立編址,存在IO空間的概念。目前,大多數(shù)嵌入式微控制器如ARM、PowerPC等并不提供I/O空間,僅有內(nèi)存空間,可直接用地址、指針訪問(wèn)。但對(duì)于Linux內(nèi)核而言,它可能用于不同的CPU,所以它必須都要考慮這兩種方式,于是它采用一種新的方法,將基于I/O映射方式的或內(nèi)存映射方式的I/O端口通稱為“I/O區(qū)域”(I/O region),不論你采用哪種方式,都要先申請(qǐng)IO區(qū)域:request_resource(),結(jié)束時(shí)釋放它:release_resource()。

IO region是一種IO資源,因此它可以用resource結(jié)構(gòu)類(lèi)型來(lái)描述。

訪問(wèn)IO端口有2種途徑:I/O映射方式(I/O-mapped)、內(nèi)存映射方式(Memory-mapped)。前一種途徑不映射到內(nèi)存空間,直接使用 intb()/outb()之類(lèi)的函數(shù)來(lái)讀寫(xiě)IO端口;后一種MMIO是先把IO端口映射到IO內(nèi)存(“內(nèi)存空間”),再使用訪問(wèn)IO內(nèi)存的函數(shù)來(lái)訪問(wèn) IO端口。

1、I/O映射方式

直接使用IO端口操作函數(shù):在設(shè)備打開(kāi)或驅(qū)動(dòng)模塊被加載時(shí)申請(qǐng)IO端口區(qū)域,之后使用inb(),outb()等進(jìn)行端口訪問(wèn),最后在設(shè)備關(guān)閉或驅(qū)動(dòng)被卸載時(shí)釋放IO端口范圍。

in、out、ins和outs匯編語(yǔ)言指令都可以訪問(wèn)I/O端口。內(nèi)核中包含了以下輔助函數(shù)來(lái)簡(jiǎn)化這種訪問(wèn):

inb( )、inw( )、inl( )
分別從I/O端口讀取1、2或4個(gè)連續(xù)字節(jié)。后綴“b”、“w”、“l”分別代表一個(gè)字節(jié)(8位)、一個(gè)字(16位)以及一個(gè)長(zhǎng)整型(32位)。

inb_p( )、inw_p( )、inl_p( )
分別從I/O端口讀取1、2或4個(gè)連續(xù)字節(jié),然后執(zhí)行一條“啞元(dummy,即空指令)”指令使CPU暫停。

outb( )、outw( )、outl( )
分別向一個(gè)I/O端口寫(xiě)入1、2或4個(gè)連續(xù)字節(jié)。

outb_p( )、outw_p( )、outl_p( )
分別向一個(gè)I/O端口寫(xiě)入1、2或4個(gè)連續(xù)字節(jié),然后執(zhí)行一條“啞元”指令使CPU暫停。

insb( )、insw( )、insl( )
分別從I/O端口讀入以1、2或4個(gè)字節(jié)為一組的連續(xù)字節(jié)序列。字節(jié)序列的長(zhǎng)度由該函數(shù)的參數(shù)給出。

outsb( )、outsw( )、outsl( )
分別向I/O端口寫(xiě)入以1、2或4個(gè)字節(jié)為一組的連續(xù)字節(jié)序列。

流程如下:

雖然訪問(wèn)I/O端口非常簡(jiǎn)單,但是檢測(cè)哪些I/O端口已經(jīng)分配給I/O設(shè)備可能就不這么簡(jiǎn)單了,對(duì)基于ISA總線的系統(tǒng)來(lái)說(shuō)更是如此。通常,I/O設(shè)備驅(qū)動(dòng)程序?yàn)榱颂綔y(cè)硬件設(shè)備,需要盲目地向某一I/O端口寫(xiě)入數(shù)據(jù);但是,如果其他硬件設(shè)備已經(jīng)使用這個(gè)端口,那么系統(tǒng)就會(huì)崩潰。為了防止這種情況的發(fā)生,內(nèi)核必須使用“資源”來(lái)記錄分配給每個(gè)硬件設(shè)備的I/O端口。資源表示某個(gè)實(shí)體的一部分,這部分被互斥地分配給設(shè)備驅(qū)動(dòng)程序。在這里,資源表示I/O端口地址的一個(gè)范圍。每個(gè)資源對(duì)應(yīng)的信息存放在resource數(shù)據(jù)結(jié)構(gòu)中:

[plain]view plaincopy
  1. structresource{
  2. resource_size_tstart;//資源范圍的開(kāi)始
  3. resource_size_tend;//資源范圍的結(jié)束
  4. constchar*name;//資源擁有者的名字
  5. unsignedlongflags;//各種標(biāo)志
  6. structresource*parent,*sibling,*child;//指向資源樹(shù)中父親,兄弟和孩子的指針
  7. };


所有的同種資源都插入到一個(gè)樹(shù)型數(shù)據(jù)結(jié)構(gòu)(父親、兄弟和孩子)中;例如,表示I/O端口地址范圍的所有資源都包括在一個(gè)根節(jié)點(diǎn)為ioport_resource的樹(shù)中。節(jié)點(diǎn)的孩子被收集在一個(gè)鏈表中,其第一個(gè)元素由child指向。sibling字段指向鏈表中的下一個(gè)節(jié)點(diǎn)。

為什么使用樹(shù)?例如,考慮一下IDE硬盤(pán)接口所使用的I/O端口地址-比如說(shuō)從0xf000 到 0xf00f。那么,start字段為0xf000 且end 字段為0xf00f的這樣一個(gè)資源包含在樹(shù)中,控制器的常規(guī)名字存放在name字段中。但是,IDE設(shè)備驅(qū)動(dòng)程序需要記住另外的信息,也就是IDE鏈主盤(pán)使用0xf000 到0xf007的子范圍,從盤(pán)使用0xf008 到0xf00f的子范圍。為了做到這點(diǎn),設(shè)備驅(qū)動(dòng)程序把兩個(gè)子范圍對(duì)應(yīng)的孩子插入到從0xf000 到0xf00f的整個(gè)范圍對(duì)應(yīng)的資源下。一般來(lái)說(shuō),樹(shù)中的每個(gè)節(jié)點(diǎn)肯定相當(dāng)于父節(jié)點(diǎn)對(duì)應(yīng)范圍的一個(gè)子范圍。I/O端口資源樹(shù)(ioport_resource)的根節(jié)點(diǎn)跨越了整個(gè)I/O地址空間(從端口0到65535)。

任何設(shè)備驅(qū)動(dòng)程序都可以使用下面三個(gè)函數(shù),傳遞給它們的參數(shù)為資源樹(shù)的根節(jié)點(diǎn)和要插入的新資源數(shù)據(jù)結(jié)構(gòu)的地址:

request_resource( ) //把一個(gè)給定范圍分配給一個(gè)I/O設(shè)備。

allocate_resource( ) //在資源樹(shù)中尋找一個(gè)給定大小和排列方式的可用范圍;若存在,將這個(gè)范圍分配給一個(gè)I/O設(shè)備(主要由PCI設(shè)備驅(qū)動(dòng)程序使用,可以使用任意的端口號(hào)和主板上的內(nèi)存地址對(duì)其進(jìn)行配置)。

release_resource( ) //釋放以前分配給I/O設(shè)備的給定范圍。

內(nèi)核也為以上函數(shù)定義了一些應(yīng)用于I/O端口的快捷函數(shù):request_region( )分配I/O端口的給定范圍,release_region( )釋放以前分配給I/O端口的范圍。當(dāng)前分配給I/O設(shè)備的所有I/O地址的樹(shù)都可以從/proc/ioports文件中獲得。

2、內(nèi)存映射方式

將IO端口映射為內(nèi)存進(jìn)行訪問(wèn),在設(shè)備打開(kāi)或驅(qū)動(dòng)模塊被加載時(shí),申請(qǐng)IO端口區(qū)域并使用ioport_map()映射到內(nèi)存,之后使用IO內(nèi)存的函數(shù)進(jìn)行端口訪問(wèn),最后,在設(shè)備關(guān)閉或驅(qū)動(dòng)模塊被卸載時(shí)釋放IO端口并釋放映射。

映射函數(shù)的原型為:
void *ioport_map(unsigned long port, unsigned int count);
通過(guò)這個(gè)函數(shù),可以把port開(kāi)始的count個(gè)連續(xù)的I/O端口重映射為一段“內(nèi)存空間”。然后就可以在其返回的地址上像訪問(wèn)I/O內(nèi)存一樣訪問(wèn)這些I/O端口。但請(qǐng)注意,在進(jìn)行映射前,還必須通過(guò)request_region( )分配I/O端口。

當(dāng)不再需要這種映射時(shí),需要調(diào)用下面的函數(shù)來(lái)撤消:
void ioport_unmap(void *addr);

在設(shè)備的物理地址被映射到虛擬地址之后,盡管可以直接通過(guò)指針訪問(wèn)這些地址,但是宜使用Linux內(nèi)核的如下一組函數(shù)來(lái)完成訪問(wèn)I/O內(nèi)存:·讀I/O內(nèi)存
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
與上述函數(shù)對(duì)應(yīng)的較早版本的函數(shù)為(這些函數(shù)在Linux 2.6中仍然被支持):
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
·寫(xiě)I/O內(nèi)存
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
與上述函數(shù)對(duì)應(yīng)的較早版本的函數(shù)為(這些函數(shù)在Linux 2.6中仍然被支持):
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);

流程如下:

六、Linux下訪問(wèn)IO內(nèi)存

IO內(nèi)存的訪問(wèn)方法是:首先調(diào)用request_mem_region()申請(qǐng)資源,接著將寄存器地址通過(guò)ioremap()映射到內(nèi)核空間的虛擬地址,之后就可以Linux設(shè)備訪問(wèn)編程接口訪問(wèn)這些寄存器了,訪問(wèn)完成后,使用ioremap()對(duì)申請(qǐng)的虛擬地址進(jìn)行釋放,并釋放release_mem_region()申請(qǐng)的IO內(nèi)存資源。

struct resource *requset_mem_region(unsigned long start, unsigned long len,char *name);
這個(gè)函數(shù)從內(nèi)核申請(qǐng)len個(gè)內(nèi)存地址(在3G~4G之間的虛地址),而這里的start為I/O物理地址,name為設(shè)備的名稱。注意,。如果分配成功,則返回非NULL,否則,返回NULL。
另外,可以通過(guò)/proc/iomem查看系統(tǒng)給各種設(shè)備的內(nèi)存范圍。

要釋放所申請(qǐng)的I/O內(nèi)存,應(yīng)當(dāng)使用release_mem_region()函數(shù):
void release_mem_region(unsigned long start, unsigned long len)

申請(qǐng)一組I/O內(nèi)存后, 調(diào)用ioremap()函數(shù):
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
其中三個(gè)參數(shù)的含義為:
phys_addr:與requset_mem_region函數(shù)中參數(shù)start相同的I/O物理地址;
size:要映射的空間的大小;
flags:要映射的IO空間的和權(quán)限有關(guān)的標(biāo)志;

功能:將一個(gè)I/O地址空間映射到內(nèi)核的虛擬地址空間上(通過(guò)release_mem_region()申請(qǐng)到的)

流程如下:

六、ioremap和ioport_map

下面具體看一下ioport_map和ioport_umap的源碼:

[plain]view plaincopy
  1. void__iomem*ioport_map(unsignedlongport,unsignedintnr)
  2. {
  3. if(port>PIO_MASK)
  4. returnNULL;
  5. return(void__iomem*)(unsignedlong)(port+PIO_OFFSET);
  6. }
  7. voidioport_unmap(void__iomem*addr)
  8. {
  9. /*Nothingtodo*/
  10. }

ioport_map僅僅是將port加上PIO_OFFSET(64k),而ioport_unmap則什么都不做。這樣portio的64k空間就被映射到虛擬地址的64k~128k之間,而ioremap返回的虛擬地址則肯定在3G之上。ioport_map函數(shù)的目的是試圖提供與ioremap一致的虛擬地址空間。分析ioport_map()的源代碼可發(fā)現(xiàn),所謂的映射到內(nèi)存空間行為實(shí)際上是給開(kāi)發(fā)人員制造的一個(gè)“假象”,并沒(méi)有映射到內(nèi)核虛擬地址,僅僅是為了讓工程師可使用統(tǒng)一的I/O內(nèi)存訪問(wèn)接口ioread8/iowrite8(......)訪問(wèn)I/O端口。
最后來(lái)看一下ioread8的源碼,其實(shí)現(xiàn)也就是對(duì)虛擬地址進(jìn)行了判斷,以區(qū)分IO端口和IO內(nèi)存,然后分別使用inb/outb和readb/writeb來(lái)讀寫(xiě)。

[plain]view plaincopy
  1. unsignedintfastcallioread8(void__iomem*addr)
  2. {
  3. IO_COND(addr,returninb(port),returnreadb(addr));
  4. }
  5. #defineVERIFY_PIO(port)BUG_ON((port&~PIO_MASK)!=PIO_OFFSET)
  6. #defineIO_COND(addr,is_pio,is_mmio)do{
  7. unsignedlongport=(unsignedlong__force)addr;
  8. if(port
  9. VERIFY_PIO(port);
  10. port&=PIO_MASK;
  11. is_pio;
  12. }else{
  13. is_mmio;
  14. }
  15. }while(0)
  16. 展開(kāi):
  17. unsignedintfastcallioread8(void__iomem*addr)
  18. {
  19. unsignedlongport=(unsignedlong__force)addr;
  20. if(port<0x40000UL){
  21. BUG_ON((port&~PIO_MASK)!=PIO_OFFSET);
  22. port&=PIO_MASK;
  23. returninb(port);
  24. }else{
  25. returnreadb(addr);
  26. }
  27. }

七、總結(jié)

外設(shè)IO寄存器地址獨(dú)立編址的CPU,這時(shí)應(yīng)該稱外設(shè)IO寄存器為IO端口,訪問(wèn)IO寄存器可通過(guò)ioport_map將其映射到虛擬地址空間,但實(shí)際上這是給開(kāi)發(fā)人員制造的一個(gè)“假象”,并沒(méi)有映射到內(nèi)核虛擬地址,僅僅是為了可以使用和IO內(nèi)存一樣的接口訪問(wèn)IO寄存器;也可以直接使用in/out指令訪問(wèn)IO寄存器。

例如:Intel x86平臺(tái)普通使用了名為內(nèi)存映射(MMIO)的技術(shù),該技術(shù)是PCI規(guī)范的一部分,IO設(shè)備端口被映射到內(nèi)存空間,映射后,CPU訪問(wèn)IO端口就如同訪 問(wèn)內(nèi)存一樣。

外設(shè)IO寄存器地址統(tǒng)一編址的CPU,這時(shí)應(yīng)該稱外設(shè)IO寄存器為IO內(nèi)存,訪問(wèn)IO寄存器可通過(guò)ioremap將其映射到虛擬地址空間,然后再使用read/write接口訪問(wèn)。


上一頁(yè) 1 2 下一頁(yè)

評(píng)論


技術(shù)專(zhuān)區(qū)

關(guān)閉