這樣理解mmap,挺有意思!
大概雍正皇帝怎么也不會想到,自己在西歷2022年的男生和女生眼里,會是截然不同的兩種形象。
1
以我對身邊同學(xué)朋友的觀察,男生們大多愛看《雍正王朝》,他們眼中的雍正,大約是個(gè)推行了“火耗歸公”、“攤丁入畝”等遏制貪腐,減輕稅收之類政策的改革家,是個(gè)經(jīng)歷了九子奪嫡的驚心動魄、腹黑深沉的政治家,是個(gè)登基后也兢兢業(yè)業(yè),熬夜加班996的工作狂。
而女生們大多愛看《甄嬛傳》,她們眼里的雍正,是“大胖橘”,是“大豬蹄子”,是被后宮一眾妃嬪玩弄于股掌之中,戴了N頂綠帽,最后還被鈕鈷祿甄嬛氣死的渣男。
我沒完整的看過甄嬛傳,但是有幸在吃飯的時(shí)候陪我家那位看過幾集。
正所謂后宮佳麗三千人,鐵杵磨成繡花針... (不是妃嬪太多了,皇帝就一個(gè),難免會互相爭風(fēng)吃醋。位份高的貴妃的仗勢欺人,一些小妃嬪無法正面回?fù)?,自然會用點(diǎn)別的奇淫技巧,扎小人就是其中一個(gè)出現(xiàn)頻率較高的方法。
據(jù)我總結(jié),扎小人這個(gè)技術(shù)的核心思想是:用戶這邊由于無法扎到正主,只能拿個(gè)自己身邊的布片等物品模擬一個(gè)小人出來,在上面畫上正主的經(jīng)筋脈絡(luò),寫上名字,施以某種魔法,然后用針扎自己手邊的小人的某個(gè)穴道,遠(yuǎn)程那位正主的對應(yīng)部位就會受到同樣的折磨。
雖然有點(diǎn)神乎其技,令人羨慕而不可得。但是在linux內(nèi)核開發(fā)里面,卻可以用mmap的機(jī)制實(shí)現(xiàn)類似的效果。
2
mmap的核心思想是:用戶這邊由于在用戶態(tài)無法直接操作寄存器的物理地址,于是通過mmap方法進(jìn)行內(nèi)存映射,將物理地址映射到用戶態(tài)的虛擬地址上,然后用戶通過讀寫自己手邊的虛擬地址,就可以實(shí)現(xiàn)對物理地址的讀取/寫入。
兩者的共同點(diǎn)是,由于無法直接操作目標(biāo),所以通過某種方法,將自己能操作的事物和目標(biāo)建立一種映射關(guān)系,從而達(dá)到如臂使指,指哪打哪,打哪哪疼的效果。
只要能建立起對目標(biāo)的映射,我們借此映射能做什么文章,自然有很多想象空間。所以mmap有很多用途,有人用它來實(shí)現(xiàn)進(jìn)程間通信,有人用它搬運(yùn)數(shù)據(jù),對于我們嵌入式工程師來說,我們可以用它來點(diǎn)燈。嘿嘿,想不到吧!
且聽我慢慢道來。
3
作為一個(gè)嵌入式工程師,花式點(diǎn)燈是必備技能。無論是寫裸機(jī)代碼操作GPIO口,還是通過物聯(lián)網(wǎng)云端遠(yuǎn)程控制LED,從硬件的角度講,核心原理都是找到連接LED的GPIO口,讓它輸出一個(gè)電信號。而從軟件的角度講,最終目的就是找到這個(gè)GPIO口對應(yīng)寄存器的地址,根據(jù)實(shí)際的電路要求,讓CPU給它寫入一個(gè)1或者0。
裸機(jī)開發(fā)的時(shí)候,我們可以直接找到物理地址進(jìn)行操作。而在Linux系統(tǒng)里卻略有不同。因?yàn)樵诓僮飨到y(tǒng)里有內(nèi)核空間的存在,我們寫的程序都是運(yùn)行在用戶態(tài)的,需要經(jīng)過內(nèi)核來對硬件進(jìn)行驅(qū)動,無法直接操作物理地址。
你當(dāng)然可以選擇為這個(gè)LED寫一個(gè)驅(qū)動,從而在用戶空間通過read,write來操作它的狀態(tài)。不過有些同學(xué)一聽要寫驅(qū)動,就想吟一首蜀道難來表達(dá)自己的望而卻步。所以沒了解過驅(qū)動的同學(xué)你也可以選擇用一種更直接的方式:mmap。
就好像你可以選擇給貴妃下藥來控制她,不過下藥這種方式需要精通藥理、掌控時(shí)機(jī),成本較高,難度較大。只要能達(dá)到目的,有時(shí)候扎個(gè)小人或許更加經(jīng)濟(jì)適用。
我在上家公司的時(shí)候用ARM Cortex-A9芯片做過一個(gè)項(xiàng)目,開發(fā)過程大概是先和硬件同事約定好一個(gè)協(xié)議,然后我通過GPIO口的輸入輸出模擬出這個(gè)協(xié)議,通過它對寄存器進(jìn)行讀寫配置,驅(qū)動硬件ADC采樣,然后將采回來的數(shù)據(jù)通過DMA傳輸,最終到應(yīng)用層進(jìn)行分析處理。其中驅(qū)動GPIO口的部分就用了mmap。
項(xiàng)目很大,做了半年多,想完全講明白也不現(xiàn)實(shí),不過我們可以從驅(qū)動GPIO口這個(gè)點(diǎn)切入,體會一下軟件驅(qū)動硬件中間這玄妙的過程。聰明的你一定可以舉一反三。
4
作為一個(gè)軟件工程師,拿到板子的時(shí)候,硬件工程師一般會給你一份文檔,類似這樣:
這個(gè)文檔會指明,如果想操作這個(gè)GPIO口的話,你需要用GPIO外設(shè)的基地址加上偏移地址找到對應(yīng)的寄存器地址,再用位操作給指定的bit寫入命令。
不過我由于FPGA也會一些,所以我們公司里FPGA的Block Diagram都是我來建的。建好FPGA的硬件工程后做一下綜合,從Address Map里就能看到我想用的GPIO口地址了。如圖:
無論怎樣,你現(xiàn)在拿到這個(gè)所謂的硬件寄存器地址了,接下來我們就可以拿小人扎它了。
以上圖我拿到的0x43C00000為例,這是寄存器的地址,那我能否直接在應(yīng)用程序里把0x43C00000賦值給一個(gè)指針,然后對它進(jìn)行讀寫呢?
在玩裸機(jī)的時(shí)候確實(shí)是這樣的。但是上面說了,Linux系統(tǒng)有虛擬內(nèi)存的存在,就不能這么做了。因?yàn)槔碚撋衔铱梢栽谙到y(tǒng)里開100個(gè)進(jìn)程,這100個(gè)進(jìn)程里都有0x43C00000這個(gè)地址,那這100個(gè)地址哪個(gè)是真正的寄存器地址呢?可能都不是。因?yàn)檫M(jìn)程里的0x43C00000是虛擬的,它真正對應(yīng)的物理地址在哪里,沒人知道。要想把虛擬地址和物理地址對應(yīng)起來,就得用mmap進(jìn)行內(nèi)存映射。
5
mmap的函數(shù)接口定義如下:
void mmap(void addr,size_t length,
int prot,int flags,
int fd,off_t offset);
這里面參數(shù)比較多。其中addr一般指定為NULL,prot則用于設(shè)置映射區(qū)域的權(quán)限,比如是否可讀可寫;flags則用于指定是共享映射還是私有映射;而fd,offset,length這三個(gè)參數(shù)表示將fd對應(yīng)的文件,從offset位置起,將長度為length的內(nèi)容映射到進(jìn)程的地址空間。
需要注意mmap的操作單元是頁,即最后映射的offset參數(shù)必須是內(nèi)存頁大小的整數(shù)倍,而Linux系統(tǒng)內(nèi)存頁大小一般為4096字節(jié)。
一個(gè)我在程序中的調(diào)用示例:
#define AXI_GPIO_BASEADDR 0x43C00000
int memfd = open("/dev/mem", O_RDWR
| O_SYNC);
if (-1 == memfd) {
printf("Can't open /dev/mem\n");
return -1;
}
unsigned int* led_gpio =
(unsigned int*)(mmap(
NULL, MMAP_SIZE,
PROT_READ | PROT_WRITE,
MAP_SHARED, memfd,
AXI_GPIO_BASEADDR));
調(diào)用mmap后,我們拿到一個(gè)指針,通過這個(gè)指針對指向的地址做任何操作,對應(yīng)的寄存器物理地址也會有相同的效果。于是我們將它循環(huán)賦值0101,相應(yīng)的寄存器控制的GPIO口輸出電信號,于是板卡上的燈成功的閃爍起來,類似奧特曼體力不支時(shí)的能量燈。
6
多說兩句,除了用來操作GPIO/字符設(shè)備外,mmap還有個(gè)常用的場景是操作塊設(shè)備。它和傳統(tǒng)的用read,write的區(qū)別,最關(guān)鍵的是省一次拷貝。
比如要讀取磁盤上某個(gè)文件的數(shù)據(jù),用read write的話,由于會涉及到系統(tǒng)調(diào)用,進(jìn)程是無法直接訪問內(nèi)核的,所以在read系統(tǒng)調(diào)用返回前,內(nèi)核需要將數(shù)據(jù)從內(nèi)核復(fù)制到進(jìn)程指定的buffer里。
但如果用mmap的話,那么這段數(shù)據(jù)會首先拷貝到內(nèi)存中作為頁緩存(即page cache)。用mmap將這段內(nèi)存映射到用戶空間,則進(jìn)程可以通過指針直接讀寫page cache,不再需要多余的系統(tǒng)調(diào)用和內(nèi)存拷貝。
不過雖然少了一次拷貝,但mmap會觸發(fā)缺頁中斷(page fault),相比于內(nèi)存拷貝而言,缺頁中斷的開銷更大。所以性能而言mmap大部分情況下并不會比read/write要好。
說到頁緩存,我在上家公司開發(fā)項(xiàng)目的時(shí)候,還被臟頁延遲這玩意坑過。篇幅所限,頁緩存涉及到的缺頁中斷,臟頁,延遲寫回,sync強(qiáng)制寫回等內(nèi)容,我們下次再詳細(xì)聊聊。
7
好了,于是我們學(xué)會用mmap點(diǎn)亮一個(gè)燈了。想象一下接下來的場景:
你跟公司研發(fā)部最漂亮的女同事說,
“hi,領(lǐng)導(dǎo)那邊給了我們組一個(gè)新任務(wù),你寫個(gè)驅(qū)動控制一下LED吧?”
“???驅(qū)動這么難,我不會啦”
“哦沒事,那你用mmap吧”
“誒你怎么罵人呢!”