天天看點

linux中的 IO端口映射和IO記憶體映射【轉】

CPU位址空間

(一)位址的概念

1)實體位址:CPU位址總線傳來的位址,由硬體電路控制其具體含義。實體位址中很大一部分是留給記憶體條中的記憶體的,但也常被映射到其他存儲器上 (如顯存、BIOS等)。在程式指令中的虛拟位址經過段映射和頁面映射後,就生成了實體位址,這個實體位址被放到CPU的位址線上。

        實體位址空間,一部分給實體RAM(記憶體)用,一部分給總線用,這是由硬體設計來決定的,是以在32 bits位址線的x86處理器中,實體位址空間是2的32次方,即4GB,但實體RAM一般不能上到4GB,因為還有一部分要給總線用(總線上還挂着别的 許多裝置)。在PC機中,一般是把低端實體位址給RAM用,高端實體位址給總線用。

2)總線位址:總線的位址線或在位址周期上産生的信号。外設使用的是總線位址,CPU使用的是實體位址。

        實體位址與總線位址之間的關系由系統的設計決定的。在x86平台上,實體位址就是總線位址,這是因為它們共享相同的位址空間——這句話有點難了解,詳見下 面的“獨立編址”。在其他平台上,可能需要轉換/映射。比如:CPU需要通路實體位址是0xfa000的單元,那麼在x86平台上,會産生一個PCI總線 上對0xfa000位址的通路。因為實體位址和總線位址相同,是以憑眼睛看是不能确定這個位址是用在哪兒的,它或者在記憶體中,或者是某個卡上的存儲單元, 甚至可能這個位址上沒有對應的存儲器。

3)虛拟位址:現代作業系統普遍采用虛拟記憶體管理(Virtual Memory Management)機制,這需要MMU(Memory Management Unit)的支援。MMU通常是CPU的一部分,如果處理器沒有MMU,或者有MMU但沒有啟用,CPU執行單元發出的記憶體位址将直接傳到晶片引腳上,被 記憶體晶片(實體記憶體)接收,這稱為實體位址(Physical Address),如果處理器啟用了MMU,CPU執行單元發出的記憶體位址将被MMU截獲,從CPU到MMU的位址稱為虛拟位址(Virtual Address),而MMU将這個位址翻譯成另一個位址發到CPU晶片的外部位址引腳上,也就是将虛拟位址映射成實體位址。

        Linux中,程序的4GB(虛拟)記憶體分為使用者空間、核心空間。使用者空間分布為0~3GB(即PAGE_OFFSET,在0X86中它等于0xC0000000),剩下的1G為核心空間。程式員隻能使用虛拟位址。系統中每個程序有各自的私有使用者空間(0~3G),這個空間對系統中的其他程序是不可見的。

        CPU發出取指令請求時的位址是目前上下文的虛拟位址,MMU再從頁表中找到這個虛拟位址的實體位址,完成取指。同樣讀取資料的也是虛拟位址,比如mov ax, var. 編譯時var就是一個虛拟位址,也是通過MMU從也表中來找到實體位址,再産生總線時序,完成取資料的。

(二)編址方式

1)外設都是通過讀寫裝置上的寄存器來進行的,外設寄存器也稱為“I/O端口”,而IO端口有兩種編址方式:獨立編址和統一編制。

        統一編址:外設接口中的IO寄存器(即IO端口)與主存單元一樣看待,每個端口占用一個存儲單元的位址,将主存的一部分劃出來用作IO位址空間,如,在 PDP-11中,把最高的4K主存作為IO裝置寄存器位址。端口占用了存儲器的位址空間,使存儲量容量減小。

        統一編址也稱為“I/O記憶體”方式,外設寄存器位于“記憶體空間”(很多外設有自己的記憶體、緩沖區,外設的寄存器和記憶體統稱“I/O空間”)。

        如,Samsung的S3C2440,是32位ARM處理器,它的4GB位址空間被外設、RAM等瓜分:

0x8000 1000    LED 8*8點陣的位址

0x4800 0000 ~ 0x6000 0000  SFR(特殊暫存器)位址空間

0x3800 1002   鍵盤位址

0x3000 0000 ~ 0x3400 0000  SDRAM空間 

0x2000 0020 ~ 0x2000 002e  IDE

0x1900 0300   CS8900

        獨立編址(單獨編址):IO位址與存儲位址分開獨立編址,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指令通路。PC架構一共有65536個8bit的I/O端口,組成64K個I/O位址空間,編号從 0~0xFFFF,有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 : dma1

0020-003f : pic1

0040-005f : timer

0060-006f : keyboard

0070-007f : rtc

0080-008f : dma page reg

00a0-00bf : pic2

00c0-00df : dma2

00f0-00ff : fpu

0170-0177 : ide1

……

        不過Intel x86平台普通使用了名為記憶體映射(MMIO)的技術,該技術是PCI規範的一部分,IO裝置端口被映射到記憶體空間,映射後,CPU通路IO端口就如同訪 問記憶體一樣。看Intel TA 719文檔給出的x86/x64系統典型記憶體位址配置設定表:

系統資源  占用

------------------------------------------------------------------------

BIOS  1M

本地APIC  4K

晶片組保留 2M

IO APIC  4K

PCI裝置  256M

PCI Express裝置 256M

PCI裝置(可選) 256M

顯示幀緩存 16M

TSEG  1M

        對于某一既定的系統,它要麼是獨立編址、要麼是統一編址,具體采用哪一種則取決于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()。

2)對外設的通路

1、通路I/O記憶體的流程是:request_mem_region() -> ioremap() -> ioread8()/iowrite8() -> iounmap() ->release_mem_region() 。

        前面說過,IO記憶體是統一編址下的概念,對于統一編址,IO位址空間是實體主存的一部分,對于程式設計而言,我們隻能操作虛拟記憶體,是以,通路的第一步就是要把裝置所處的實體位址映射到虛拟位址,Linux2.6下用ioremap():

        void *ioremap(unsigned long offset, unsigned long size);

然後,我們可以直接通過指針來通路這些位址,但是也可以用Linux核心的一組函數來讀寫:

ioread8(), iowrite16(), ioread8_rep(), iowrite8_rep()......

2、通路I/O端口

        通路IO端口有2種途徑:I/O映射方式(I/O-mapped)、記憶體映射方式(Memory-mapped)。前一種途徑不映射到記憶體空間,直接使用 intb()/outb()之類的函數來讀寫IO端口;後一種MMIO是先把IO端口映射到IO記憶體(“記憶體空間”),再使用通路IO記憶體的函數來通路 IO端口。

        void ioport_map(unsigned long port, unsigned int count);

通過這個函數,可以把port開始的count個連續的IO端口映射為一段“記憶體空間”,然後就可以在其傳回的位址是像通路IO記憶體一樣通路這些IO端口。

Linux下的IO端口和IO記憶體

CPU對外設端口實體位址的編址方式有兩種:一種是IO映射方式,另一種是記憶體映射方式。 

 Linux将基于IO映射方式的和記憶體映射方式的IO端口統稱為IO區域(IO region)。

  IO region仍然是一種IO資源,是以它仍然可以用resource結構類型來描述。

  Linux管理IO region:

  1) request_region()

  把一個給定區間的IO端口配置設定給一個IO裝置。

  2) check_region()

  檢查一個給定區間的IO端口是否空閑,或者其中一些是否已經配置設定給某個IO裝置。

  3) release_region()

  釋放以前配置設定給一個IO裝置的給定區間的IO端口。

  Linux中可以通過以下輔助函數來通路IO端口:

  inb(),inw(),inl(),outb(),outw(),outl()

  “b”“w”“l”分别代表8位,16位,32位。

 對IO記憶體資源的通路

  1) request_mem_region()

  請求配置設定指定的IO記憶體資源。

  2) check_mem_region()

  檢查指定的IO記憶體資源是否已被占用。

  3) release_mem_region()

  釋放指定的IO記憶體資源。

  其中傳給函數的start address參數是記憶體區的實體位址(以上函數參數表已省略)。

  驅動開發人員可以将記憶體映射方式的IO端口和外設記憶體統一看作是IO記憶體資源。

  ioremap()用來将IO資源的實體位址映射到核心虛位址空間(3GB - 4GB)中,參數addr是指向核心虛位址的指針。

  Linux中可以通過以下輔助函數來通路IO記憶體資源:

  readb(),readw(),readl(),writeb(),writew(),writel()。

  Linux在kernel/resource.c檔案中定義了全局變量ioport_resource和iomem_resource,來分别描述基于IO映射方式的整個IO端口空間和基于記憶體映射方式的IO記憶體資源空間(包括IO端口和外設記憶體)。

1)關于IO與記憶體空間:

    在X86處理器中存在着I/O空間的概念,I/O空間是相對于記憶體空間而言的,它通過特定的指令in、out來通路。端口号辨別了外設的寄存器位址。Intel文法的in、out指令格式為:

    IN 累加器, {端口号│DX}

    OUT {端口号│DX},累加器

    目前,大多數嵌入式微控制器如ARM、PowerPC等中并不提供I/O空間,而僅存在記憶體空間。記憶體空間可以直接通過位址、指針來通路,程式和程式運作中使用的變量和其他資料都存在于記憶體空間中。 

    即便是在X86處理器中,雖然提供了I/O空間,如果由我們自己設計電路闆,外設仍然可以隻挂接在記憶體空間。此時,CPU可以像通路一個記憶體單元那樣通路外設I/O端口,而不需要設立專門的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(unsigned 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 *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。

文章出處:飛諾網(www.diybl.com):http://www.diybl.com/course/6_system/linux/linuxjq/20110922/560798.html