最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

韋東山:Linux驅(qū)動程序基石之mmap(附視頻)

2020-06-03 16:16 作者:韋東山  | 我要投稿

應(yīng)用程序和驅(qū)動程序之間傳遞數(shù)據(jù)時,可以通過read、write函數(shù)進(jìn)行。這涉及在用戶態(tài)buffer和內(nèi)核態(tài)buffer之間傳數(shù)據(jù),如下圖所示:



應(yīng)用程序不能直接讀寫驅(qū)動程序中的buffer,需要在用戶態(tài)buffer和內(nèi)核態(tài)buffer之間進(jìn)行一次數(shù)據(jù)拷貝。這種方式在數(shù)據(jù)量比較小時沒什么問題;但是數(shù)據(jù)量比較大時效率就太低了。比如更新LCD顯示時,如果每次都讓APP傳遞一幀數(shù)據(jù)給內(nèi)核,假設(shè)LCD采用1024*600*32bpp的格式,一幀數(shù)據(jù)就有1024*600*32/8=2.3MB左右,這無法忍受。

改進(jìn)的方法就是讓程序可以直接讀寫驅(qū)動程序中的buffer,這可以通過mmap實(shí)現(xiàn)(memory map),把內(nèi)核的buffer映射到用戶態(tài),讓APP在用戶態(tài)直接讀寫。

1.內(nèi)存映射現(xiàn)象與數(shù)據(jù)結(jié)構(gòu)

假設(shè)有這樣的程序,名為test.c:

#include <stdio.h>

#include <unistd.h>

int a;

int main(int argc, char **argv)

{

? ?printf("enter a's value: \n");

? ?scanf("%d", &a);

? printf("a's address = 0x%x, a's value = %d\n", &a, a);

?while (1)

{

? ? sleep(10);

}

return 0;

}


在PC上如下編譯:

gcc? -o? test? test.c -static

在2個終端中分別執(zhí)行test程序,在第3個終端執(zhí)行ps -a,可以看到這2個程序同時存在,如下圖:

觀察到這些現(xiàn)象:

① 2個程序同時運(yùn)行,它們的變量a的地址都是一樣的:0x6d73c0;

② 2個程序同時運(yùn)行,它們的變量a的值是不一樣的,一個是12,另一個是123。


疑問來了:

① 這2個程序同時在內(nèi)存中運(yùn)行,它們在內(nèi)存中的地址肯定不同,比如變量a的地址肯定不同;

② 但是打印出來的變量a的地址卻是一樣的。

怎么回事?

這里要引入虛擬地址的概念:CPU發(fā)出的地址是虛擬地址,它經(jīng)過MMU(Memory Manage Unit,內(nèi)存管理單元)映射到物理地址上,對于不同進(jìn)程的同一個虛擬地址,MMU會把它們映射到不同的物理地址。如下圖:




當(dāng)前運(yùn)行的是app1時,MMU會把CPU發(fā)出的虛擬地址addr映射為物理地址paddr1,用paddr1去訪問內(nèi)存。

當(dāng)前運(yùn)行的是app2時,MMU會把CPU發(fā)出的虛擬地址addr映射為物理地址paddr2,用paddr2去訪問內(nèi)存。

MMU負(fù)責(zé)把虛擬地址映射為物理地址,虛擬地址映射到哪個物理地址去?映射關(guān)系保存在頁表中:



解析如下:

① 每個APP在內(nèi)核中都有一個task_struct結(jié)構(gòu)體,它用來描述一個進(jìn)程;

② 每個APP都要占據(jù)內(nèi)存,在task_struct中用mm_struct來管理進(jìn)程占用的內(nèi)存;

內(nèi)存在虛擬地址、物理地址,mm_struct中用mmap來描述虛擬地址,用pgd來描述對應(yīng)的物理地址。

注意:pgd,Page Global Directory,頁目錄。

③ 每個APP都有一系列的VMA:virtual memory

比如APP含有代碼段、數(shù)據(jù)段、BSS段、棧等等,還有共享庫。這些單元會保存在內(nèi)存里,它們的地址空間不同,權(quán)限不同(代碼段是只讀的可運(yùn)行的、數(shù)據(jù)段可讀可寫),內(nèi)核用一系列的vm_area_struct來描述它們。

vm_area_struct中的vm_start、vm_end是虛擬地址。

④ vm_area_struct中虛擬地址如何映射到物理地址去?

每一個APP的虛擬地址可能相同,物理地址不相同,這些對應(yīng)關(guān)系保存在pgd中。


2.ARM架構(gòu)內(nèi)存映射簡介

ARM架構(gòu)支持一級頁表映射,也就是說MMU根據(jù)CPU發(fā)來的虛擬地址可以找到第1個頁表,從第1個頁表里就可以知道這個虛擬地址對應(yīng)的物理地址。一級頁表里地址映射的最小單位是1M。

ARM架構(gòu)還支持二級頁表映射,也就是說MMU根據(jù)CPU發(fā)來的虛擬地址先找到第1個頁表,從第1個頁表里就可以知道第2級頁表在哪里;再取出第2級頁表,從第2個頁表里才能確定這個虛擬地址對應(yīng)的物理地址。二級頁表地址旺射的最小單位有4K、1K,Linux使用4K。

一級頁表項里的內(nèi)容,決定了它是指向一塊物理內(nèi)存,還是指問二級頁表,如下圖:



2.1, 一級頁表映射過程

一線頁表中每一個表項用來設(shè)置1M的空間,對于32位的系統(tǒng),虛擬地址空間有4G,4G/1M=4096。所以一級頁表要映射整個4G空間的話,需要4096個頁表項。

第0個頁表項用來表示虛擬地址第0個1M(虛擬地址為0~0x1FFFFF)對應(yīng)哪一塊物理內(nèi)存,并且有一些權(quán)限設(shè)置;

第1個頁表項用來表示虛擬地址第1個1M(虛擬地址為0x100000~0x2FFFFF)對應(yīng)哪一塊物理內(nèi)存,并且有一些權(quán)限設(shè)置;

依次類推。

使用一級頁表時,先在內(nèi)存里設(shè)置好各個頁表項,然后把頁表基地址告訴MMU,就可以加動MMU了。

以下圖為例介紹地址映射過程:

① CPU發(fā)出虛擬地址vaddr,假設(shè)為0x12345678

② MMU根據(jù)vaddr[31:20]找到一級頁表項:

虛擬地址0x12345678是虛擬地址空間里第0x123個1M,所以找到頁表里第0x123項,根據(jù)此項內(nèi)容知道它是一個段頁表項。

段內(nèi)偏移是0x45678。

③ 從這個表項里取出物理基地址:Section Base Address,假設(shè)是0x81000000

④ 物理基地址加上段內(nèi)偏移得到:0x81045678

所以CPU要訪問虛擬地址0x12345678時,實(shí)際上訪問的是0x81045678的物理地址





2.2, 二級頁表映射過程

首先設(shè)置好一級頁表、二級頁表,并且把一級頁表的首地址告訴MMU。

以下圖為例介紹地址映射過程:

① CPU發(fā)出虛擬地址vaddr,假設(shè)為0x12345678

② MMU根據(jù)vaddr[31:20]找到一級頁表項:

虛擬地址0x12345678是虛擬地址空間里第0x123個1M,所以找到頁表里第0x123項。根據(jù)此項內(nèi)容知道它是一個二級頁表項。

③ 從這個表項里取出地址,假設(shè)是address,這表示的是二級頁表項的物理地址;

④ vaddr[19:12]表示的是二級頁表項中的索引index即0x45,在二級頁表項中找到第0x45項;

⑤ 二級頁表項中含有頁基地址page base addr,假設(shè)是0x81889000:

它跟vaddr[11:0]組合得到物理地址:0x81889000 + 0x678 = 0x818678。

所以CPU要訪問虛擬地址0x12345678時,實(shí)際上訪問的是0x81889678的物理地址




3, 怎么給APP新建一塊內(nèi)存映射

3.1, mmap調(diào)用過程

從上面內(nèi)存映射的過程可以知道,要給APP端新開劈一塊虛擬內(nèi)存,并且讓它指向某塊內(nèi)核buffer,我們要做這些事:

① 得到一個vm_area_struct,它表示APP的一塊虛擬內(nèi)存空間;

很幸運(yùn),APP調(diào)用mmap系統(tǒng)函數(shù)時,內(nèi)核就幫我們構(gòu)造了一個vm_area_stuct結(jié)構(gòu)體。里面含有虛擬地址的地址范圍、權(quán)限。

② 確定物理地址:

你想映射某個內(nèi)核buffer,你需要得到它的物理地址,這得由你提供。

③ 給vm_area_struct和物理地址建立映射關(guān)系:

也很幸運(yùn),內(nèi)核提供有相關(guān)函數(shù)。

APP里調(diào)用mmap時,導(dǎo)致的內(nèi)核相關(guān)函數(shù)調(diào)用過程如下:




3.2 cache和buffer

本小節(jié)參考:

ARM的cache和寫緩沖器(write buffer)

ARM的cache和寫緩沖器(write buffer)blog.csdn.net

圖標(biāo)


使用mmap時,需要有cache、buffer的知識。下圖是CPU和內(nèi)存之間的關(guān)系,有cache、buffer(寫緩沖器)。Cache是一塊高速內(nèi)存;寫緩沖器相當(dāng)于一個FIFO,可以把多個寫操作集合起來一次寫入內(nèi)存。



程序運(yùn)行時有“局部性原理”,這又分為時間局部性、空間局部性。

① 時間局部性:

在某個時間點(diǎn)訪問了存儲器的特定位置,很可能在一小段時間里,會反復(fù)地訪問這個位置。

② 空間局部性:

訪問了存儲器的特定位置,很可能在不久的將來訪問它附近的位置。

而CPU的速度非常快,內(nèi)存的速度相對來說很慢。CPU要讀寫比較慢的內(nèi)存時,怎樣可以加快速度?根據(jù)“局部性原理”,可以引入cache。

① 讀取內(nèi)存addr處的數(shù)據(jù)時:

先看看cache中有沒有addr的數(shù)據(jù),如果有就直接從cache里返回數(shù)據(jù):這被稱為cache命中。

如果cache中沒有addr的數(shù)據(jù),則從內(nèi)存里把數(shù)據(jù)讀入,注意:它不是僅僅讀入一個數(shù)據(jù),而是讀入一行數(shù)據(jù)(cache line)。

而CPU很可能會再次用到這個addr的數(shù)據(jù),或是會用到它附近的數(shù)據(jù),這時就可以快速地從cache中獲得數(shù)據(jù)。

② 寫數(shù)據(jù):

CPU要寫數(shù)據(jù)時,可以直接寫內(nèi)存,這很慢;也可以先把數(shù)據(jù)寫入cache,這很快。

但是cache中的數(shù)據(jù)終究是要寫入內(nèi)存的啊,這有2種寫策略:

a. 寫通(write through):

數(shù)據(jù)要同時寫入cache和內(nèi)存,所以cache和內(nèi)存中的數(shù)據(jù)保持一致,但是它的效率很低。能改進(jìn)嗎?可以!使用“寫緩沖器”:cache大哥,你把數(shù)據(jù)給我就可以了,我來慢慢寫,保證幫你寫完。

有些寫緩沖器有“寫合并”的功能,比如CPU執(zhí)行了4條寫指令:寫第0、1、2、3個字節(jié),每次寫1字節(jié);寫緩沖器會把這4個寫操作合并成一個寫操作:寫word。對于內(nèi)存來說,這沒什么差別,但是對于硬件寄存器,這就有可能導(dǎo)致問題。

所以對于寄存器操作,不會啟動buffer功能;對于內(nèi)存操作,比如LCD的顯存,可以啟用buffer功能。

b. 寫回(write back):

新數(shù)據(jù)只是寫入cache,不會立刻寫入內(nèi)存,cache和內(nèi)存中的數(shù)據(jù)并不一致。

新數(shù)據(jù)寫入cache時,這一行cache被標(biāo)為“臟”(dirty);當(dāng)cache不夠用時,才需要把臟的數(shù)據(jù)寫入內(nèi)存。

使用寫回功能,可以大幅提高效率。但是要注意cache和內(nèi)存中的數(shù)據(jù)很可能不一致。這在很多時間要小心處理:比如CPU產(chǎn)生了新數(shù)據(jù),DMA把數(shù)據(jù)從內(nèi)存搬到網(wǎng)卡,這時候就要CPU執(zhí)行命令先把新數(shù)據(jù)從cache刷到內(nèi)存。反過來也是一樣的,DMA從網(wǎng)卡得過了新數(shù)據(jù)存在內(nèi)存里,CPU讀數(shù)據(jù)之前先把cache中的數(shù)據(jù)丟棄。

是否使用cache、是否使用buffer,就有4種組合(Linux內(nèi)核文件arch\arm\include\asm\pgtable-2level.h):

第1種是不使用cache也不使用buffer,讀寫時都直達(dá)硬件,這適合寄存器的讀寫。

第2種是不使用cache但是使用buffer,寫數(shù)據(jù)時會用buffer進(jìn)行優(yōu)化,可能會有“寫合并”,這適合顯存的操作。因?yàn)閷︼@存很少有讀操作,基本都是寫操作,而寫操作即使被“合并”也沒有關(guān)系。

第3種是使用cache不使用buffer,就是“write through”,適用于只讀設(shè)備:在讀數(shù)據(jù)時用cache加速,基本不需要寫。

第4種是既使用cache又使用buffer,適合一般的內(nèi)存讀寫。


3.3, 驅(qū)動程序要做的事

驅(qū)動程序要做的事情有3點(diǎn):

① 確定物理地址

② 確定屬性:是否使用cache、buffer

③ 建立映射關(guān)系

參考Linux源文件,示例代碼如下:


還有一個更簡單的函數(shù):


9.4 驅(qū)動編程

我們在驅(qū)動程序中申請一個8K的buffer,讓APP通過mmap能直接訪問。

① 使用哪一個函數(shù)分配內(nèi)存?

我們應(yīng)該使用kmalloc或kzalloc,這樣得到的內(nèi)存物理地址是連續(xù)的,在mmap時后APP才可以使用同一個基地址去訪問這塊內(nèi)存。(如果物理地址不連續(xù),就要執(zhí)行多次mmap了)。

關(guān)鍵代碼現(xiàn)場編寫,再完善文檔。

本文配套視頻:

mmap基礎(chǔ)知識:

【韋東山】韋東山升級版全系列嵌入式視頻_快速入門篇_嗶哩嗶哩 (゜-゜)つロ 干杯~-bilibiliwww.bilibili.com

mmap驅(qū)動編程:

【韋東山】韋東山升級版全系列嵌入式視頻_快速入門篇_嗶哩嗶哩 (゜-゜)つロ 干杯~-bilibiliwww.bilibili.com

韋東山:Linux驅(qū)動程序基石之mmap(附視頻)的評論 (共 條)

分享到微博請遵守國家法律
巴东县| 洛南县| 双牌县| 龙游县| 长武县| 莱西市| 凭祥市| 体育| 横峰县| 治县。| 哈巴河县| 本溪市| 通榆县| 金寨县| 成安县| 宜丰县| 固安县| 噶尔县| 耿马| 临夏县| 神木县| 黎川县| 凌海市| 宣威市| 福州市| 沂南县| 英吉沙县| 深水埗区| 晋宁县| 河源市| 东乌| 西充县| 宜阳县| 通渭县| 浮梁县| 习水县| 濮阳县| 金堂县| 宜昌市| 大庆市| 太仓市|