當前位置:首頁 » 操作系統 » linux內存映射文件

linux內存映射文件

發布時間: 2022-06-27 08:28:35

1. linux內核中虛擬內存是怎樣映射到物理內存

當程序在運行的時候,會檢測到數據在虛擬內存中,並沒在物理內存中,這時候會產生一個缺頁中斷,
有缺頁中斷來映射。

2. Linux關於地址空間和MMAP映射有何特點

Linux採用
虛擬
內存技術,系統中的所有進程之間以虛擬方式共享內存。對每個進程來說,它們好像都可以訪問整個系統的所有物理內存。更重要的是,即使單獨一個進程,它擁有的地址空間也可以遠遠大於系統物理內存。
進程地址空間由每個進程中的線性地址區組成,每個進程都有一個32位或64位的平坦(flat)空間,空間的具體大小取決於體系結構。「平坦」指地址空間范圍是一個獨立的連續區間。通常情況下,每個進程都有唯一的這種平坦空間,而且每個進程的地址空間之間彼此互不相干。兩個不同的進程可以在它們各自地址空間的相同地址內存存放不同的數據。但是進程之間也可以選擇共享地址空間,我們稱這樣的進程為線程。
在地址空間中,我們更為關心的是進程有權訪問的虛擬內存地址區間,比如08048000~0804c000。這些可被訪問的合法地址區間被成為內存區域(memory area),通過內核,進程可以給自己的地址空間動態地添加或減少內存區域。
進程只能訪問有效范圍內的內存地址。每個內存區域也具有相應進程必須遵循的特定訪問屬性,如只讀、只寫、可執行等屬性。如果一個進程訪問了不在有效范圍中的地址,或以不正確的方式訪問有效地址,那麼內核就會終止該進程,並返回「段錯誤」信息。
?
內存區域可以包含各種內存對象,如下:
?
可執行文件代碼的內存映射,成為代碼段(text section)。
?
可執行文件的已初始化全局變數的內存映射,成為數據段(data section)。
?
包含未初始化全局變數的零頁(也就是bss段)的內存映射。零頁是指頁面中的數據全部為0。
?
用於進程用戶空間棧的零頁的內存映射。
?
每一個諸如C庫或動態鏈接程序等共享庫的代碼段、數據段和bss也會被載入進程的地址空間。
?
任何內存映射文件。
?
任何共享內存段。
?
任何匿名的內存映射,比如由malloc()分配的內存。
進程地址空間的任何有效地址都只能位於唯一的區域,這些內存區域不能相互覆蓋。可以看到,在執行的進程中,每個不同的內存片斷都對應一個獨立的內存區域:棧、對象代碼、全局變數、被映射的文件等等。
內核使用內存描述符表示進程的地址空間。內存描述符由mm_struct結構體表示,定義在文件中,該結構包含了和進程地址空間有關的全部信息。
VMA
內存區域由vm_area_struct結構體描述,定義在文件中,內存區域在內核中也經常被稱作虛擬內存區域或者VMA。
VMA標志是一種位標志,它定義在vm_area_struct結構中(該結構中的vm_flags子域)。和物理頁的訪問許可權不同,VMA標志反映了內核處理頁面索需要遵守的行為准則,而不是硬體要求。VM_IO標志內存區域中包含對設備I/O空間的映射。該標志通常在設備驅動程序執行 mmap()函數進行I/O空間映射時才被設置,同時該標志也表示該內存區域不能被包含在任何進程的存放轉存(core mp)中。VM_RESERVED標志內存區域不能被換出,它也是在設備驅動程序進行映射時被設置。
vm_area_struct結構體中的vm_ops域指向與指定內存區域相關的操作函數表,內核使用表中的方法操作VMA。
mmap()和do_mmap():創建地址區間
內核使用do_mmap()函數創建一個新的線性地址區間。但是說給函數創建一個新VMA並不非常准確,因為如果創建的地址區間和一個已經存在的地址區間相鄰,並且它們具有相同的訪問許可權的話,那麼兩個區間將合並為一個。如果不能合並,那麼就確實需要創建一個新的VMA了。但無論哪種情況,do_mmap()函數都會將一個地址區間加入到進程的地址空間中——無論是擴展已經存在的內存區域還是創建一個新的區域。
do_mmap()函數聲明在文件中,原型如下:
unsigned long do_mmap(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flag, unsigned long offset)
在用戶空間可以通過mmap()函數調用獲取內核函數do_mmap()的功能。mmap()系統調用原型如下:
void *mmap2(void *start, size_t length,
int prot, int flags,
int fd, off_t pgoff)
do_munmap()函數從特定的進程地址空間中刪除指定地址區間,該函數在文件中聲明:
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
系統調用munmap()給用戶空間程序提供了一種從自身地址空間中刪除指定地址區間的方法,它和系統調用mmap()的作用相反:
int munmap(void *start, size_t length)
mmap設備操作
對於驅動程序來說,內存映射可以提供給用戶程序直接訪問設備內存的能力。映射一個設備,意味著使用戶空間的一段地址關聯到設備內存上。無論何時,只要程序在分配的地址范圍內進行讀取或者寫入,實際上就是對設備的訪問。
並不是所有的設備都能進行mmap抽象。例如,串口設備和其他面向流的設備就無法實現這種抽象。mmap的另一個限制是映射都是以 PAGE_SIZE為單位的。內核只能在頁表一級處理虛擬地址;因此,被映射的區域必須是PAGE_SIZE的整數倍,而且必須位於起始於 PAGE_SIZE整數倍地址的物理內存內。如果區域的大小不是頁大小的整數倍,內核就通過生成一個稍微大一些的區域來容納它。
mmap方法是file_operations結構中的一員,並且在執行mmap系統調用時就會調用該方法。在調用實際方法之前,內核會完成很多工作,而且該方法的原型與系統調用的原型由很大區別。關於Linux命令的介紹,看看《linux就該這么學》,具體關於這一章地址3w(dot)linuxprobe/chapter-02(dot)html
文件操作聲明如下:
int (*mmap) (struct file * filp, struct vm_area_struct *vma);
其中vma參數包含了用於訪問設備的虛擬地址區間的信息。大部分工作已經由內核完成了,要實現mmap,驅動程序只要為這一地址范圍構造合適的頁表即可,如果需要的話,就用一個新的操作集替換vma->vm_ops。
有兩種建立頁表的方法:使用remap_page_range函數可一次建立所有的頁表,或者通過nopage VMA方法每次建立一個頁表。
構造用於映射一段物理地址的新頁表的工作是由remap_page_range完

3. 嵌入式 linux 的 內存映射 是個什麼過程具體什麼意思什麼功能一定採納

我的理解:
將A內存 隱射到B內存。
那麼對對B的讀寫,將直接反饋到A上。
我所接觸到的,Linux平台下,對設備寄存器的讀寫,用到內存映射。

4. linux中使用了什麼內存管理方法,為什麼

「事實勝於雄辯」,我們用一個小例子(原形取自《User-Level Memory Management》)來展示上面所講的各種內存區的差別與位置。

進程的地址空間對應的描述結構是「內存描述符結構」,它表示進程的全部地址空間,——包含了和進程地址空間有關的全部信息,其中當然包含進程的內存區域。

進程內存的分配與回收

創建進程fork()、程序載入execve()、映射文件mmap()、動態內存分配malloc()/brk()等進程相關操作都需要分配內存給進程。不過這時進程申請和獲得的還不是實際內存,而是虛擬內存,准確的說是「內存區域」。進程對內存區域的分配最終都會歸結到do_mmap()函數上來(brk調用被單獨以系統調用實現,不用do_mmap()),

內核使用do_mmap()函數創建一個新的線性地址區間。但是說該函數創建了一個新VMA並不非常准確,因為如果創建的地址區間和一個已經存在的地址區間相鄰,並且它們具有相同的訪問許可權的話,那麼兩個區間將合並為一個。如果不能合並,那麼就確實需要創建一個新的VMA了。但無論哪種情況,do_mmap()函數都會將一個地址區間加入到進程的地址空間中--無論是擴展已存在的內存區域還是創建一個新的區域。

同樣,釋放一個內存區域應使用函數do_ummap(),它會銷毀對應的內存區域。

如何由虛變實!

從上面已經看到進程所能直接操作的地址都為虛擬地址。當進程需要內存時,從內核獲得的僅僅是虛擬的內存區域,而不是實際的物理地址,進程並沒有獲得物理內存(物理頁面——頁的概念請大家參考硬體基礎一章),獲得的僅僅是對一個新的線性地址區間的使用權。實際的物理內存只有當進程真的去訪問新獲取的虛擬地址時,才會由「請求頁機制」產生「缺頁」異常,從而進入分配實際頁面的常式。

該異常是虛擬內存機制賴以存在的基本保證——它會告訴內核去真正為進程分配物理頁,並建立對應的頁表,這之後虛擬地址才實實在在地映射到了系統的物理內存上。(當然,如果頁被換出到磁碟,也會產生缺頁異常,不過這時不用再建立頁表了)

這種請求頁機制把頁面的分配推遲到不能再推遲為止,並不急於把所有的事情都一次做完(這種思想有點像設計模式中的代理模式(proxy))。之所以能這么做是利用了內存訪問的「局部性原理」,請求頁帶來的好處是節約了空閑內存,提高了系統的吞吐率。要想更清楚地了解請求頁機制,可以看看《深入理解linux內核》一書。

這里我們需要說明在內存區域結構上的nopage操作。當訪問的進程虛擬內存並未真正分配頁面時,該操作便被調用來分配實際的物理頁,並為該頁建立頁表項。在最後的例子中我們會演示如何使用該方法。

系統物理內存管理

雖然應用程序操作的對象是映射到物理內存之上的虛擬內存,但是處理器直接操作的卻是物理內存。所以當應用程序訪問一個虛擬地址時,首先必須將虛擬地址轉化成物理地址,然後處理器才能解析地址訪問請求。地址的轉換工作需要通過查詢頁表才能完成,概括地講,地址轉換需要將虛擬地址分段,使每段虛地址都作為一個索引指向頁表,而頁表項則指向下一級別的頁表或者指向最終的物理頁面。

每個進程都有自己的頁表。進程描述符的pgd域指向的就是進程的頁全局目錄。下面我們借用《linux設備驅動程序》中的一幅圖大致看看進程地址空間到物理頁之間的轉換關系。

上面的過程說起來簡單,做起來難呀。因為在虛擬地址映射到頁之前必須先分配物理頁——也就是說必須先從內核中獲取空閑頁,並建立頁表。下面我們介紹一下內核管理物理內存的機制。

物理內存管理(頁管理)

Linux內核管理物理內存是通過分頁機制實現的,它將整個內存劃分成無數個4k(在i386體系結構中)大小的頁,從而分配和回收內存的基本單位便是內存頁了。利用分頁管理有助於靈活分配內存地址,因為分配時不必要求必須有大塊的連續內存[3],系統可以東一頁、西一頁的湊出所需要的內存供進程使用。雖然如此,但是實際上系統使用內存時還是傾向於分配連續的內存塊,因為分配連續內存時,頁表不需要更改,因此能降低TLB的刷新率(頻繁刷新會在很大程度上降低訪問速度)。

鑒於上述需求,內核分配物理頁面時為了盡量減少不連續情況,採用了「夥伴」關系來管理空閑頁面。夥伴關系分配演算法大家應該不陌生——幾乎所有操作系統方面的書都會提到,我們不去詳細說它了,如果不明白可以參看有關資料。這里只需要大家明白Linux中空閑頁面的組織和管理利用了夥伴關系,因此空閑頁面分配時也需要遵循夥伴關系,最小單位只能是2的冪倍頁面大小。內核中分配空閑頁面的基本函數是get_free_page/get_free_pages,它們或是分配單頁或是分配指定的頁面(2、4、8…512頁)。

注意:get_free_page是在內核中分配內存,不同於malloc在用戶空間中分配,malloc利用堆動態分配,實際上是調用brk()系統調用,該調用的作用是擴大或縮小進程堆空間(它會修改進程的brk域)。如果現有的內存區域不夠容納堆空間,則會以頁面大小的倍數為單位,擴張或收縮對應的內存區域,但brk值並非以頁面大小為倍數修改,而是按實際請求修改。因此Malloc在用戶空間分配內存可以以位元組為單位分配,但內核在內部仍然會是以頁為單位分配的。

另外,需要提及的是,物理頁在系統中由頁結構structpage描述,系統中所有的頁面都存儲在數組mem_map[]中,可以通過該數組找到系統中的每一頁(空閑或非空閑)。而其中的空閑頁面則可由上述提到的以夥伴關系組織的空閑頁鏈表(free_area[MAX_ORDER])來索引。

內核內存使用

Slab

所謂尺有所長,寸有所短。以頁為最小單位分配內存對於內核管理系統中的物理內存來說的確比較方便,但內核自身最常使用的內存卻往往是很小(遠遠小於一頁)的內存塊——比如存放文件描述符、進程描述符、虛擬內存區域描述符等行為所需的內存都不足一頁。這些用來存放描述符的內存相比頁面而言,就好比是麵包屑與麵包。一個整頁中可以聚集多個這些小塊內存;而且這些小塊內存塊也和麵包屑一樣頻繁地生成/銷毀。

為了滿足內核對這種小內存塊的需要,Linux系統採用了一種被稱為slab分配器的技術。Slab分配器的實現相當復雜,但原理不難,其核心思想就是「存儲池[4]」的運用。內存片段(小塊內存)被看作對象,當被使用完後,並不直接釋放而是被緩存到「存儲池」里,留做下次使用,這無疑避免了頻繁創建與銷毀對象所帶來的額外負載。

Slab技術不但避免了內存內部分片(下文將解釋)帶來的不便(引入Slab分配器的主要目的是為了減少對夥伴系統分配演算法的調用次數——頻繁分配和回收必然會導致內存碎片——難以找到大塊連續的可用內存),而且可以很好地利用硬體緩存提高訪問速度。

Slab並非是脫離夥伴關系而獨立存在的一種內存分配方式,slab仍然是建立在頁面基礎之上,換句話說,Slab將頁面(來自於夥伴關系管理的空閑頁面鏈表)撕碎成眾多小內存塊以供分配,slab中的對象分配和銷毀使用kmem_cache_alloc與kmem_cache_free。

Kmalloc

Slab分配器不僅僅只用來存放內核專用的結構體,它還被用來處理內核對小塊內存的請求。當然鑒於Slab分配器的特點,一般來說內核程序中對小於一頁的小塊內存的請求才通過Slab分配器提供的介面Kmalloc來完成(雖然它可分配32到131072位元組的內存)。從內核內存分配的角度來講,kmalloc可被看成是get_free_page(s)的一個有效補充,內存分配粒度更靈活了。

有興趣的話,可以到/proc/slabinfo中找到內核執行現場使用的各種slab信息統計,其中你會看到系統中所有slab的使用信息。從信息中可以看到系統中除了專用結構體使用的slab外,還存在大量為Kmalloc而准備的Slab(其中有些為dma准備的)。

內核非連續內存分配(Vmalloc)

夥伴關系也好、slab技術也好,從內存管理理論角度而言目的基本是一致的,它們都是為了防止「分片」,不過分片又分為外部分片和內部分片之說,所謂內部分片是說系統為了滿足一小段內存區(連續)的需要,不得不分配了一大區域連續內存給它,從而造成了空間浪費;外部分片是指系統雖有足夠的內存,但卻是分散的碎片,無法滿足對大塊「連續內存」的需求。無論何種分片都是系統有效利用內存的障礙。slab分配器使得一個頁面內包含的眾多小塊內存可獨立被分配使用,避免了內部分片,節約了空閑內存。夥伴關系把內存塊按大小分組管理,一定程度上減輕了外部分片的危害,因為頁框分配不在盲目,而是按照大小依次有序進行,不過夥伴關系只是減輕了外部分片,但並未徹底消除。你自己比劃一下多次分配頁面後,空閑內存的剩餘情況吧。

所以避免外部分片的最終思路還是落到了如何利用不連續的內存塊組合成「看起來很大的內存塊」——這里的情況很類似於用戶空間分配虛擬內存,內存邏輯上連續,其實映射到並不一定連續的物理內存上。Linux內核借用了這個技術,允許內核程序在內核地址空間中分配虛擬地址,同樣也利用頁表(內核頁表)將虛擬地址映射到分散的內存頁上。以此完美地解決了內核內存使用中的外部分片問題。內核提供vmalloc函數分配內核虛擬內存,該函數不同於kmalloc,它可以分配較Kmalloc大得多的內存空間(可遠大於128K,但必須是頁大小的倍數),但相比Kmalloc來說,Vmalloc需要對內核虛擬地址進行重映射,必須更新內核頁表,因此分配效率上要低一些(用空間換時間)

與用戶進程相似,內核也有一個名為init_mm的mm_strcut結構來描述內核地址空間,其中頁表項pdg=swapper_pg_dir包含了系統內核空間(3G-4G)的映射關系。因此vmalloc分配內核虛擬地址必須更新內核頁表,而kmalloc或get_free_page由於分配的連續內存,所以不需要更新內核頁表。

vmalloc分配的內核虛擬內存與kmalloc/get_free_page分配的內核虛擬內存位於不同的區間,不會重疊。因為內核虛擬空間被分區管理,各司其職。進程空間地址分布從0到3G(其實是到PAGE_OFFSET,在0x86中它等於0xC0000000),從3G到vmalloc_start這段地址是物理內存映射區域(該區域中包含了內核鏡像、物理頁面表mem_map等等)比如我使用的系統內存是64M(可以用free看到),那麼(3G——3G+64M)這片內存就應該映射到物理內存,而vmalloc_start位置應在3G+64M附近(說"附近"因為是在物理內存映射區與vmalloc_start期間還會存在一個8M大小的gap來防止躍界),vmalloc_end的位置接近4G(說"接近"是因為最後位置系統會保留一片128k大小的區域用於專用頁面映射,還有可能會有高端內存映射區,這些都是細節,這里我們不做糾纏)。

上圖是內存分布的模糊輪廓

由get_free_page或Kmalloc函數所分配的連續內存都陷於物理映射區域,所以它們返回的內核虛擬地址和實際物理地址僅僅是相差一個偏移量(PAGE_OFFSET),你可以很方便的將其轉化為物理內存地址,同時內核也提供了virt_to_phys()函數將內核虛擬空間中的物理映射區地址轉化為物理地址。要知道,物理內存映射區中的地址與內核頁表是有序對應的,系統中的每個物理頁面都可以找到它對應的內核虛擬地址(在物理內存映射區中的)。

而vmalloc分配的地址則限於vmalloc_start與vmalloc_end之間。每一塊vmalloc分配的內核虛擬內存都對應一個vm_struct結構體(可別和vm_area_struct搞混,那可是進程虛擬內存區域的結構),不同的內核虛擬地址被4k大小的空閑區間隔,以防止越界——見下圖)。與進程虛擬地址的特性一樣,這些虛擬地址與物理內存沒有簡單的位移關系,必須通過內核頁表才可轉換為物理地址或物理頁。它們有可能尚未被映射,在發生缺頁時才真正分配物理頁面。

這里給出一個小程序幫助大家認清上面幾種分配函數所對應的區域。

#include<linux/mole.h>

#include<linux/slab.h>

#include<linux/vmalloc.h>

unsignedchar*pagemem;

unsignedchar*kmallocmem;

unsignedchar*vmallocmem;

intinit_mole(void)

{

pagemem = get_free_page(0);

printk("<1>pagemem=%s",pagemem);

kmallocmem = kmalloc(100,0);

printk("<1>kmallocmem=%s",kmallocmem);

vmallocmem = vmalloc(1000000);

printk("<1>vmallocmem=%s",vmallocmem);

}

voidcleanup_mole(void)

{

free_page(pagemem);

kfree(kmallocmem);

vfree(vmallocmem);

}

實例

內存映射(mmap)是Linux操作系統的一個很大特色,它可以將系統內存映射到一個文件(設備)上,以便可以通過訪問文件內容來達到訪問內存的目的。這樣做的最大好處是提高了內存訪問速度,並且可以利用文件系統的介面編程(設備在Linux中作為特殊文件處理)訪問內存,降低了開發難度。許多設備驅動程序便是利用內存映射功能將用戶空間的一段地址關聯到設備內存上,無論何時,只要內存在分配的地址范圍內進行讀寫,實際上就是對設備內存的訪問。同時對設備文件的訪問也等同於對內存區域的訪問,也就是說,通過文件操作介面可以訪問內存。Linux中的X伺服器就是一個利用內存映射達到直接高速訪問視頻卡內存的例子。

熟悉文件操作的朋友一定會知道file_operations結構中有mmap方法,在用戶執行mmap系統調用時,便會調用該方法來通過文件訪問內存——不過在調用文件系統mmap方法前,內核還需要處理分配內存區域(vma_struct)、建立頁表等工作。對於具體映射細節不作介紹了,需要強調的是,建立頁表可以採用remap_page_range方法一次建立起所有映射區的頁表,或利用vma_struct的nopage方法在缺頁時現場一頁一頁的建立頁表。第一種方法相比第二種方法簡單方便、速度快,但是靈活性不高。一次調用所有頁表便定型了,不適用於那些需要現場建立頁表的場合——比如映射區需要擴展或下面我們例子中的情況。

我們這里的實例希望利用內存映射,將系統內核中的一部分虛擬內存映射到用戶空間,以供應用程序讀取——你可利用它進行內核空間到用戶空間的大規模信息傳輸。因此我們將試圖寫一個虛擬字元設備驅動程序,通過它將系統內核空間映射到用戶空間——將內核虛擬內存映射到用戶虛擬地址。從上一節已經看到Linux內核空間中包含兩種虛擬地址:一種是物理和邏輯都連續的物理內存映射虛擬地址;另一種是邏輯連續但非物理連續的vmalloc分配的內存虛擬地址。我們的例子程序將演示把vmalloc分配的內核虛擬地址映射到用戶地址空間的全過程。

程序里主要應解決兩個問題:

第一是如何將vmalloc分配的內核虛擬內存正確地轉化成物理地址?

因為內存映射先要獲得被映射的物理地址,然後才能將其映射到要求的用戶虛擬地址上。我們已經看到內核物理內存映射區域中的地址可以被內核函數virt_to_phys轉換成實際的物理內存地址,但對於vmalloc分配的內核虛擬地址無法直接轉化成物理地址,所以我們必須對這部分虛擬內存格外「照顧」——先將其轉化成內核物理內存映射區域中的地址,然後在用virt_to_phys變為物理地址。

轉化工作需要進行如下步驟:

  • 找到vmalloc虛擬內存對應的頁表,並尋找到對應的頁表項。

  • 獲取頁表項對應的頁面指針

  • 通過頁面得到對應的內核物理內存映射區域地址。

  • 如下圖所示:

    第二是當訪問vmalloc分配區時,如果發現虛擬內存尚未被映射到物理頁,則需要處理「缺頁異常」。因此需要我們實現內存區域中的nopaga操作,以能返回被映射的物理頁面指針,在我們的實例中就是返回上面過程中的內核物理內存映射區域中的地址。由於vmalloc分配的虛擬地址與物理地址的對應關系並非分配時就可確定,必須在缺頁現場建立頁表,因此這里不能使用remap_page_range方法,只能用vma的nopage方法一頁一頁的建立。

    程序組成

    map_driver.c,它是以模塊形式載入的虛擬字元驅動程序。該驅動負責將一定長的內核虛擬地址(vmalloc分配的)映射到設備文件上。其中主要的函數有——vaddress_to_kaddress()負責對vmalloc分配的地址進行頁表解析,以找到對應的內核物理映射地址(kmalloc分配的地址);map_nopage()負責在進程訪問一個當前並不存在的VMA頁時,尋找該地址對應的物理頁,並返回該頁的指針。

    test.c它利用上述驅動模塊對應的設備文件在用戶空間讀取讀取內核內存。結果可以看到內核虛擬地址的內容(ok!),被顯示在了屏幕上。

    執行步驟

    編譯map_driver.c為map_driver.o模塊,具體參數見Makefile

    載入模塊:insmodmap_driver.o

    生成對應的設備文件

    1在/proc/devices下找到map_driver對應的設備命和設備號:grepmapdrv/proc/devices

    2建立設備文件mknodmapfilec 254 0(在我的系統里設備號為254)

    利用maptest讀取mapfile文件,將取自內核的信息列印到屏幕上。

    5. Linux I/O內存靜態映射

    在將Linux移植到目標電路板的過程中,有得會建立外設IO內存物理地址到虛擬地址的靜態映射,這個映射通過在與電路板對應的map_desc結構體數組中添加新的成員來完成。iotable_init()是最終建立頁映射的函數,它通過ACHINE_START、MACHINE_END宏賦值給電路板的map_io())函數。將Linux操作系統移植到特定平台上,MACHINE_START(或者DT_MACHINE_START)、MACHINE_END宏之間的定義針對特定電路板而設計,其中的map_io ()成員函數完成IO內存的靜態映射。在一個已經移植好操作系統的內核中,驅動工程師可以對非常規內存區域的IO內存(外設控制器寄存器、MCU內部集成的外設控制器寄存器等)依照電路板的資源使用情況添加到map_desc數組中,但是目前該方法已經不值得推薦。
    Cache和DMA本身似乎是兩個毫不相關的事物。Cache被用作CPU針對內存的緩存,利用程序的空間局部性和時間局部性原理,達到較高的命中率,從而避免CPU每次都必須要與相對慢速的內存交互數據來提高數據的訪問速率。DMA可以作為內存與外設之間傳輸數據的方式,在這種傳輸方式之下,數據並不需要經過CPU中轉。
    假設DMA針對內存的目的地址與Cache緩存的對象沒有重疊區域,DMA和Cache之間將相安無事。但是,如果DMA的目的地址與Cache所緩存的內存地址訪問有重疊,經過DMA操作,與Cache緩存對應的內存中的數據已經被修改,而CPU本身並不知道,它仍然認為Cache中的數據就是內存中的數據,那在以後訪問Cache映射的內存時,它仍然使用陳舊的Cache數據。這樣就會發生Cache與內存之間數據「不一致性」的錯誤。

    6. LINUX內存映射問題

    Linux的內存模型,一般為:

    地址
    作用
    說明

    >=0xc000 0000
    內核虛擬存儲器
    用戶代碼不可見區域

    <0xc000 0000
    Stack(用戶棧)
    ESP指向棧頂





    空閑內存

    >=0x4000 0000
    文件映射區

    <0x4000 0000



    空閑內存

    Heap(運行時堆)
    通過brk/sbrk系統調用擴大堆,向上增長。

    .data、.bss(讀寫段)
    從可執行文件中載入

    >=0x0804 8000(0x00008000 for arm linux)
    .init、.text、.rodata(只讀段)
    從可執行文件中載入

    <0x0804 8000(0x00008000 for arm linux)
    保留區域

    運行一個測試程序,觀察其結果:

    #include <stdio.h>

    int main(int argc, char* argv[])

    {

    int first = 0;

    int* p0 = malloc(1024);

    int* p1 = malloc(1024 * 1024);

    int* p2 = malloc(512 * 1024 * 1024 );

    int* p3 = malloc(1024 * 1024 * 1024 );

    printf("main=%p print=%p\n", main, printf);

    printf("first=%p\n", &first);

    printf("p0=%p p1=%p p2=%p p3=%p\n", p0, p1, p2, p3);

    getchar();

    return 0;

    }

    運行後,輸出結果為:

    main=0x8048404 print=0x8048324

    first=0xbfcd1264

    p0=0x9253008 p1=0xb7ec0008 p2=0x97ebf008 p3=0x57ebe008

    my pc (fc5)輸出結果如下:

    main=0x80483f4 print=0x8048324
    first=0xbf848660
    p0=0x9ab2008 p1=0xb7e38008 p2=0x97e37008 p3=(nil)

    arm-linux輸出如下結果:

    main=0x8528 print=0x8404
    first=0xbec9fe10
    p0=0x11008 p1=0x4005a008 p2=(nil) p3=(nil)

    main和print兩個函數是代碼段(.text)的,其地址符合表一的描述。

    first是第一個臨時變數,由於在first之前還有一些環境變數,它的值並非0xbfffffff,而是0xbfcd1264,這是正常的。

    p0是在堆中分配的,其地址小於0x4000 0000,這也是正常的。

    但p1和p2也是在堆中分配的,而其地址竟大於0x4000 0000,與表一描述不符。

    原因在於:運行時堆的位置與內存管理演算法相關,也就是與malloc的實現相關。關於內存管理演算法的問題,我們在後繼文章中有詳細描述,這里只作簡要說明。在glibc實現的內存管理演算法中,Malloc小塊內存是在小於0x4000 0000的內存中分配的,通過brk/sbrk不斷向上擴展,而分配大塊內存,malloc直接通過系統調用mmap實現,分配得到的地址在文件映射區,所以其地址大於0x4000 0000。

    7. Linux將設備地址映射到用戶空間內存映射與VMA

    一般情況下,用戶空間是不可能也不應該直接訪問設備的,但是,設備驅動程序中可實現mmap ()函數,這個函數可使得用戶空間能直接訪問設備的物理地址。實際上,mmap ()實現了這樣的一個映射過程:它將用戶空間的一段內存與設備內存關聯,當用戶訪問用戶空間的這段地址范圍時,實際上會轉化為對設備的訪問。
    這種能力對於顯示適配器一類的設備非常有意義,如果用戶空間可直接通過內存映射訪問顯存的話,屏幕幀的各點像素將不再需要一個從用戶空間到內核空間的復制的過程。
    mmap ()必須以PAGE_SIZE為單位進行映射,實際上,內存只能以頁為單位進行映射,若要映射非PAGE_SIZE整數倍的地址范圍,要先進行頁對齊,強行以PAGE_SIZE的倍數大小進行映射。
    從file_operations文件操作結構體可以看出,驅動中mmap ()函數的原型如下:
    int ( *mmap)(struct file *, struct vm_area_struct* ) ;
    驅動中的mmap () 函數將在用戶進行mmap ()系統調用時最終被調用,mmap ()系統調用的原型與file_operations中mmap ()的原型區別很大,如下所示:
    caddr_t mmap (caddr_t addr,size_t len,int prot,int flags,int fd,off_t offset);
    參數fd為文件描述符,一般由open ()返回,fd也可以指定為-1,此時需指定flags參數中的MAP_ANON,表明進行的是匿名映射。
    len是映射到調用用戶空間的位元組數,它從被映射文件開頭offset個位元組開始算起,offset參數一般設為0,表示從文件頭開始映射。
    prot參數指定訪問許可權,可取如下幾個值的「或」:PROT_READ(可讀)、PROT_WRITE(可寫)、PROT_EXEC(可執行)和PROT_NONE(不可訪問)。
    參數addr指定文件應被映射到用戶空間的起始地址,一般被指定為NULL,這樣,選擇起始地址的任務將由內核完成,而函數的返回值就是映射到用戶空間的地址。其類型caddr_t實際上就是void*。
    當用戶調用mmap ())的時候,內核會進行如下處理。
    1)在進程的虛擬空間查找一塊VMA。
    2)將這塊VMA進行映射。
    3)如果設備驅動程序或者文件系統的file_operations定義了mmap ()操作,則調用它。
    4)將這個VMA插入進程的VMA鏈表中。
    file_operations中mmap ()函數的第一個參數就是步驟1)找到的VMA。
    由mmap ()系統調用映射的內存可由munmap ()解除映射,這個函數的原型如下:
    int munmap(caddr_t addr, size_t len ) ;
    驅動程序中mmap ()的實現機制是建立頁表,並填充VMA結構體中vm_operations_struct指針。

    8. 一個文本文件用內存映射打開後,如何一行一行地進行讀取 Linux下C語言

    //mbuf為文件的內存映射地址,buf為你要保存字元串的地址。
    char *readline(const char *mbuf, char *buf, int maxlen)
    {
    int len=0;
    while(len<maxlen-1 && *(mbuf+len)!='\n')
    {
    *(buf+len)=*(mbuf+len);
    len++;
    }
    *(buf+len)='\0';
    return buf;
    }

    9. 關於linux系統下的物理內存映射

    內存是一種存儲設備,現在一般是ddr
    sdram,地址是用來標記內存的數據的。在操作系統中物理內存指實際的ddr
    sdram,而虛擬內存指的是在硬碟中的緩存,windows中是頁面文件,linux中是swap分區。cpu產生的地址是虛擬地址也可以稱作有效地址,而在cpu外地址線上的信號稱作實際地址或物理地址。這2類地址有某種對應關系,由操作系統管理。如果是x86架構的話,物理地址和虛擬地址中間還有線性地址的概念。

    熱點內容
    刪資料庫事件 發布:2024-11-15 12:10:54 瀏覽:455
    資料庫選課管理系統 發布:2024-11-15 12:10:15 瀏覽:126
    android音樂波形圖 發布:2024-11-15 11:57:12 瀏覽:378
    福建社保銀行卡初始密碼是多少 發布:2024-11-15 11:47:40 瀏覽:911
    游戲多開用什麼配置 發布:2024-11-15 11:46:51 瀏覽:729
    管理java版本 發布:2024-11-15 11:44:03 瀏覽:629
    ndk編譯的程序如何執行 發布:2024-11-15 11:43:18 瀏覽:626
    輕應用伺服器適合搭建網站嗎 發布:2024-11-15 11:36:08 瀏覽:246
    c語言的百分號 發布:2024-11-15 11:34:24 瀏覽:31
    一加五安卓8什麼時候推送 發布:2024-11-15 11:19:40 瀏覽:854