① 驅動中操作物理絕對地址為什麼要先ioremap
void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags) 入口: phys_addr:要映射的起始的IO地址; size:要映射的空間的大小; flags:要映射的IO空間的和許可權有關的標志; 功能: 將一個IO地址空間映射到內核的虛擬地址空間上去,便於訪問; 實現:對要映射的IO地址空間進行判斷,低PCI/ISA地址不需要重新映射,也不允許用戶將IO地址空間映射到正在使用的RAM中,最後申請一 個 vm_area_struct結構,調用remap_area_pages填寫頁表,若填寫過程不成功則釋放申請的vm_area_struct空 間; 意義: 比如isa設備和pci設備,或者是fb,硬體的跳線或者是物理連接方式決定了硬體上的內存影射到的cpu物理地址。 在內核訪問這些地址必須分配給這段內存以虛擬地址,這正是__ioremap的意義所在 ,需要注意的是,物理內存已經"存在"了,無需alloc page給這段地址了. 文件中的注釋也是比較詳盡的,並且只 暴露了__ioremap,iounmap兩個函數供其他模 塊調用,函數remap_area_pte,remap_area_pmd,remap_area_pages只為__ioremap所用. -------- 為了使軟體訪問I/O內存,必須為設備分配虛擬地址.這就是ioremap的工作.這個函數專門用來為I/O內存區域分配虛擬地址(空間).對於直接映射的I/O地址ioremap不做任何事情(uClinux中是這么實現的??) 有了ioremap(和iounmap),設備就可以訪問任高搜何I/O內存空間,不論它是否直接映射到虛擬地址空間.但是戚讓歷,這些地址永遠不能直接使用(指物理地址),而要用readb這種函數. 根據計算機平台和所使用匯流排的不同,I/O 內存可能是,也可能不是通過頁表訪問的,通過頁表訪問的是統一編址(PowerPC),否則是獨立編址(Intel)。如果訪問是經由頁表進行的,內核必須首先安排物理地址使其對設備驅動 程序可見(這通常意味著在進行任何 I/O 之前必須先調用 ioremap)。如果訪問無需頁表,那麼 I/O 內存區域就很象 I/O 埠,可以使 用適當形式的函數讀寫它們。 不管訪問 I/O 內存時是否需要調用 ioremap,都不鼓勵直接使用指向 I/O 內存的指針。盡管(在「I/O 埠和 I/O 內存」 介紹過)I/O 內存在硬體一級是象普通 RAM 一樣定址的,但在「I/O 寄存器和常規內存」中描述過的那些需要額外小心的情況中已經建議不要使用普 通指針。相反,使用「包裝的」函數訪問 I/O 內存,一方面在所有平台上都是安全的,另一方面,在可以直接對指針指向的內存區域執行操作的時候,該函數 是經過優化的 ------- 自己原以為當給顯卡上的存儲空間分配了匯流排地址A以後,它所對應的虛擬空間就隨之確定了.也就是A+3G.可是事實上,在ioremap.c文件裡面的實現並不是這樣的.所用的函數是 __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)實現的時候是為從phys_addr開始的size大小的物理地址分配一塊虛擬地址.注意這里是分配,而不是指定.我所認為的分配應該是指定即根據phys_addr得到其所對應的滑含虛擬地址是phys_addr+3G. 本人認為一合理的解釋是這樣的:系統虛擬空間中映射的非IO卡上的地址空間滿足3G差關系,而IO卡上的 存儲空間就不滿足了.
② linux中ioremap和mmap的區別
你不是已經說了區別
ioremap是將物理地址轉換為虛擬地址
mmap是將設備內存線性地址映射到用戶地址空間
linux的線程只能訪問虛擬地址,不管是不是內核,ioremap應用,比如有個寄存器地址是0xe8000000
你要用ioremap映射後,才能訪問地址0xe8000000。這兩個地址是不同的,mmu會幫你搞定,對你是透明的
mmap在內核我用過ops中的mmap方法
我寫過一個常式,見附件。裡面還有用戶態的測試程序。
③ linux驅動中ioremap返回值是0是怎麼回事
#include #include #include int mian() { pid_t pid; int a=2; int ret=1; pid=fork(); if(pid==0) { return 2; } else if(pid>春侍0) { wait((void *)&ret); printf("return is:%d\乎兄n",ret); return 0; } else { printf("create process error!"歲森襲); ...
④ linux中VM_IOREMAP的作用
可以寫一個空函數放在min之前,比如
int TestForAc97include()
{
return 0;
}
如果變成TestForAc97報錯,說明問題出在ac97或者更靠前位置,如果還是min報錯,則是函數本身的問題。可以考慮把參數寫到一行里,然後函數內爛盯部都注釋掉,來看看問題出在函數名還是程序內部。鏈歷敏
看提示出在Ac97或者更前面的可能性比較大
另外,站長團上棚枝有產品團購,便宜有保證
⑤ linux設備樹gpio控制不了
linux設備樹gpio控制不了是linux設備樹不能直接控制gpio。根據查詢相關信息得知linux設備樹不能直接控制gpio,linux設備樹有旁遲兩個模式的用戶態和內核態,gpio操作只能在內核態咐團進行,而應用程序運行在用戶態。在內核空間控制gpio有兩種方法。
1、通過調用gpiolib的介面來衡啟橘控制gpio。
2、通過ioremap來控制gpio。
⑥ 初學Linux,linux中使用ioremap函數可以映射一個數組嗎
是的,你可以使用 ioremap() 函數來映射一個物理地址的連續區域,並將其映射到一個虛凱昌擬地址的連續區域,從而訪問整個寄存器組。在這種情況下,你可以將這個寄存器組看作是一個數組,通過訪問返回的虛擬首地址來訪問整個寄存器組。
下面是一個使用 ioremap() 函雹孫襪數映射一個物理地址連續區域的例子:
#define REG_ADDR_BASE 0x1000 // 寄存器組物理地址
#define REG_SIZE 0x100 // 寄存器組大小
void *virt_addr;
// 映射寄存器組物理地址到虛擬地址
virt_addr = ioremap(REG_ADDR_BASE, REG_SIZE);
// 訪問寄存器組
u32 reg_value = readl(virt_addr + offset); // 讀取偏移量為 offset 的寄存器值
writel(reg_value, virt_addr + offset); // 向偏移量為 offset 的寄存器寫入值
...
// 解除虛擬地址和物理地址的映射關系
ioremap_free(virt_addr);
在這個例子中,REG_ADDR_BASE 是寄存器組的起始物理地址,REG_SIZE 是寄存器組的大小。ioremap() 函數將這個物理地址區域映射到一個虛擬地址區域,並返回虛擬地址的首地址。通過對返回的虛擬地址加上偏移量,就可以訪問整個寄存器組了。最後,使用 ioremap_free() 函數來解除虛擬地址和物理地址的映射關源激系。
⑦ LINUX設備驅動程序如何與硬體通信
LINUX設備驅動程序是怎麼樣和硬體通信的?下面將由我帶大家來解答這個疑問吧,希望對大家有所收獲!
LINUX設備驅動程序與硬體設備之間的通信
設備驅動程序是軟體概念和硬體電路之間的一個抽象層,因此兩方面都要討論。到目前為止,我們已經討論詳細討論了軟體概念上的一些細節,現在討論另一方面,介紹驅動程序在Linux上如何在保持可移植性的前提下訪問I/O埠和I/O內存。
我們在需要示例的場合會使用簡單的數字I/O埠來講解I/O指令,並使用普通的幀緩沖區顯存來講解內存映射I/O。
I/O埠和I/O內存
計算機對每種外設都是通過讀寫它的寄存器進行控制的。大部分外設都有幾個寄存器,不管是在內存地址空間還是在I/O地址空間,這些寄存器的訪問地址都是連續的。
I/O埠就是I/O埠,設備會把寄存器映射到I/O埠,不管處理器是否具有獨立的I/O埠地址空間。即使沒有在訪問外設時也要模擬成讀寫I/O埠。
I/O內存是設備把寄存器映射到某個內存地址區段(如PCI設備)。這種I/O內存通常是首先方案,它不需要特殊的處理器指令,而且CPU核心訪問內存更有效率。
I/O寄存器和常規內存
盡管硬體寄存器和內存非常相似,但程序員在訪問I/O寄存器的時候必須注意避免由於CPU或編譯器不恰當的優化而改變預期的I/O動作。
I/O寄存器和RAM最主要的區別就是I/O操作具有邊際效應,而內存操作則沒有:由於內存沒有邊際效應,所以可以用多種 方法 進行優化,如使用高速緩存保存數值、重新排序讀/寫指令等。
編譯器能夠將數值緩存在CPU寄存器中而不寫入內存,即使儲存數據,讀寫操作也都能在高速緩存中進行而不用訪問物理RAM。無論是在編譯器一級或是硬體一級,指令的重新排序都有可能發生:一個指令序列如果以不同於程序文本中的次序運行常常能執行得更快。
在對常規內存進行這些優化的時候,優化過程是透明的,而且效果良好,但是對I/O操作來說這些優化很可能造成致命的錯誤,這是因為受到邊際效應的干擾,而這卻是驅動程序訪問I/O寄存器的主要目的。處理器無法預料某些 其它 進程(在另一個處理器上運行,或在在某個I/O控制器中發生的操作)是否會依賴於內存訪問的順序。編譯器或CPU可能會自作聰明地重新排序所要求的操作,結果會發生奇怪的錯誤,並且很難調度。因此,驅動程序必須確保不使用高速緩沖,並且在訪問寄存器時不發生讀或寫指令的重新排序。
由硬體自身引起的問題很解決:只要把底層硬體配置成(可以是自動的或是由Linux初始化代碼完成)在訪問I/O區域(不管是內存還是埠)時禁止硬體緩存即可。
由編譯器優化和硬體重新排序引起的問題的解決辦法是:對硬體(或其他處理器)必須以特定順序的操作之間設置內存屏障(memory barrier)。Linux提供了4個宏來解決所有可能的排序問題:
#include <linux/kernel.h>
void barrier(void)
這個函數通知編譯器插入一個內存屏障,但對硬體沒有影響。編譯後的代碼會把當前CPU寄存器中的所有修改過的數值保存到內存中,需要這些數據的時候再重新讀出來。對barrier的調用可避免在屏障前後的編譯器優化,但硬體完成自己的重新排序。
#include <asm/system.h>
void rmb(void);
void read_barrier_depends(void);
void wmb(void);
void mb(void);
這些函數在已編譯的指令流中插入硬體內存屏障;具體實現方法是平台相關的。rmb(讀內存屏障)保證了屏障之前的讀操作一定會在後來的讀操作之前完成。wmb保證寫操作不會亂序,mb指令保證了兩者都不會。這些函數都是barrier的超集。
void smp_rmb(void);
void smp_read_barrier_depends(void);
void smp_wmb(void);
void smp_mb(void);
上述屏障宏版本也插入硬體屏障,但僅僅在內核針對SMP系統編譯時有效;在單處理器系統上,它們均會被擴展為上面那些簡單的屏障調用。
設備驅動程序中使用內存屏障的典型形式如下:
writel(dev->registers.addr, io_destination_address);
writel(dev->registers.size, io_size);
writel(dev->registers.operation, DEV_READ);
wmb();
writel(dev->registers.control, DEV_GO);
在這個例子中,最重要的是要確保控制某種特定操作的所有設備寄存器一定要在操作開始之前已被正確設置。其中的內存屏障會強制寫操作以要求的順序完成。
因為內存屏障會影響系統性能,所以應該只用於真正需要的地方。不同類型的內存屏障對性能的影響也不盡相同,所以最好盡可能使用最符合需要的特定類型。
值得注意的是,大多數處理同步的內核原語,如自旋鎖和atomic_t操作,也能作為內存屏障使用。同時還需要注意,某些外設匯流排(比如PCI匯流排)存在自身的高速緩存問題,我們將在後面的章節中討論相關問題。
在某些體系架構上,允許把賦值語句和內存屏障進行合並以提高效率。內核提供了幾個執行這種合並的宏,在默認情況下,這些宏的定義如下:
#define set_mb(var, value) do {var = value; mb();} while 0
#define set_wmb(var, value) do {var = value; wmb();} while 0
#define set_rmb(var, value) do {var = value; rmb();} while 0
在適當的地方,<asm/system.h>中定義的這些宏可以利用體系架構特有的指令更快的完成任務。注意只有小部分體系架構定義了set_rmb宏。
使用I/O埠
I/O埠是驅動程序與許多設備之間的通信方式——至少在部分時間是這樣。本節講解了使用I/O埠的不同函數,另外也涉及到一些可移植性問題。
I/O埠分配
下面我們提供了一個注冊的介面,它允允許驅動程序聲明自己需要操作的埠:
#include <linux/ioport.h>
struct resource *request_region(unsigned long first, unsigned long n, const char *name);
它告訴內核,我們要使用起始於first的n個埠。name是設備的名稱。如果分配成功返回非NULL,如果失敗返回NULL。
所有分配的埠可從/proc/ioports中找到。如果我們無法分配到我們要的埠集合,則可以查看這個文件哪個驅動程序已經分配了這些埠。
如果不再使用這些埠,則用下面函數返回這些埠給系統:
void release_region(unsigned long start, unsigned long n);
下面函數允許驅動程序檢查給定的I/O埠是否可用:
int check_region(unsigned long first, unsigned long n);//不可用返回負的錯誤代碼
我們不贊成用這個函數,因為它返回成功並不能確保分配能夠成功,因為檢查和其後的分配並不是原子操作。我們應該始終使用request_region,因為這個函數執行了必要的鎖定,以確保分配過程以安全原子的方式完成。
操作I/O埠
當驅動程序請求了需要使用的I/O埠范圍後,必須讀取和/或寫入這些埠。為此,大多數硬體都會把8位、16位、32位區分開來。它們不能像訪問系統內存那樣混淆使用。
因此,C語言程序必須調用不同的函數訪問大小不同的埠。那些只支持映射的I/O寄存器的計算機體系架構通過把I/O埠地址重新映射到內存地址來偽裝埠I/O,並且為了易於移植,內核對驅動程序隱藏了這些細節。Linux內核頭文件中(在與體系架構相關的頭文件<asm/io.h>中)定義了如下一些訪問I/O埠的內聯函數:
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
位元組讀寫埠。
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
訪問16位埠
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
訪問32位埠
在用戶空間訪問I/O埠
上面這些函數主要是提供給設備驅動程序使用的,但它們也可以用戶空間使用,至少在PC類計算機上可以使用。GNU的C庫在<sys/io.h>中定義了這些函數。如果要要用戶空間使用inb及相關函數,則必須滿足正下面這些條件:
編譯程序時必須帶有-O選項來強制內聯函數的展開。
必須用ioperm(獲取單個埠的許可權)或iopl(獲取整個I/O空間)系統調用來獲取對埠進行I/O操作的許可權。這兩個函數都是x86平台特有的。
必須以root身份運行該程序才能調用ioperm或iopl。或者進程的祖先進程之一已經以root身份獲取對埠的訪問。
如果宿主平台沒有以上兩個系統調用,則用戶空間程序仍然可以使用/dev/port設備文件訪問I/O埠。不過要注意,該設備文件的含義與平台密切相關,並且除PC平台以處,它幾乎沒有什麼用處。
串操作
以上的I/O操作都是一次傳輸一個數據,作為補充,有些處理器實現了一次傳輸一個數據序列的特殊指令,序列中的數據單位可以是位元組、字、雙字。這些指令稱為串操作指令,它們執行這些任務時比一個C語言編寫的循環語句快得多。下面列出的宏實現了串I/O:
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);從內存addr開始連續讀/寫count數目的位元組。只對單一埠port讀取或寫入數據
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);對一個16位埠讀寫16位數據
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);對一個32位埠讀寫32位數據
在使用串I/O操作函數時,需要銘記的是:它們直接將位元組流從埠中讀取或寫入。因此,當埠和主機系統具有不同的位元組序時,將導致不可預期的結果。使用inw讀取埠將在必要時交換位元組,以便確保讀入的值匹配於主機的位元組序。然而,串函數不會完成這種交換。
暫停式I/O
在處理器試圖從匯流排上快速傳輸數據時,某些平台(特別是i386)就會出現問題。當處理器時鍾比外設時鍾(如ISA)快時就會出現問題,並且在設備板上特別慢時表現出來。為了防止出現丟失數據的情況,可以使用暫停式的I/O函數來取代通常的I/O函數,這些暫停式的I/O函數很像前面介紹的那些I/O函數,不同之處是它們的名字用_p結尾,如inb_p、outb_p等等。在linux支持的大多數平台上都定義了這些函數,不過它們常常擴展為非暫停式I/O同樣的代碼,因為如果不使用過時的外設匯流排就不需要額外的暫停。
平台相關性
I/O指令是與處理器密切相關的。因為它們的工作涉及到處理器移入移出數據的細節,所以隱藏平台間的差異非常困難。因此,大部分與I/O埠相關的源代碼都與平台相關。
回顧前面函數列表可以看到有一處不兼容的地方,即數據類型。函數的參數根據各平台體系架構上的不同要相應地使用不同的數據類型。例如,port參數在x86平台上(處理器只支持64KB的I/O空間)上定義為unsigned short,但在其他平台上定義為unsigned long,在這些平台上,埠是與內存在同一地址空間內的一些特定區域。
感興趣的讀者可以從io.h文件獲得更多信息,除了本章介紹的函數,一些與體系架構相關的函數有時也由該文件定義。
值得注意的是,x86家族之外的處理器都不為埠提供獨立的地址空間。
I/O操作在各個平台上執行的細節在對應平台的編程手冊中有詳細的敘述;也可以從web上下載這些手冊的PDF文件。
I/O埠示例
演示設備驅動程序的埠I/O的示例代碼運行於通用的數字I/O埠上,這種埠在大多數計算機平台上都能找到。
數字I/O埠最常見的一種形式是一個位元組寬度的I/O區域,它或者映射到內存,或者映射到埠。當把數字寫入到輸出區域時,輸出引腳上的電平信號隨著寫入的各位而發生相應變化。從輸入區域讀取到的數據則是輸入引腳各位當前的邏輯電平值。
這類I/O埠的具體實現和軟體介面是因系統而異的。大多數情況下,I/O引腳由兩個I/O區域控制的:一個區域中可以選擇用於輸入和輸出的引腳,另一個區域中可以讀寫實際的邏輯電平。不過有時情況簡單些,每個位不是輸入就是輸出(不過這種情況下就不能稱為“通用I/O"了);在所有個人計算機上都能找到的並口就是這樣的非通用的I/O埠。
並口簡介
並口的最小配置由3個8位埠組成。第一個埠是一個雙向的數據寄存器,它直接連接到物理連接器的2~9號引腳上。第二個埠是一個只讀的狀態寄存器;當並口連接列印機時,該寄存器 報告 列印機狀態,如是否是線、缺紙、正忙等等。第三個埠是一個只用於輸出的控制寄存器,它的作用之一是控制是否啟用中斷。
如下所示:並口的引腳
示例驅動程序
while(count--) {
outb(*(ptr++), port);
wmb();
}
使用I/O內存
除了x86上普遍使的I/O埠之外,和設備通信的另一種主要機制是通過使用映射到內存的寄存器或設備內存,這兩種都稱為I/O內存,因為寄存器和內存的差別對軟體是透明的。
I/O內存僅僅是類似RAM的一個區域,在那裡處理器可以通過匯流排訪問設備。這種內存有很多用途,比如存放視頻數據或乙太網數據包,也可以用來實現類似I/O埠的設備寄存器(也就是說,對它們的讀寫也存在邊際效應)。
根據計算機平台和所使用匯流排的不同,i/o內存可能是,也可能不是通過頁表訪問的。如果訪問是經由頁表進行的,內核必須首先安排物理地址使其對設備驅動程序可見(這通常意味著在進行任何I/O之前必須先調用ioremap)。如果訪問無需頁表,那麼I/O內存區域就非常類似於I/O埠,可以使用適當形式的函數讀取它們。
不管訪問I/O內存是否需要調用ioremap,都不鼓勵直接使用指向I/O內存的指針。相反使用包裝函數訪問I/O內存,這一方面在所有平台上都是安全的,另一方面,在可以直接對指針指向的內存區域執行操作的時候,這些函數是經過優化的。並且直接使用指針會影響程序的可移植性。
I/O內存分配和映射
在使用之前,必須首先分配I/O區域。分配內存區域的介面如下(在<linux/ioport.h>中定義):
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
該函數從start開始分配len位元組長的內存區域。如果成功返回非NULL,否則返回NULL值。所有的I/O內存分配情況可從/proc/iomem得到。
不再使用已分配的內存區域時,使用如下介面釋放:
void release_mem_region(unsigned long start, unsigned long len);
下面函數用來檢查給定的I/O內存區域是否可用的老函數:
int check_mem_region(unsigned long start, unsigned long len);//這個函數和check_region一樣不安全,應避免使用
分配內存之後我們還必須確保該I/O內存對內存而言是可訪問的。獲取I/O內存並不意味著可引用對應的指針;在許多系統上,I/O內存根本不能通過這種方式直接訪問。因此,我們必須由ioremap函數建立映射,ioremap專用於為I/O內存區域分配虛擬地址。
我們根據以下定義來調用ioremap函數:
#include <asm/io.h>
void *ioremap(unsigned long phys_addr, unsigned long size);
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);在大多數計算機平台上,該函數和ioremap相同:當所有I/O內存已屬於非緩存地址時,就沒有必要實現ioremap的獨立的,非緩沖版本。
void iounmap(void *addr);
記住,由ioremap返回的地址不應該直接引用,而應該使用內核提供的accessor函數。
訪問I/O內存
在某些平台上我們可以將ioremap的返回值直接當作指針使用。但是,這種使用不具有可移植性,訪問I/O內存的正確方法是通過一組專用於些目的的函數(在<asm/io.h>中定義)。
從I/O內存中讀取,可使用以下函數之一:
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
其中,addr是從ioremap獲得的地址(可能包含一個整數偏移量);返回值是從給定I/O內存讀取到的值。
寫入I/O內存的函數如下:
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
如果必須在給定的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);
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);
上述函數從給定的buf向給定的addr讀取或寫入count個值。count以被寫入數據的大小為單位。
上面函數均在給定的addr處執行所有的I/O操作,如果我們要在一塊I/O內存上執行操作,則可以使用下面的函數:
void memset_io(void *addr, u8 value, unsigned int count);
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);
上述函數和C函數庫的對應函數功能一致。
像I/O內存一樣使用I/O埠
某些硬體具有一種有趣的特性:某些版本使用I/O埠,而其他版本則使用I/O內存。導出給處理器的寄存器在兩種情況下都是一樣的,但訪問方法卻不同。為了讓處理這類硬體的驅動程序更加易於編寫,也為了最小化I/O埠和I/O內存訪問這間的表面區別,2.6內核引入了ioport_map函數:
void *ioport_map(unsigned long port, unsigned int count);
該函數重新映射count個I/O埠,使其看起來像I/O內存。此後,驅動程序可在該函數返回的地址上使用ioread8及其相關函數,這樣就不必理會I/O埠和I/O內存之間的區別了。
當不需要這種映射時使用下面函數一撤消:
void ioport_unmap(void *addr);
這些函數使得I/O埠看起來像內存。但需要注意的是,在重新映射之前,我們必須通過request_region來分配這些I/O埠。
為I/O內存重用short
前面介紹的short示例模塊訪問的是I/O埠,它也可以訪問I/O內存。為此必須在載入時通知它使用I/O內存,另外還要修改base地址以使其指向I/O區域。
下例是在MIPS開發板上點亮調試用的LED:
mips.root# ./short_load use_mem=1 base = 0xb7ffffc0
mips.root# echo -n 7 > /dev/short0
下面代碼是short寫入內存區域時使用的循環:
while(count--) {
iowrite8(*ptr++, address);
wmb();
}
1MB地址空間之下的ISA內存
最廣為人知的I/O內存區之一就是個人計算機上的ISA內存段。它的內存范圍在64KB(0xA0000)到1MB(0x100000)之間,因此它正好出現在常規系統RAM的中間。這種地址看上去有點奇怪,因為這個設計決策是20世紀80年代早期作出的,在當時看來沒有人會用到640KB以上的內存。
⑧ linux內核 io.h中的outb是哪裡的用法
這個用於io映射後寫單個字元,它並不是CPU的某個特殊指令,所以手冊查不到的。
它也不是匯編指令,一般是一個宏定義,所以匯滾神編也是查不到的。
linux內核可以調用ioremap把殲備緩io埠映射到一個虛擬內存地址,這樣可以把氏模io埠的讀寫操作統一為內存訪問。
ioremap之後,outb可以向映射後的地址寫一個位元組,其效果相當於對映射的io埠寫一個位元組。
⑨ 寫內存算是訪問內存嗎
當用戶訪問用戶空間的這段地址范圍時,實際是訪問設備內存。
在linux上電時,並不會為外設地址空間建立頁表。
但我們知道,linux訪問內存使用的都是虛擬地址,因此如果想訪問外設的寄存器(一般包括數據寄存器、控制寄存器與狀態寄存器),需要在驅動初始化中將外設所處的物理地址映射為虛擬地址,使用ioremap介面可以實現該功能。
ioremap & ioremap_nocache
ioremap和ioremap_nocache實現相同,使用場景為映射device memory類型內存。同時不使用cache(device memory本身就沒有cacheable這個屬性),即CPU的讀寫操作直接操作設備內存。
ioremap_cached
ioremap_cached用來映射memory type為normal memory的設備,同時使用cache,這會提高內存的訪問速度,提高系統的性能。
ioremap_wc & ioremap_wt
ioremap_wc用來映射memory type為normal memory的設備,同時不使用cache。
I/O內存訪問流程
request_mem_region
ioremap
rw
iounmap
release_mem_region
二、設備地址映射到用戶空間
一般情況下,用戶空間是不能夠直接訪高肢問設備的。mmap可實現這個功能。
mmap通過將設備內存映射到用戶空間的一段內存上,這樣,當用戶訪問用戶空間的這段地址范圍時,實際是訪問設備內存。這樣在每次訪問時,節省了用戶空間和內核空間的復制過程。
無論是普通文件還是設備文件,讀寫都是基於系統的虛擬文件系統介面,普通文件為了保護磁碟,避免頻繁讀寫,還引入帶緩沖頁機制,通過read/write/ioctl訪問文件時,都需經歷「用戶到內核」的內存拷貝過程,然後才將文件內容寫入磁碟。
通過mmap方法,將文件(包括設備文件)映射到用戶進程虛擬內存空間,代替read/write/ioctl的訪問方式,此時內存拷貝過程只有「用戶空間到虛擬內存空間」,省去了「用戶到內核」的拷貝過程,在數據量大的情況下能顯著提升讀寫效率。因此,mmap也稱為「零拷貝」(zero )技術。
caddr_t *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
fd為文件描述符,一般由open返回。fd也可指定為-1,並指定flags參數中的MAP_ANON,表示匿名映射。
length指映射的位元組數,從offset開始計算;
prot指定訪問許可權;
start指定文件被映射到用戶空間的起始地址,一般設為NULL,由內核指定改地址;
函數返回值為映射到用戶空間的地址。
1
2
3
4
5
1
2
3
4
5
mmap過程
1、在虛擬內存中查找一塊VMA
2、將這塊VMA進行映射
3、如果設備驅動程序或文件系統的file_operation定義了mmap介面,則調用它;
4、將VMA插入進程的VMA鏈表中
1
2
3
4
1
2
3
4
進程在映射空間的對共享內容的修改不會實時同步寫回到磁碟文件中,只有調用munmap()函數釋放映射後才會執行同步操作。mmap機制提供msync()函數,用於手動同步修改內容到磁碟源文件。
linux內核使用vm_area_struct結構來表示一個獨立的虛擬內存區域,由於每個不同質的虛擬內存區域功能和內部機制都不同,因此一個進程使用多個vm_area_struct結構來分別表示不同滾老類型的虛擬內存區域。各個vm_area_struct結構使用鏈表或者樹形結構鏈接,方便進程快速訪問,如下圖所示:
在這里插入圖片描述
vm_area_struct結構中包含區域起始和終止地址以及其他相關信息,同時也包含一個vm_ops指針,其內部可引出所有針對這個區域可以使用的系統調用函數。這樣,進程對某一虛擬內存區域的任何操作需要用要的信息,都可以從vm_area_struct中獲得。mmap函數就是要創建一個新的vm_area_struct結構,並將其與文件的物理磁碟地址相連。
三、devmem原理
「/dev/mem」設備
「/大念升dev/mem」是linux系統的一個虛擬字元設備,無論是標准linux系統還是嵌入式linux系統,都支持該設備。
「/dev/mem」設備是內核所有物理地址空間的全映像,這些地址包括:
物理內存(RAM)空間
物理存儲(ROM)空間
cpu匯流排地址
cpu寄存器地址
外設寄存器地址,GPIO、定時器、ADC
1
2
3
4
5
1
2
3
4
5
「/dev/mem」設備通常與「mmap」結合使用,可將指定內存映射到用戶空間。
類似的還有/dev/kmem設備,kernel看到的虛擬內存的全鏡像。可以用來訪問kernel的內容。
devmem命令原理
應用程序通過mmap函數實現對/dev/mem驅動中mmap方法的使用,映射了設備的內存到用戶空間,實現對這些物理地址的讀寫操作。
類似的有devkmem命令,通過mmap函數實現對/dev/kmem驅動中mmap方法的使用,映射了設備的內核空間到用戶空間,實現對這些物理地址的讀寫操作。
四、malloc原理
malloc的工作原理
可執行文件載入到內存中的時候,就給棧和堆劃分了固定大小的空間。使用vm_area_struct結構體指明了一個連續區域的頭地址和尾地址。
malloc函數分配內存主要是使用brk和mmap系統調用
brk(): 小於128k
在堆段分配malloc的內存,將堆頂的指針brk往上推;
mmap(): 大於128k
是在堆和棧之間(文件映射區域)找分配一塊空閑的虛擬內存,
1
2
3
4
1
2
3
4
malloc系統調用後,並沒有實際分配物理內存。
這時候讀虛擬內存地址,返回值是0;
第一次寫的時候,發生缺頁中斷,才會實際分配物理內存,建立虛擬內存與物理內存的映射關系。
缺頁中斷
malloc的空間沒有實際分配的情況下,在寫的時候會報缺頁中斷。實際上:
進程線性地址空間里的頁面不必常駐內存,在執行一條指令時,如果發現他要訪問的頁沒有在內存中(即存在位為0),那麼停止該指令的執行,並產生一個頁不存在的異常,對應的故障處理程序可通過從外存載入該頁的方法來排除故障,之後,原先引起的異常的指令就可以繼續執行,而不再產生異常。
當一個進程發生缺頁中斷的時候,進程會陷入內核態,執行以下操作:
1、檢查要訪問的虛擬地址是否合法
2、查找/分配一個物理頁
3、填充物理頁內容(讀取磁碟,或者直接置0,或者啥也不幹)
4、建立映射關系(虛擬地址到物理地址)
1
2
3
4
1
2
3
4
重新執行發生缺頁中斷的那條指令
如果第3步,需要讀取磁碟,那麼這次缺頁中斷就是majflt,否則就是minflt。
如何查看進程發生缺頁中斷的次數:
ps -o majflt,minflt -C program
majflt代表major fault,中文名叫大錯誤,minflt代表minor fault,中文名叫小錯誤。
這兩個數值表示一個進程自啟動以來所發生的缺頁中斷的次數。
malloc的free
前面知道,通過移動brk申請的內存,存放在進程的堆區域中。
free是由運行庫實現,它只是在已分配的堆塊前面加一個可用標志,並不實際釋放內存,不論是物理內存還是進程的堆空間。
在下次的malloc時,這塊空間可能被重用。
如果進程的堆空間出現較多的碎片(這是邏輯地址中的碎片),運行庫的堆管理常式會移動/合並碎片,此時可能會出現物理內存的釋放/重新分配。
而對於brk指針,只有它指向的那片內存被free的時候才會下移。比如先malloc了一個A,然後malloc了一個B。free掉A之後,brk是不會下移的;free掉B的時候brk才會下移。
⑩ linux驅動里ioremap對外設I/O資源進行映射有點糊塗
(AT91C_BASE_AC97C->AC97C_COTHR_VIR)
=
data;
你這是結構體嗎?應該是應該是基地址映射好,然後對其對應的寄存器賦值就可以,看不懂這些代碼表達的含義,像是給結構體指針賦值