對(duì)于包含MMU(內(nèi)存管理單元)的處理器而言,linux系統(tǒng)以虛擬內(nèi)存的方式為每個(gè)進(jìn)程分配最大4GB的內(nèi)存。這真的4GB的內(nèi)存空間被分為兩個(gè)部分–用戶空間 與 內(nèi)核空間。用戶空間地地址分布為0~3GB,剩下的3 ~ 4GB 為內(nèi)核空間。如下圖。
用戶進(jìn)程通常只能訪問(wèn)用戶空間的虛擬地址,不能訪問(wèn)內(nèi)核空間的虛擬地址。用戶進(jìn)程只有通過(guò)系統(tǒng)調(diào)用(代表用戶進(jìn)程在內(nèi)核態(tài)執(zhí)行)等方式才可以訪問(wèn)到內(nèi)核空間。
每個(gè)進(jìn)程的用戶空間都是獨(dú)立的,互不相干,用戶進(jìn)程各自有不同的頁(yè)表。而內(nèi)核空間是由內(nèi)核負(fù)責(zé)映射,內(nèi)核空間的虛擬地址到物理地址映射是被所有進(jìn)程共享的,內(nèi)核的虛擬空間獨(dú)立于其它程序。
1、linux內(nèi)核管理內(nèi)存
內(nèi)核把物理內(nèi)存以頁(yè)(page)為單位進(jìn)行劃分。內(nèi)存管理單元MMU也以頁(yè)為單位進(jìn)行處理,因此虛擬內(nèi)存也把頁(yè)做為最小的單位。
1.1 頁(yè)
一般,對(duì)于32位系統(tǒng),一頁(yè)有4Kbyte,對(duì)于64位系統(tǒng),一頁(yè)有8Kbyte。這就間味著,如果一個(gè)32位系統(tǒng)有1G的物理內(nèi)存,則按4K一頁(yè)劃分,則物理內(nèi)存會(huì)被劃分為262144頁(yè)。
內(nèi)核用struct page結(jié)構(gòu)體表示系統(tǒng)的每個(gè)物理頁(yè),以下是一個(gè)簡(jiǎn)化版的page結(jié)構(gòu),用于說(shuō)明其功能:
#incluce <linux/mm_types.h>
struct page{
unsigned long flags;
atomic_t _count;
atomic_t _mapcount;
unsigned long private;
struct address_space *mapping;
pgoff_t index;
struct list_head lru;
void *virtual;
}
flags:用來(lái)存放頁(yè)的狀態(tài),每一個(gè)bit單獨(dú)表示一種狀態(tài),最多可以表示32種狀態(tài),這些標(biāo)志定義在<linux/page-flags.h>中。這些狀態(tài)包括頁(yè)是不是臟的,是不是被鎖定在內(nèi)存中。
enum pageflags {
PG_locked, /* Page is locked. Don't touch. */
PG_error,
PG_referenced,
PG_uptodate,
PG_dirty,
PG_lru,
PG_active,
PG_waiters, /* Page has waiters, check its waitqueue. Must be bit #7 and in the same byte as "PG_locked" */
PG_slab,
PG_owner_priv_1, /* Owner use. If pagecache, fs may use*/
PG_arch_1,
PG_reserved,
PG_private, /* If pagecache, has fs-private data */
PG_private_2, /* If pagecache, has fs aux data */
PG_writeback, /* Page is under writeback */
PG_head, /* A head page */
PG_mappedtodisk, /* Has blocks allocated on-disk */
PG_reclaim, /* To be reclaimed asap */
PG_swapbacked, /* Page is backed by RAM/swap */
PG_unevictable, /* Page is "unevictable" */
#ifdef CONFIG_MMU
PG_mlocked, /* Page is vma mlocked */
#endif
#ifdef CONFIG_ARCH_USES_PG_UNCACHED
PG_uncached, /* Page has been mapped as uncached */
#endif
#ifdef CONFIG_MEMORY_FAILURE
PG_hwpoison, /* hardware poisoned page. Don't touch */
#endif
#if defined(CONFIG_IDLE_PAGE_TRACKING) && defined(CONFIG_64BIT)
PG_young,
PG_idle,
#endif
__NR_PAGEFLAGS,
/* Filesystems */
PG_checked = PG_owner_priv_1,
/* SwapBacked */
PG_swapcache = PG_owner_priv_1, /* Swap page: swp_entry_t in private */
/* Two page bits are conscripted by FS-Cache to maintain local caching
* state. These bits are set on pages belonging to the netfs's inodes
* when those inodes are being locally cached.
*/
PG_fscache = PG_private_2, /* page backed by cache */
/* XEN */
/* Pinned in Xen as a read-only pagetable page. */
PG_pinned = PG_owner_priv_1,
/* Pinned as part of domain save (see xen_mm_pin_all()). */
PG_savepinned = PG_dirty,
/* Has a grant mapping of another (foreign) domain's page. */
PG_foreign = PG_owner_priv_1,
/* SLOB */
PG_slob_free = PG_private,
/* Compound pages. Stored in first tail page's flags */
PG_double_map = PG_private_2,
/* non-lru isolated movable page */
PG_isolated = PG_reclaim,
};
_count:存放頁(yè)的引用計(jì)數(shù)(即這一頁(yè)被引用了多少次)。當(dāng)計(jì)數(shù)值變?yōu)?1時(shí),就說(shuō)明這頁(yè)沒(méi)有被引用,就可以分配了。要調(diào)用page_count()函數(shù)進(jìn)行檢查,返回0表示頁(yè)空閑。
virtual:是頁(yè)的虛擬地址。指頁(yè)在虛擬內(nèi)存中的地址。
內(nèi)核用page這一結(jié)構(gòu)來(lái)管理系統(tǒng)中的所有的頁(yè)。內(nèi)核需要知道一個(gè)頁(yè)是否空閑,頁(yè)被誰(shuí)擁有等。
1.2 區(qū)
由于硬件的一些特殊性,比如:
- 某些硬件只能用某些特定的內(nèi)存地址來(lái)執(zhí)行DMA。
- 一些體系結(jié)構(gòu)的物理尋址范圍比虛擬尋址范圍大得多,這樣就有一些內(nèi)存不能永久地映射到內(nèi)核空間上。
因此,內(nèi)核把頁(yè)劃分為不同的區(qū)(zone)。內(nèi)核使用區(qū)對(duì)具有相似特性的頁(yè)進(jìn)行分組。主要定義了四種區(qū):
(以下區(qū)在linux/mmzone.h頭文件中定義)
- ZONE_DMA 這個(gè)區(qū)包含的頁(yè)能用來(lái)執(zhí)行DMA操作。
- ZONE_DMA32 和ZONE_DMA類似,該區(qū)包含的頁(yè)面只能被32位設(shè)備訪問(wèn)。
- ZONE_NORMAL 這個(gè)區(qū)包含的都是能正常映射的頁(yè)。
- ZONE_HIGHEM 這個(gè)區(qū)包含“高端內(nèi)存”,其中的頁(yè)并不能永久地映射到內(nèi)核地址空間。
這里要注意的是,區(qū)的實(shí)際使用和分布是與體系結(jié)構(gòu)相關(guān)的。
1.2.1 了解x86系統(tǒng)的內(nèi)核地址映射區(qū):
內(nèi)核地址空間劃分圖:
1、3G~3G+896M 為 低端內(nèi)存
- 特點(diǎn):低端區(qū)與物理內(nèi)存是直接映射關(guān)系( 映射方法: 虛擬地址 = 3G + 物理地址)
- 低端內(nèi)存再細(xì)分為:ZONE_DMA、ZONE_NORMAL
- 分配指令:
低端內(nèi)存區(qū)內(nèi)分配內(nèi)存區(qū)的分配指令:- kmalloc:小內(nèi)存分配,slab算法
- get_free_page:整頁(yè)分配,2的n次方頁(yè),n最大為10
2、大于3G+896M 為 高端內(nèi)存
- 特點(diǎn):虛擬地址連續(xù),物理地址不連續(xù)
- 可再細(xì)分為:vmalloc區(qū)、持久映射區(qū)、固定映射區(qū)
- 在高端內(nèi)存區(qū)分配內(nèi)存塊的分配方式:vmalloc
1.2.2 了解32位ARM系統(tǒng)的內(nèi)核地址映射區(qū):
2、內(nèi)存存取
在linux內(nèi)核空間中申請(qǐng)內(nèi)存涉及的函數(shù)主要包括kmalloc()、__get_free_pages()和vamlloc()等。
Kmalloc和__get_free_pages申請(qǐng)的內(nèi)存位于DMA和常規(guī)區(qū)域的映射區(qū),而且在物理上也是連續(xù)的,它們與真實(shí)的物理地址只有一個(gè)固定的偏移。
vmalloc()在虛擬內(nèi)存空間給出一塊連續(xù)的內(nèi)存區(qū),實(shí)質(zhì)上,這片連續(xù)的虛擬內(nèi)存在物理內(nèi)存中并不一定連續(xù),而vmalloc()申請(qǐng)的虛擬內(nèi)存和物理內(nèi)存之間與沒(méi)有簡(jiǎn)單的換算關(guān)系。
2.1 kmalloc
kmalloc()是Linux內(nèi)核中用于動(dòng)態(tài)內(nèi)存分配的函數(shù)。它的聲明和頭文件如下:
#include <linux/slab.h>
void *kmalloc(size_t size, gfp_t flags);
參數(shù):
- size:要分配的內(nèi)存塊的大小,以字節(jié)為單位。
- flags:分配標(biāo)志,指定分配的內(nèi)存屬性,如GFP_KERNEL表示分配內(nèi)核內(nèi)存等。在頭文件**</include/linux/gfp.h>**中定義。
這些gfp_t類型中的宏表示的具體含義如下:
- GFP_ATOMIC: 分配內(nèi)能在中斷/中斷上下文中安全使用的內(nèi)存,這種分配不會(huì)睡眠,因此比較適合在原子上下文中使用。
- GFP_KERNEL: 分配內(nèi)核內(nèi)存,這是最常用的標(biāo)志,允許睡眠和I/O。
- GFP_KERNEL_ACCOUNT: 與GFP_KERNEL類似,但會(huì)對(duì)分配進(jìn)行計(jì)數(shù)。
- GFP_NOWAIT: 分配內(nèi)存時(shí)不允許睡眠,要么立即分配成功,要么失敗。
- GFP_NOIO: 分配內(nèi)存時(shí)不允許進(jìn)行I/O操作。
- GFP_NOFS: 分配內(nèi)存時(shí)不允許觸發(fā)文件系統(tǒng)操作。
- GFP_USER: 分配用戶內(nèi)存,會(huì)睡眠和I/O。
- GFP_DMA: 分配DMA可訪問(wèn)的內(nèi)存,用于DMA傳輸。
- GFP_DMA32: 只分配32位DMA可訪問(wèn)的內(nèi)存。
- GFP_HIGHUSER: 分配用戶內(nèi)存中高端內(nèi)存部分。
- GFP_HIGHHUSER_MOVABLE: 同GFP_HIGHUSER,分配的內(nèi)存可以移動(dòng)。
注意:
使用GFP_KERNEL標(biāo)志申請(qǐng)內(nèi)存時(shí),若暫時(shí)不能滿足,則進(jìn)程會(huì)睡眠等待頁(yè),即會(huì)引起阻塞,因此不能在中斷上下文或持有自旋鎖的時(shí)候使用GFP_KERNEL申請(qǐng)內(nèi)存。所以此時(shí)驅(qū)動(dòng)應(yīng)當(dāng)使用GFP_ATOMIC標(biāo)志來(lái)申請(qǐng)內(nèi)存。當(dāng)使用GFP_ATOMIC標(biāo)志申請(qǐng)內(nèi)存時(shí),若不存在空閑頁(yè),則不等待,直接返回。
返回值:
kmalloc()函數(shù)返回分配得到的內(nèi)存塊的地址,如果分配失敗則返回NULL。
kmalloc()分配的內(nèi)存屬于伙伴系統(tǒng)(slub/slab分配器)管理的內(nèi)存,這些內(nèi)存會(huì)存放在緩存中,并在需要時(shí)分配給請(qǐng)求的進(jìn)程或內(nèi)核代碼。這樣可以提高內(nèi)存分配和釋放的效率,避免頻繁的物理頁(yè)分配和釋放。
2.1.1 kfree
使用kmalloc分配的內(nèi)存應(yīng)該用kfree()釋放。kfree()函數(shù)會(huì)釋放ptr指向的內(nèi)存空間,并將其歸還給伙伴系統(tǒng)(slub/slab分配器)。
它的聲明如下:
#include <linux/slab.h>
void kfree(const void *ptr);
參數(shù):
ptr指向要釋放的內(nèi)存塊的地址。
與kmalloc()類似,kfree()也是Linux內(nèi)核中常用的內(nèi)存管理函數(shù),用于釋放動(dòng)態(tài)分配的內(nèi)存。
使用kmalloc()和kfree()需要注意以下幾點(diǎn):
- 只能向kfree()傳入kmalloc()分配的內(nèi)存地址,否則會(huì)導(dǎo)致未定義行為。
- 同一塊內(nèi)存只能free一次,多次free同一地址會(huì)導(dǎo)致未定義行為。
- free已free的內(nèi)存地址會(huì)導(dǎo)致未定義行為。
- kmalloc()的內(nèi)存不會(huì)自動(dòng)釋放,必須手動(dòng)調(diào)用kfree()釋放。否則會(huì)導(dǎo)致內(nèi)存泄漏。
- kfree()時(shí)傳入的地址必須是kmalloc()的原始地址,不能在kmalloc()的基礎(chǔ)上偏移一定字節(jié)后傳入kfree()。
- kmalloc()和kfree()操作的內(nèi)存必須是同一類型的(如內(nèi)核空間和用戶空間的內(nèi)存不能混用)。
2.1.2 kzalloc
函數(shù)用于分配已清零的內(nèi)存。
#include <linux/slab.h>
void *kzalloc(size_t size, gfp_t flags);
kzalloc()函數(shù)會(huì)分配size字節(jié)大小的內(nèi)存,并在返回之前將分配的內(nèi)存清零。它使用伙伴系統(tǒng)(slab/slub分配器)分配內(nèi)存,所以返回的內(nèi)存是未初始化的,需要清零后才能使用。
如果內(nèi)存分配成功,kzalloc()會(huì)返回分配的內(nèi)存地址。如果失敗,則返回NULL。
參數(shù):
- size: 要分配內(nèi)存的大小,以字節(jié)為單位。
- flags: 分配條件,與kmalloc()相同,指定內(nèi)存類型、是否可以睡眠等。
與kmalloc()相比,kzalloc()有以下不同:
- kzalloc()會(huì)在返回內(nèi)存之前清零內(nèi)存,kmalloc()返回的內(nèi)存是未初始化的。
- 其他屬性基本相同,如:
- 使用相同的伙伴系統(tǒng)分配內(nèi)存。
- 返回的內(nèi)存需要通過(guò)kfree()釋放,否則會(huì)內(nèi)存泄漏。
- 傳入kfree()的地址必須是kzalloc()的返回地址,否則導(dǎo)致未定義行為。
- 同一內(nèi)存不能重復(fù)釋放,否則也會(huì)導(dǎo)致未定義行為。
- 要及時(shí)調(diào)用kfree()釋放內(nèi)存,否則容易OOM。
- 分配的內(nèi)存類型必須匹配(內(nèi)核空間與用戶空間內(nèi)存不能混用)。
kzalloc()很適用于需要已清零內(nèi)存的場(chǎng)景,可以保證返回的內(nèi)存是干凈的,以免產(chǎn)生未初始化變量等問(wèn)題。除此之外,其性質(zhì)與kmalloc()基本相同。
2.2 __get_free_page函數(shù)族
聲明如下:
#include <linux/gfp.h>
unsigned long __get_free_page(gfp_t flags);
作用
__get_free_page()函數(shù)用于分配一個(gè)物理頁(yè)面(page)的內(nèi)存。
參數(shù)
參數(shù)flags指定分配的條件,與kmalloc()中的flags參數(shù)相同,用于指定分配類型、是否可以睡眠等。常用的值是GFP_KERNEL和GFP_USER。
**返回值 **
__get_free_page()函數(shù)會(huì)從伙伴系統(tǒng)中分配一個(gè)物理頁(yè)面的內(nèi)存,并返回該頁(yè)面的虛擬地址。如果分配失敗,則返回0。
注意:
由于__get_free_page()直接分配物理內(nèi)存頁(yè)面,所以它的性能略高于kmalloc(),但是分配的內(nèi)存大小是固定的(一般為4KB),浪費(fèi)可能比較大。所以,只有當(dāng)需要較大塊內(nèi)存且性能要求較高時(shí),才會(huì)選擇__get_free_page()函數(shù)。
與__get_free_page()相關(guān)的其他函數(shù)有:
- free_page(addr): 釋放由__get_free_page()分配的內(nèi)存頁(yè)面,addr傳入__get_free_page()的返回地址。
- __get_dma_pages(): 分配DMA可訪問(wèn)的內(nèi)存頁(yè)面。
- __get_high_pages(): 分配高端內(nèi)存頁(yè)面。
- __get_low_pages(): 分配普通內(nèi)存頁(yè)面。
- __get_vm_area(): 分配不連續(xù)的物理頁(yè)面并映射為連續(xù)的虛擬地址空間,類似vmalloc()。
- __get_free_pages():分配2的n次方頁(yè),返回指向第一頁(yè)邏輯地址的指針。
- get_zeroed_page():只分配一頁(yè),讓其內(nèi)容填充0,返回指向其邏輯地址的指針
需要注意,這些函數(shù)直接分配物理內(nèi)存,所以使用不當(dāng)很容易導(dǎo)致內(nèi)存泄漏或未定義行為。 分配的內(nèi)存大小也比較固定,不夠靈活,因此只有在需要較大內(nèi)存塊且性能要求高的情況下才會(huì)選用。
2.2.1 free_page
函數(shù)用于釋放通過(guò)__get_free_page()分配的單個(gè)物理內(nèi)存頁(yè)面。
它的聲明如下:
#include <linux/gfp.h>
void free_page(unsigned long addr);
參數(shù)addr表示要釋放的物理頁(yè)面的虛擬地址,必須是__get_free_page()的返回值。
free_page()函數(shù)會(huì)釋放addr指向的物理內(nèi)存頁(yè)面,并將其返還給伙伴系統(tǒng)。
2.2.2 __get_free_pages()
函數(shù)用于分配多個(gè)連續(xù)的物理內(nèi)存頁(yè)面。
#include <linux/gfp.h>
unsigned long __get_free_pages(gfp_t flags, unsigned int order);
__get_free_pages()函數(shù)會(huì)從伙伴系統(tǒng)中分配2的order次冪個(gè)連續(xù)頁(yè)面的內(nèi)存,并返回第一個(gè)頁(yè)面的虛擬地址。如果分配失敗,則返回0。
參數(shù):
- flags: 分配條件,與__get_free_page()相同。
- order: 要分配頁(yè)面的個(gè)數(shù),以2的order次冪個(gè)頁(yè)面為單位。例如order為2表示4個(gè)頁(yè)面,order為3表示8個(gè)頁(yè)面,以此類推。
__get_free_pages()函數(shù)允許分配較大的內(nèi)存塊,連續(xù)的多個(gè)頁(yè)面。除此之外,其其他屬性與__get_free_page()基本相同:
- 也是直接分配物理內(nèi)存,性能高于kmalloc()但內(nèi)存分配大小較固定。
- 使用不當(dāng)也會(huì)導(dǎo)致內(nèi)存泄漏和未定義行為,需要謹(jǐn)慎使用。
- 也有對(duì)應(yīng)的free_pages()函數(shù)用于釋放分配的內(nèi)存。
- 也有其它類似函數(shù),如__get_dma_pages()等。
所以,__get_free_pages()函數(shù)在以下情況下會(huì)非常有用: - 需要分配較大的連續(xù)內(nèi)存塊(多個(gè)物理頁(yè)面)。
- 對(duì)性能有較高要求,不希望通過(guò)kmalloc()分配內(nèi)存后再手動(dòng)連接成較大塊。
- 需要的內(nèi)存大小可以通過(guò)order參數(shù)的2的n次冪大小很好滿足。
除此之外,對(duì)于更靈活或較小的內(nèi)存分配,kmalloc()函數(shù)會(huì)更加適用。所以兩者可以視情況而用,以達(dá)到最佳的效果。
2.2.3 free_pages
函數(shù)用于釋放通過(guò)__get_free_pages()分配的多個(gè)連續(xù)物理內(nèi)存頁(yè)面。
它的聲明如下:
#include <linux/gfp.h>
void free_pages(unsigned long addr, unsigned int order);
它在頭文件中聲明。
參數(shù):
- addr: 要釋放的第一個(gè)物理頁(yè)面的虛擬地址,必須是__get_free_pages()的返回值。
- order: 要釋放的頁(yè)面?zhèn)€數(shù),以2的order次冪個(gè)頁(yè)面為單位,必須與__get_free_pages()傳入的order參數(shù)大小相同。
free_pages()函數(shù)會(huì)釋放從addr開始的2的order次冪個(gè)物理內(nèi)存頁(yè)面,并將內(nèi)存返還給伙伴系統(tǒng)。
2.2.4 get_zeroed_page
函數(shù)用于分配一個(gè)已清零的物理內(nèi)存頁(yè)面。
#include <linux/gfp.h>
unsigned long get_zeroed_page(gfp_t flags);
get_zeroed_page()功能與__get_free_page()基本相同,但是它會(huì)在返回內(nèi)存頁(yè)面之前,將頁(yè)面清零。
參數(shù)
flags 指定分配條件,與__get_free_page()相同。
get_zeroed_page()會(huì)返回一個(gè)已清零的物理頁(yè)面虛擬地址,如果分配失敗則返回0。
與__get_free_page()一樣,get_zeroed_page()也需要注意:
- 返回的內(nèi)存頁(yè)面需要手動(dòng)通過(guò)free_page()釋放,防止內(nèi)存泄漏。
- 傳入free_page()的地址必須是get_zeroed_page()的返回地址,否則會(huì)導(dǎo)致未定義行為。
- 同一內(nèi)存頁(yè)面不能重復(fù)free,否則也會(huì)導(dǎo)致未定義行為。
- 要積極釋放物理內(nèi)存頁(yè)面,否則容易因分配和釋放不均衡導(dǎo)致OOM。
- 內(nèi)存頁(yè)面類型必須匹配(內(nèi)核空間與用戶空間內(nèi)存不能混用)。
- 直接操作物理內(nèi)存,使用不當(dāng)會(huì)導(dǎo)致各種問(wèn)題,需要特別小心。
get_zeroed_page()適用于需要已清零內(nèi)存的場(chǎng)景,除此之外,其性質(zhì)與__get_free_page()基本相同。
與get_zeroed_page()對(duì)應(yīng),我們還有:
- get_zeroed_pages(): 分配多個(gè)連續(xù)的已清零物理內(nèi)存頁(yè)面。
- kzalloc(): 在用戶空間分配已清零內(nèi)存,更加安全靈活,適用于絕大多數(shù)場(chǎng)景。
2.2.5 __get_dma_pages
函數(shù)用于分配DMA可訪問(wèn)的物理內(nèi)存頁(yè)面。
#include <linux/gfp.h>
unsigned long __get_dma_pages(gfp_t flags, unsigned int order);
__get_dma_pages()函數(shù)會(huì)從伙伴系統(tǒng)中分配2的order次冪個(gè)DMA可訪問(wèn)的物理內(nèi)存頁(yè)面,并返回第一個(gè)頁(yè)面的虛擬地址。如果分配失敗,則返回0。
參數(shù):
- flags: 分配條件,與__get_free_pages()相同,指定是否可以睡眠等。
- order: 要分配的頁(yè)面?zhèn)€數(shù),以2的order次冪個(gè)頁(yè)面為單位。
與__get_free_pages()類似,__get_dma_pages()也需要注意:
- 返回的內(nèi)存需要通過(guò)free_pages()釋放,以防止內(nèi)存泄漏。
- 傳入free_pages()的地址和order必須與__get_dma_pages()相同,否則會(huì)導(dǎo)致未定義行為。
- 同一內(nèi)存不能重復(fù)釋放,否則也會(huì)導(dǎo)致未定義行為。
- 要及時(shí)調(diào)用free_pages()釋放內(nèi)存,否則容易OOM。
- __get_dma_pages()分配的內(nèi)存只能用于DMA,不能在非DMA場(chǎng)景下使用。
除__get_dma_pages()之外,我們還有:
- get_dma_pages(): 分配已清零的DMA內(nèi)存頁(yè)面。
- dma_alloc_coherent(): 在用戶空間分配DMA內(nèi)存,更加安全和實(shí)用。
根據(jù)需求的不同,可以選擇底層的__get_dma_pages()或是更高層的dma_alloc_coherent(),得到最佳效果。
2.2.6 __get_high_pages()
函數(shù)用于分配高端內(nèi)存(high memory)的物理頁(yè)面。
#include <linux/gfp.h>
unsigned long __get_high_pages(gfp_t flags, unsigned int order);
__get_high_pages()函數(shù)會(huì)從伙伴系統(tǒng)中分配2的order次冪個(gè)高端內(nèi)存的物理頁(yè)面,并返回第一個(gè)頁(yè)面的虛擬地址。如果分配失敗,則返回0。
參數(shù):
- flags: 分配條件,與__get_free_pages()相同,指定是否可以睡眠等。
- order: 要分配頁(yè)面的個(gè)數(shù),以2的order次冪個(gè)頁(yè)面為單位。
高端內(nèi)存的物理地址范圍較大,超出普通內(nèi)存的直接映射范圍,所以訪問(wèn)高端內(nèi)存會(huì)較慢。但其優(yōu)點(diǎn)是較少使用,較容易分配較大的連續(xù)內(nèi)存塊。
__get_high_pages()函數(shù)在以下情況下很有用:
- 需要分配較大塊內(nèi)存(通過(guò)較大的order),并且對(duì)性能要求不高。
- 普通內(nèi)存空間不足,需要使用高端內(nèi)存。
- 僅用于I/O映射等不需要頻繁訪問(wèn)的場(chǎng)景。
與其他類似函數(shù)一樣,__get_high_pages()也需要注意:
1、返回的內(nèi)存需要通過(guò)free_pages()釋放,防止內(nèi)存泄漏。
2、傳入free_pages()的地址和order必須正確,否則導(dǎo)致未定義行為。
3、同一內(nèi)存不能重復(fù)釋放,否則也會(huì)導(dǎo)致未定義行為。
4、要及時(shí)釋放分配的內(nèi)存,否則容易OOM。
除__get_high_pages()之外,我們還有:
- get_high_pages(): 分配已清零的高端內(nèi)存頁(yè)面。
- kmalloc(GFP_HIGHUSER): 在用戶空間分配高端內(nèi)存,更加安全和實(shí)用。
2.2.7 __get_low_pages()
函數(shù)用于分配普通內(nèi)存(low memory)的物理頁(yè)面。
#include <linux/gfp.h>
unsigned long __get_low_pages(gfp_t flags, unsigned int order);
__get_low_pages()函數(shù)會(huì)從伙伴系統(tǒng)中分配2的order次冪個(gè)普通內(nèi)存的物理頁(yè)面,并返回第一個(gè)頁(yè)面的虛擬地址。如果分配失敗,則返回0。
參數(shù):
- flags: 分配條件,與__get_free_pages()相同,指定是否可以睡眠等。
- order: 要分配頁(yè)面的個(gè)數(shù),以2的order次冪個(gè)頁(yè)面為單位。
普通內(nèi)存的物理地址范圍較小,可以直接映射,所以訪問(wèn)速度較快。但其缺點(diǎn)是空間較小,不易分配較大的連續(xù)內(nèi)存塊。
__get_low_pages()函數(shù)在以下情況下很有用:
- 需要較快速度訪問(wèn)內(nèi)存,對(duì)性能有要求。
- 普通內(nèi)存空間足夠,不需要使用高端內(nèi)存。
- 訪問(wèn)頻繁,需要較快的速度。
與其他類似函數(shù)一樣,__get_low_pages()也需要注意:
- 返回的內(nèi)存需要通過(guò)free_pages()釋放,防止內(nèi)存泄漏。
- 傳入free_pages()的地址和order必須正確,否則導(dǎo)致未定義行為。
- 同一內(nèi)存不能重復(fù)釋放,否則也會(huì)導(dǎo)致未定義行為。
- 要及時(shí)釋放分配的內(nèi)存,否則容易OOM。
除__get_low_pages()之外,我們還可以使用:
- get_low_pages(): 分配已清零的普通內(nèi)存頁(yè)面。
- kmalloc(): 在用戶空間分配普通內(nèi)存,更加安全和實(shí)用,適用于絕大多數(shù)場(chǎng)景。
2.3 vmalloc
vmalloc()函數(shù)用于分配不連續(xù)的物理內(nèi)存,并將其映射為連續(xù)的虛擬地址空間。
#include <linux/vmalloc.h>
void *vmalloc(unsigned long size);
vmalloc()函數(shù)會(huì)分配size字節(jié)的物理內(nèi)存,并建立從連續(xù)虛擬地址到不連續(xù)物理地址的映射,從而提供一塊連續(xù)的虛擬地址空間給調(diào)用者。
如果分配成功,vmalloc()返回連續(xù)虛擬地址空間的起始地址。如果失敗,返回NULL。
參數(shù)
size指定要分配的內(nèi)存大小,以字節(jié)為單位。
vmalloc()有以下主要特征:
- 分配的物理內(nèi)存不連續(xù),但提供連續(xù)的虛擬地址空間,方便使用。
- 使用頁(yè)表來(lái)建立虛擬地址到物理地址的映射,所以分配和訪問(wèn)速度較慢。
- 分配的內(nèi)存需要通過(guò)vfree()釋放,否則會(huì)產(chǎn)生內(nèi)存泄漏。
- 傳入vfree()的地址必須是vmalloc()的返回地址,否則會(huì)導(dǎo)致未定義行為。
- 同一內(nèi)存塊不能重復(fù)釋放,否則也會(huì)導(dǎo)致未定義行為。
- 要及時(shí)調(diào)用vfree()釋放內(nèi)存,否則容易導(dǎo)致OOM。
- 分配的內(nèi)存類型必須匹配(內(nèi)核空間與用戶空間內(nèi)存不能混用)。
vmalloc()適用于以下情況:
- 需要較大塊內(nèi)存(超過(guò)128K)時(shí),kmalloc()就難以滿足需求。
- 內(nèi)核映像大小(initrd大小)較大時(shí),也需要用vmalloc()分配。
- 在ioremap()之后,也會(huì)使用vmalloc()映射分配的I/O內(nèi)存。
- 訪問(wèn)速度要求不高,以空間為主,可以接受頁(yè)表帶來(lái)的性能損耗。
2.3.1 vfree
vfree()函數(shù)用于釋放通過(guò)vmalloc()分配的虛擬內(nèi)存。
它的聲明如下:
#include <linux/vmalloc.h>
void vfree(const void *addr);
vfree()函數(shù)會(huì)釋放傳入的虛擬地址addr開始的內(nèi)存塊。該內(nèi)存塊必須是通過(guò)vmalloc()分配的,否則會(huì)導(dǎo)致未定義行為。
參數(shù)addr是要釋放的虛擬內(nèi)存塊的起始地址,必須是vmalloc()的返回值。
vfree()有以下主要特征:
- 會(huì)釋放vmalloc()分配的虛擬連續(xù)地址到物理不連續(xù)地址的映射。
- addr傳入的地址必須正確,否則會(huì)導(dǎo)致未定義行為。
- 同一內(nèi)存塊不能重復(fù)釋放,否則也會(huì)導(dǎo)致未定義行為。
- 要及時(shí)調(diào)用vfree()釋放內(nèi)存,否則會(huì)產(chǎn)生內(nèi)存泄漏,導(dǎo)致OOM。
- 只能釋放與vmalloc()相匹配的內(nèi)存,否則結(jié)果未定義。
- 會(huì)釋放vmalloc()為映射創(chuàng)建的頁(yè)表等數(shù)據(jù)結(jié)構(gòu),所以調(diào)用vfree()后不能再訪問(wèn)該內(nèi)存。
vfree()主要用于釋放較大塊的內(nèi)存,一般在以下情況使用:
- 不再需要vmalloc()分配的大塊內(nèi)存時(shí),需要將其釋放。
- 加載結(jié)束后的initrd需要釋放內(nèi)存。
- 模塊卸載時(shí)需要釋放內(nèi)核映像等占用的大量?jī)?nèi)存。
- 其它分配大塊連續(xù)虛擬內(nèi)存后,使用結(jié)束需要釋放場(chǎng)景。
2.4 kmalloc & vmalloc 的比較
kmalloc()、kzalloc()、vmalloc() 的共同特點(diǎn)是:
- 用于申請(qǐng)內(nèi)核空間的內(nèi)存;
- 內(nèi)存以字節(jié)為單位進(jìn)行分配;
- 所分配的內(nèi)存虛擬地址上連續(xù);
kmalloc()、kzalloc()、vmalloc() 的區(qū)別是:
- kzalloc 是強(qiáng)制清零的 kmalloc 操作;(以下描述不區(qū)分 kmalloc 和 kzalloc)
- kmalloc 分配的內(nèi)存大小有限制(128KB),而 vmalloc 沒(méi)有限制;
- kmalloc 可以保證分配的內(nèi)存物理地址是連續(xù)的,但是 vmalloc 不能保證;
- kmalloc 分配內(nèi)存的過(guò)程可以是原子過(guò)程(使用 GFP_ATOMIC),而 vmalloc 分配內(nèi)存時(shí)則可能產(chǎn)生阻塞;
- kmalloc 分配內(nèi)存的開銷小,因此 kmalloc 比 vmalloc 要快;
一般情況下,內(nèi)存只有在要被 DMA 訪問(wèn)的時(shí)候才需要物理上連續(xù),但為了性能上的考慮,內(nèi)核中一般使用 kmalloc(),而只有在需要獲得大塊內(nèi)存時(shí)才使用 vmalloc()。
3、IO訪問(wèn)
設(shè)備通常會(huì)提供一組寄存器來(lái)控制設(shè)備、讀寫設(shè)備、獲取設(shè)備狀態(tài)。這些寄存器的訪問(wèn)入口可能位于獨(dú)立的I/O空間中,具有獨(dú)立的訪問(wèn)地址,也可能位于內(nèi)存空間中,與內(nèi)存地址共享內(nèi)存地址空間。
當(dāng)設(shè)備的寄存器入口位于獨(dú)立的IO空間時(shí),通常被稱為I/O端口;對(duì)應(yīng)的外設(shè)訪問(wèn)方式也稱為IO端口法。
當(dāng)設(shè)備的寄存器入口位于內(nèi)存空間時(shí),對(duì)應(yīng)的內(nèi)存空間被稱為I/O內(nèi)存。對(duì)應(yīng)的外設(shè)訪問(wèn)方式也稱為IO內(nèi)存法。
3.1 IO端口法
在x86系統(tǒng)上,linux提供了一系列的函數(shù)用來(lái)訪問(wèn)定位于IO空間的端口。
這些函數(shù)都是用于I/O端口操作的內(nèi)聯(lián)匯編函數(shù),聲明如下:
unsigned char inb(unsigned short port);
void outb(unsigned char value, unsigned short port);
unsigned short inw(unsigned short port);
void outw(unsigned short value, unsigned short port);
unsigned int inl(unsigned short port);
void outl(unsigned int value, unsigned short port);
void insb(unsigned short port, void *addr, unsigned long count);
void outsb(unsigned short port, const void *addr, unsigned long count);
void insw(unsigned short port, void *addr, unsigned long count);
void outsw(unsigned short port, const void *addr, unsigned long count);
void insl(unsigned short port, void *addr, unsigned long count);
void outsl(unsigned short port, const void *addr, unsigned long count);
這些函數(shù)的作用如下:
- inb/outb: 讀/寫1個(gè)字節(jié)
- inw/outw: 讀/寫2個(gè)字節(jié)(16位)
- inl/outl: 讀/寫4個(gè)字節(jié)(32位)
- insb/outsb: 讀/寫count個(gè)字節(jié)
- insw/outsw: 讀/寫count個(gè)16位字
- insl/outsl: 讀/寫count個(gè)32位字
port參數(shù)指定I/O端口地址,value是要寫入的值,addr是存儲(chǔ)讀取數(shù)據(jù)的緩沖區(qū)地址,count是要讀/寫的數(shù)據(jù)量。
3.2 IO內(nèi)存法
在arm系統(tǒng)中,外設(shè)的寄存器入口是參與到內(nèi)存的統(tǒng)一編址空間的,也即訪問(wèn)它們就像對(duì)一個(gè)內(nèi)存空間的讀寫是一樣的。
在linux系統(tǒng)的內(nèi)核中訪問(wèn)I/O內(nèi)存之前,需首先使用ioremap()函數(shù)將設(shè)備所處的物理地址映射到虛擬地址上。
3.2.1 ioremap
ioremap()函數(shù)用于映射物理I/O內(nèi)存到虛擬地址空間。
#include <asm/io.h>
void __iomem *ioremap(phys_addr_t offset, size_t size);
ioremap()函數(shù)會(huì)創(chuàng)建從offset開始的size字節(jié)I/O內(nèi)存的虛擬映射。如果映射成功,它會(huì)返回虛擬地址空間的起始地址,如果失敗則返回NULL。
參數(shù):
- offset: 要映射的物理I/O內(nèi)存的起始物理地址。
- size: 要映射的I/O內(nèi)存大小,以字節(jié)為單位。
__iomem是一個(gè)類型修飾符,用于指明某個(gè)指針指向的內(nèi)存區(qū)域是I/O內(nèi)存。
在C語(yǔ)言中,指針只有兩種類型:
- 普通指針:指向普通內(nèi)存(RAM)
- 函數(shù)指針:指向函數(shù)
但是在內(nèi)核開發(fā)中,還需要操作I/O內(nèi)存。為了區(qū)分普通指針和指向I/O內(nèi)存的指針,就使用__iomem類型修飾符。
定義__iomem類型的指針時(shí),需要在原有的指針類型前加上__iomem,如:
char __iomem *io_ptr; // 指向I/O內(nèi)存的字符指針
ioremap()有以下主要特征:
- 它只能映射物理I/O內(nèi)存,不能映射普通內(nèi)存。
- 返回的虛擬地址可以像普通內(nèi)存一樣訪問(wèn)I/O內(nèi)存,但實(shí)際上是通過(guò)映射實(shí)現(xiàn)的。
- 需要通過(guò)iounmap()釋放映射,否則會(huì)產(chǎn)生內(nèi)存泄漏。
- 傳入iounmap()的地址必須是ioremap()的返回地址,否則會(huì)導(dǎo)致未定義行為。
- 同一I/O內(nèi)存區(qū)不能重復(fù)映射,否則也會(huì)導(dǎo)致未定義行為。
- 要及時(shí)調(diào)用iounmap()釋放映射,否則容易導(dǎo)致OOM。
- 映射過(guò)程需要消耗一定的CPU和內(nèi)存資源。
ioremap()主要用于以下場(chǎng)景:
- 需要從用戶空間訪問(wèn)物理I/O內(nèi)存時(shí),可以先通過(guò)ioremap()映射,然后像訪問(wèn)普通內(nèi)存一樣進(jìn)行訪問(wèn)。
- 驅(qū)動(dòng)程序需要在中斷/底半部里訪問(wèn)I/O內(nèi)存,這時(shí)候也需要先建立映射。
- 需要長(zhǎng)期頻繁訪問(wèn)I/O內(nèi)存,映射后可以提高訪問(wèn)效率。
- 當(dāng)I/O內(nèi)存只有物理地址,需要建立映射才能在內(nèi)核訪問(wèn)。
除ioremap()之外,我們還有ioremap_nocache()和ioremap_cache()函數(shù)。根據(jù)不同的Cache需求,選擇不同的映射函數(shù),可以獲得最佳效果。
3.2.2 iounmap
iounmap()函數(shù)用于釋放通過(guò)ioremap()建立的I/O內(nèi)存虛擬映射。
#include <asm/io.h>
void iounmap(volatile void __iomem *addr);
iounmap()函數(shù)會(huì)釋放傳入的I/O內(nèi)存虛擬地址addr開始的映射。addr必須是ioremap()返回的映射起始地址,否則會(huì)導(dǎo)致未定義行為。
參數(shù)addr是要釋放映射的虛擬地址。
iounmap()有以下主要特征:
- 它只能釋放通過(guò)ioremap()建立的I/O內(nèi)存映射,不能釋放普通內(nèi)存。
- addr傳入的地址必須正確,否則會(huì)導(dǎo)致未定義行為。
- 同一I/O內(nèi)存區(qū)域的映射不能重復(fù)釋放,否則也會(huì)導(dǎo)致未定義行為。
- 要及時(shí)調(diào)用iounmap()釋放映射,否則會(huì)產(chǎn)生內(nèi)存泄漏,并占用MMU資源。
- 釋放映射后,該虛擬地址范圍不可訪問(wèn),否則未定義。
- 會(huì)釋放映射過(guò)程中占用的頁(yè)表、TLB等資源。
iounmap()主要用于以下場(chǎng)景:
- 不再需要訪問(wèn)I/O內(nèi)存時(shí),需要將其映射釋放。
- 模塊卸載時(shí)需要釋放建立的I/O內(nèi)存映射。
- 釋放驅(qū)動(dòng)程序不再使用的I/O內(nèi)存虛擬地址。
- 卸載initrd等需要釋放映射的大塊I/O內(nèi)存。
3.2.3 readb/readw/readl和writeb/writew/writel
這些函數(shù)都是用于內(nèi)存讀/寫的匯編函數(shù),聲明和作用如下:
#include <asm/io.h>
unsigned char readb(const volatile void __iomem *addr);
unsigned short readw(const volatile void __iomem *addr);
unsigned int readl(const volatile void __iomem *addr);
void writeb(u8 value, volatile void __iomem *addr);
void writew(u16 value, volatile void __iomem *addr);
void writel(u32 value, volatile void __iomem *addr);
- readb/writeb: 讀/寫1個(gè)字節(jié)
- readw/writew: 讀/寫2個(gè)字節(jié)(16位)
- readl/writel: 讀/寫4個(gè)字節(jié)(32位)
這些函數(shù)主要用于:
- 訪問(wèn)I/O內(nèi)存:當(dāng)某段物理內(nèi)存被映射為I/O內(nèi)存時(shí),可以使用這些函數(shù)讀/寫。
- 訪問(wèn)I/O端口:I/O端口的內(nèi)存映射基址也被定義為__iomem類型,可以用這些函數(shù)讀寫端口。
- 訪問(wèn)普通內(nèi)存:對(duì)于某些體系結(jié)構(gòu),直接使用這些匯編函數(shù)訪問(wèn)內(nèi)存會(huì)效率更高。但對(duì)于大部分體系結(jié)構(gòu),使用C語(yǔ)言的指針直接訪問(wèn)內(nèi)存會(huì)更通用和可移植。
這些函數(shù)的參數(shù)如下:
- addr: 要訪問(wèn)的內(nèi)存地址,必須是__iomem類型。
- value: 要寫入的值,必須與訪問(wèn)內(nèi)存寬度匹配(寬度由函數(shù)名中的b/w/l指定)。
這些函數(shù)需要注意的地方:
- addr必須是I/O內(nèi)存或I/O端口的地址,否則會(huì)產(chǎn)生未定義行為。
- value的類型必須與讀/寫寬度匹配,否則也會(huì)產(chǎn)生未定義行為。
- 要按正確的對(duì)齊 accessing I/O registers 要按正確字節(jié)對(duì)齊方式訪問(wèn)I/O寄存器和內(nèi)存。
- 如果編寫模塊驅(qū)動(dòng),記得聲明為__init和__iomem等,提高可移植性。
- 要考慮端序問(wèn)題,根據(jù)不同的體系結(jié)構(gòu)選擇合適的訪問(wèn)寬度。
3.3 IO內(nèi)存訪問(wèn)實(shí)例
在FS4412華清的ARM開發(fā)板上,用字符驅(qū)動(dòng)的方式控制板上的LED燈的亮滅。應(yīng)用層發(fā)出控制指令,控制LED。
電路原理圖
這里每個(gè)SOC上的GPIO引腳(GPX2_7 ,GPX1_0,GPF_4,GPF3_5)分別控制著LED2-LED5。當(dāng)GPIO引腳高電平時(shí),開關(guān)三極管導(dǎo)通,LED二級(jí)管發(fā)光。反之則關(guān)閉。因此,驅(qū)動(dòng)程序主要是通過(guò)控制GPIO引腳輸出低電平或高電平來(lái)控制LED的亮滅。
GPIO端口的控制是通過(guò)對(duì)應(yīng)的寄存器的控制實(shí)現(xiàn)的,通過(guò)查閱芯片手冊(cè),可以知道GPIO端口對(duì)應(yīng)的寄存器地址,寄存器設(shè)置規(guī)則等。以GPX2_7的寄存器為例,其基地址,偏移量,狀態(tài)位的狀態(tài)如下:
以GPX2_7口為例,要設(shè)置為輸出模式,就要把寄存器GPX2CON(地址為01100 0c40)的第28-31位設(shè)為0x01。如果要讓該端口輸出高電平,則需要把寄存器GPX2DAT(地址為0x1100 0c44)的第七位設(shè)為1,輸出低電平則把GPX2DAT的第七位設(shè)為0。
下面是涉及到LED的所有端口的地址等信息, 查閱至SOC芯片手冊(cè)
GPIO口 | led號(hào) | 控制寄存器 | 地址 | 控制字對(duì)應(yīng)位號(hào) | 設(shè)置2進(jìn)制值 | 數(shù)據(jù)寄存器 | 地址 | 對(duì)應(yīng)位號(hào) |
---|---|---|---|---|---|---|---|---|
GPX2_7 | led2 | GPX2CON | 0x11000C40 | 28~31 | 0001 | GPX2DAT | 0x11000C44 | 7 |
GPX1_0 | led3 | GPX1CON | 0x11000C20 | 0~3 | 0001 | GPX1DAT | 0x11000C24 | 0 |
GPF3_4 | led4 | GPF3CON | 0x114001E0 | 16~19 | 0001 | GPF3DAT | 0x114001E4 | 4 |
GPF3_5 | led5 | GPF3CON | 0x114001E0 | 20~23 | -0001 | GPF3DAT | 0x114001E4 | 5 |
public.h頭文件
/********
public.h
*******/
#ifndef _H_PUBLIC_
#define _H_PUBLIC_
#include <asm/ioctl.h>
#define LED_ON _IO('a',1)
#define LED_OFF _IO('a',0)
#endif
驅(qū)動(dòng)代碼:
/*************************************************************************
> File Name: led-access.c
************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <asm/io.h>
#include "public.h"
#define GPX2CON 0x11000c40 //led2對(duì)應(yīng)控制寄存器的地址
#define GPX2DAT 0x11000c44 //led2對(duì)應(yīng)數(shù)據(jù)寄存器的地址
/*1、定義重要的變量及結(jié)構(gòu)體*/
struct x_dev_t {
struct cdev my_dev; //cdev設(shè)備描述結(jié)構(gòu)體變量
atomic_t have_open; //記錄驅(qū)動(dòng)是否被打開的原子變量
unsigned long volatile __iomem *gpx2con; //指向?qū)?yīng)控制寄存器的虛擬地址
unsigned long volatile __iomem *gpx2dat; //指向?qū)?yīng)數(shù)據(jù)寄存器的虛擬地址
};
struct x_dev_t *pcdev;
/*所有驅(qū)動(dòng)函數(shù)聲明*/
int open (struct inode *, struct file *);
int release (struct inode *, struct file *);
long unlocked_ioctl (struct file *pf, unsigned int cmd, unsigned long arg);
//驅(qū)動(dòng)操作函數(shù)結(jié)構(gòu)體,成員函數(shù)為需要實(shí)現(xiàn)的設(shè)備操作函數(shù)指針
//簡(jiǎn)單版的模版里,只寫了open與release兩個(gè)操作函數(shù)。
struct file_operations fops={
.owner = THIS_MODULE,
.open = open,
.release = release,
.unlocked_ioctl = unlocked_ioctl,
};
void led_init(void){
//設(shè)置GPX2CON的28-31位為0b0001,輸出模式
pcdev->gpx2con = ioremap(GPX2CON,4);
pcdev->gpx2dat = ioremap(GPX2DAT,4);
writel((readl(pcdev->gpx2con) & (~(0xf << 28))) | (0x1 << 28) , pcdev->gpx2con );
}
void led_cntl(int cmd){
if (cmd ){ //開
writel(readl(pcdev->gpx2dat)|(1 << 7),pcdev->gpx2dat );
}else{
writel(readl(pcdev->gpx2dat)&(~(1<< 7)), pcdev->gpx2dat);
}
}
static int __init my_init(void){
int unsucc =0;
dev_t devno;
int major,minor;
pcdev = kzalloc(sizeof(struct x_dev_t), GFP_KERNEL);
/*2、創(chuàng)建 devno */
unsucc = alloc_chrdev_region(&devno , 0 , 1 , "led2");
if (unsucc){
printk(" creating devno faild\n");
return -1;
}
major = MAJOR(devno);
minor = MINOR(devno);
printk("devno major = %d ; minor = %d;\n",major , minor);
/*3、初始化 cdev結(jié)構(gòu)體,并將cdev結(jié)構(gòu)體與file_operations結(jié)構(gòu)體關(guān)聯(lián)起來(lái)*/
/*這樣在內(nèi)核中就有了設(shè)備描述的結(jié)構(gòu)體cdev,以及設(shè)備操作函數(shù)的調(diào)用集合file_operations結(jié)構(gòu)體*/
cdev_init(&pcdev->my_dev , &fops);
pcdev->my_dev.owner = THIS_MODULE;
/*4、注冊(cè)cdev結(jié)構(gòu)體到內(nèi)核鏈表中*/
unsucc = cdev_add(&pcdev->my_dev,devno,1);
if (unsucc){
printk("cdev add faild \n");
return 1;
}
//初始化原子量have_open為1
atomic_set(&pcdev->have_open,1);
//初始化led2
led_init();
printk("the driver led2 initalization completed\n");
return 0;
}
static void __exit my_exit(void)
{
cdev_del(&pcdev->my_dev);
unregister_chrdev_region(pcdev->my_dev.dev , 1);
printk("***************the driver timer-second exit************\n");
}
/*5、驅(qū)動(dòng)函數(shù)的實(shí)現(xiàn)*/
/*file_operations結(jié)構(gòu)全成員函數(shù).open的具體實(shí)現(xiàn)*/
int open(struct inode *pnode , struct file *pf){
struct x_dev_t *p = container_of(pnode->i_cdev,struct x_dev_t , my_dev);
pf->private_data = (void *)p;
//在open函數(shù)中對(duì)原子量have_open進(jìn)行減1并檢測(cè)。=0,允許打開文件,<0則不允許打開
if (atomic_dec_and_test(&p->have_open)){
printk("led2 driver is opened\n");
return 0 ;
}else{
printk("device led2 can't be opened again\n");
atomic_inc(&p->have_open);//原子量=-1,記得這里要把原子量加回到0
return -1;
}
}
/*file_operations結(jié)構(gòu)全成員函數(shù).release的具體實(shí)現(xiàn)*/
int release(struct inode *pnode , struct file *pf){
struct x_dev_t *p = (struct x_dev_t *)pf->private_data;
printk("led2 is closed \n");
iounmap(p->gpx2con);
iounmap(p->gpx2dat);
atomic_set(&p->have_open,1);
return 0;
}
long unlocked_ioctl (struct file *pf, unsigned int cmd, unsigned long arg){
switch(cmd){
case LED_ON:
led_cntl(1);
break;
case LED_OFF:
led_cntl(0);
break;
default:
break;
}
return 0;
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
應(yīng)用層測(cè)試代碼:
/*************************************************************************
> File Name: test.c
> Created Time: Wed 19 Apr 2023 02:33:42 PM CST
************************************************************************/
#include<stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "public.h"
int main (int argc , char **argv){
int fd0;
if (argc <2){
printf("argument is too less\n");
return -1;
}else{
fd0 = open(argv[1] , O_RDONLY );
while (fd0){
printf("led on......\n");
ioctl(fd0,LED_ON);
sleep(2);
printf("led off......\n");
ioctl(fd0,LED_OFF);
sleep(2);
}
}
close(fd0);
return 0;
}
4、將設(shè)備地址映射到用戶空間
4.1 應(yīng)用層接口
4.1.1 函數(shù)mmap
mmap函數(shù)的聲明如下:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
參數(shù):
- addr:映射區(qū)的起始地址,一般設(shè)置為NULL讓系統(tǒng)自動(dòng)分配地址
- length:映射區(qū)的長(zhǎng)度,必須大于0
- prot:映射區(qū)的保護(hù)模式,可以是PROT_READ、PROT_WRITE、PROT_EXEC等
prot參數(shù)表示內(nèi)存映射區(qū)的訪問(wèn)權(quán)限,它可以取的值有:
- PROT_READ: 表示內(nèi)存映射區(qū)具有讀權(quán)限,可以讀取內(nèi)存映射區(qū)中的數(shù)據(jù)。
- PROT_WRITE: 表示內(nèi)存映射區(qū)具有寫權(quán)限,可以向內(nèi)存映射區(qū)寫入數(shù)據(jù)。
- PROT_EXEC: 表示內(nèi)存映射區(qū)具有執(zhí)行權(quán)限,映射區(qū)中的數(shù)據(jù)可以被執(zhí)行。
- PROT_NONE: 表示內(nèi)存映射區(qū)沒(méi)有任何訪問(wèn)權(quán)限。
這些值可以通過(guò)|操作符組合使用
- flags:映射類型,可以是MAP_PRIVATE、MAP_SHARED、MAP_ANONYMOUS等
具體flags可以取的值:
- MAP_PRIVATE: 表示映射的內(nèi)存區(qū)域只能被當(dāng)前進(jìn)程訪問(wèn),父進(jìn)程創(chuàng)建的映射會(huì)被子進(jìn)程繼承。寫時(shí)復(fù)制技術(shù)使父子進(jìn)程的內(nèi)存區(qū)域互不影響。
- MAP_SHARED: 表示映射的內(nèi)存區(qū)域能被所有映射該區(qū)域的進(jìn)程訪問(wèn)。數(shù)據(jù)的寫入會(huì)影響其他進(jìn)程訪問(wèn)的數(shù)據(jù)。
- MAP_ANONYMOUS: 表示映射匿名內(nèi)存區(qū)域,而不是文件的內(nèi)容。
- MAP_FIXED: 表示使用addr參數(shù)指定的內(nèi)存地址,如果該地址已經(jīng)被占用或無(wú)法訪問(wèn),會(huì)導(dǎo)致mmap調(diào)用失敗。
- MAP_GROWSDOWN: 表示內(nèi)存映射區(qū)可以向下擴(kuò)展,擴(kuò)展后的區(qū)域會(huì)與當(dāng)前區(qū)域相鄰,并具有相同的訪問(wèn)權(quán)限。
- MAP_DENYWRITE: 表示其他進(jìn)程將無(wú)法對(duì)該區(qū)域進(jìn)行寫操作,而當(dāng)前進(jìn)程仍然可以進(jìn)行寫操作。
- fd:被映射的文件描述符,如果映射匿名區(qū)則為-1
- offset:文件映射的偏移量
offset參數(shù)表示文件映射時(shí)的偏移量。它有以下幾個(gè)作用:
- 指定文件映射時(shí)的起始位置。例如文件大小為100字節(jié),offset設(shè)置為20,那么mapping的區(qū)域就從文件偏移量20開始,長(zhǎng)度為length指定的大小。
- 可以實(shí)現(xiàn)對(duì)同一個(gè)文件映射多次的目的。例如第一次mapping offset設(shè)置為0,length為60,得到一塊內(nèi)存區(qū)域。第二次mapping offset設(shè)置為60,length也為60,就可以獲取文件剩余部分的映射。
- 實(shí)現(xiàn)對(duì)文件的部分區(qū)域映射。設(shè)置offset和length可以映射文件中的一定區(qū)域,實(shí)現(xiàn)部分映射。
mmap函數(shù)的作用是建立內(nèi)存映射,將文件或者匿名區(qū)域映射到調(diào)用進(jìn)程的地址空間。這有以下用途:
- 文件映射:將文件內(nèi)容映射到內(nèi)存,實(shí)現(xiàn)文件I/O訪問(wèn),提高性能。
- 匿名映射:獲取一塊匿名內(nèi)存區(qū)域,實(shí)現(xiàn)進(jìn)程間通信。
- 設(shè)備映射:將設(shè)備內(nèi)存區(qū)域映射到進(jìn)程,實(shí)現(xiàn)設(shè)備I/O。
針對(duì)以上三種用途的第一種文件映射,舉例
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
int fd = open("file.txt", O_RDONLY);
// 映射偏移0后size為30的區(qū)域
char *p1 = mmap(NULL, 30, PROT_READ, MAP_PRIVATE, fd, 0);
// 映射偏移30后size為20的區(qū)域
char *p2 = mmap(NULL, 20, PROT_READ, MAP_PRIVATE, fd, 30);
// 多次映射實(shí)現(xiàn)了對(duì)同一個(gè)文件不同區(qū)域的訪問(wèn)
printf("%s\n", p1);
printf("%s\n", p2);
munmap(p1, 30);
munmap(p2, 20);
close(fd);
}
這個(gè)例子打開file.txt文件,第一次mmap偏移量設(shè)置為0,映射前30個(gè)字節(jié),得到p1指向的內(nèi)存區(qū)域。
第二次mmap偏移量設(shè)置為30,映射30-50個(gè)字節(jié),得到p2指向的內(nèi)存區(qū)域。
這樣通過(guò)兩次mmap就可以分別訪問(wèn)文件不同的區(qū)域。offset參數(shù)實(shí)現(xiàn)了多次mmap的目的。
針對(duì)以上三種用途的第二種匿名映射,舉例
匿名映射:獲取一塊匿名內(nèi)存區(qū)域,實(shí)現(xiàn)進(jìn)程間通信。
c
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char *p = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
sprintf(p, "Hello");
pid_t pid = fork();
if (pid == 0) { // 子進(jìn)程
printf("Child see: %s\n", p);
strcpy(p, "Hi");
} else { // 父進(jìn)程
sleep(1); // 等待子進(jìn)程執(zhí)行
printf("Parent see: %s\n", p);
}
}
針對(duì)以上三種用途第三種設(shè)備映射,舉例
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
int fd = open("/dev/mem", O_RDWR);
// 獲取總線的物理內(nèi)存起始地址
unsigned long phy_base = 0x1000;
// 獲取要映射的內(nèi)存大小
size_t map_size = 0x100;
// 偏移量設(shè)置為phy_base,映射物理地址0x1000開始的256字節(jié)內(nèi)存區(qū)域
char *p = mmap(NULL, map_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, (off_t)phy_base);
*p = 0x12; // 向mapped區(qū)域第一個(gè)字節(jié)寫0x12
// 讀取物理內(nèi)存0x1004這個(gè)地址的值
unsigned char val = *((unsigned char*)(p + 0x4));
printf("val = %x\n", val);
munmap(p, map_size);
close(fd);
}
4.1.2 munmap函數(shù)
munmap函數(shù)用于解除內(nèi)存映射,釋放mmap所分配的資源。其函數(shù)原型為:
#include <sys/mman.h>
int munmap(void *addr, size_t length);
- addr: 要解除映射的內(nèi)存區(qū)域起始地址,必須與mmap調(diào)用時(shí)的addr參數(shù)相同。
- length: 要解除映射的內(nèi)存區(qū)域長(zhǎng)度,必須與mmap調(diào)用時(shí)的length參數(shù)相同。
munmap會(huì)釋放通過(guò)mmap分配的所有資源,包括:
- 釋放內(nèi)存頁(yè)表項(xiàng)
- 釋放物理內(nèi)存
- 釋放文件資源(如果映射的是文件)
所以當(dāng)一個(gè)內(nèi)存映射區(qū)域不再需要時(shí),必須調(diào)用munmap function釋放其資源,否則可能導(dǎo)致資源泄露。
4.2 內(nèi)核層
一般情況下,用戶空間是不可能也不應(yīng)該直接訪問(wèn)設(shè)備的,但是有時(shí)候?yàn)榱颂岣咝?,需要將用戶的一段?nèi)存與設(shè)備內(nèi)存關(guān)聯(lián),當(dāng)用戶訪問(wèn)用戶空間這段地址范圍時(shí),相當(dāng)于對(duì)設(shè)備進(jìn)行訪問(wèn)。比如對(duì)顯示適配器一類的設(shè)備非常有用。
這時(shí),在驅(qū)動(dòng)的開發(fā)時(shí),就需要用到struct file_operations結(jié)構(gòu)體的一個(gè)成員.mmap函數(shù),來(lái)實(shí)現(xiàn)該種映射。
該函數(shù)與應(yīng)用層的mmap函數(shù)相對(duì)應(yīng)。應(yīng)用層執(zhí)行的mmap函數(shù)取終是調(diào)用.mmap函數(shù)實(shí)現(xiàn)的。具體內(nèi)核做了以下:
4.2.1 mmap函數(shù)
file_operations結(jié)構(gòu)體中的mmap成員對(duì)應(yīng)內(nèi)核中的文件mmap操作。其原型為:
int (*mmap) (struct file *filp, struct vm_area_struct *vma);
- filp: 要mmap的文件句柄
- vma: 包含映射區(qū)域信息的vm_area_struct結(jié)構(gòu)體指針
這個(gè)函數(shù)用來(lái)映射文件內(nèi)容到進(jìn)程地址空間,實(shí)現(xiàn)文件內(nèi)容的共享映射。
在用戶空間調(diào)用mmap系統(tǒng)調(diào)用時(shí),內(nèi)核會(huì)調(diào)用文件inode的f_op->mmap來(lái)映射文件。
使用例子:
#include <linux/mm.h>
#include <linux/file.h>
struct file_operations my_fops = {
.mmap = my_mmap,
...
}
int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
unsigned long phys = __pa(someaddr);
unsigned long len = vma->vm_end - vma->vm_start;
if (vma->vm_flags & VM_WRITE) {
// file is writable
} else {
// file is read only
}
// map the file contents to process VM
remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
len, vma->vm_page_prot);
// 更新文件大小(如果文件正在增長(zhǎng))
filp->f_mapping->host->i_size = ...
}
注意:
對(duì)filp->f_mapping->host進(jìn)行修改的代碼必須鎖住文件i_mutex避免競(jìng)爭(zhēng)。
remap_pfn_range函數(shù)會(huì)建立物理頁(yè)到虛擬內(nèi)存區(qū)域的映射。
4.2.2 vm_area_struct結(jié)構(gòu)體
內(nèi)核在調(diào)用.mmap函數(shù)之前會(huì)根據(jù)應(yīng)用層的傳參完成VMA結(jié)構(gòu)體的構(gòu)造,并把VMA結(jié)構(gòu)體傳遞給.mmap函數(shù)。
VMA結(jié)構(gòu)體就是vm_area_struct。表示進(jìn)程的一個(gè)虛擬內(nèi)存區(qū)域,結(jié)構(gòu)體主要內(nèi)容如下如下:
struct vm_area_struct {
/* The first cache line has the info for VMA tree walking. */
unsigned long vm_start; /* Our start address within vm_mm. */
unsigned long vm_end; /* The first byte after our end address
within vm_mm. */
/* linked list of VM areas per task, sorted by address */
struct vm_area_struct *vm_next, *vm_prev;
struct rb_node vm_rb;
/*
* Largest free memory gap in bytes to the left of this VMA.
* Either between this VMA and vma->vm_prev, or between one of the
* VMAs below us in the VMA rbtree and its ->vm_prev. This helps
* get_unmapped_area find a free area of the right size.
*/
unsigned long rb_subtree_gap;
/* Second cache line starts here. */
struct mm_struct *vm_mm; /* The address space we belong to. */
pgprot_t vm_page_prot; /* Access permissions of this VMA. */
unsigned long vm_flags; /* Flags, see mm.h. */
/*
* For areas with an address space and backing store,
* linkage into the address_space->i_mmap interval tree.
*/
struct {
struct rb_node rb;
unsigned long rb_subtree_last;
} shared;
/*
* A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
* list, after a COW of one of the file pages. A MAP_SHARED vma
* can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack
* or brk vma (with NULL file) can only be in an anon_vma list.
*/
struct list_head anon_vma_chain; /* Serialized by mmap_sem &
* page_table_lock */
struct anon_vma *anon_vma; /* Serialized by page_table_lock */
/* Function pointers to deal with this struct. */
const struct vm_operations_struct *vm_ops;
/* Information about our backing store: */
unsigned long vm_pgoff; /* Offset (within vm_file) in PAGE_SIZE
units */
struct file * vm_file; /* File we map to (can be NULL). */
void * vm_private_data; /* was vm_pte (shared mem) */
atomic_long_t swap_readahead_info;
#ifndef CONFIG_MMU
struct vm_region *vm_region; /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
struct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
}
說(shuō)明
- vm_start: 虛擬內(nèi)存區(qū)域的起始地址
- vm_end: 虛擬內(nèi)存區(qū)域的結(jié)束地址
- vm_next和vm_prev: 用于連接多個(gè) VMA 構(gòu)成雙向鏈表
- vm_rb: 用于將多個(gè) VMA 構(gòu)成紅黑樹,以地址排序
- vm_mm: 指向所屬的 mm_struct,表示所屬的進(jìn)程
- vm_flags: 標(biāo)志位,表示區(qū)域的訪問(wèn)權(quán)限(讀/寫/執(zhí)行)、映射類型(共享/私有)等
- vm_page_prot: 頁(yè)級(jí)保護(hù),控制如何在頁(yè)表中設(shè)置訪問(wèn)權(quán)限
- vm_pgoff: 文件映射時(shí)的偏移量(page offset)
- vm_file: 如果映射了文件,此處指向文件結(jié)構(gòu)體file;否則為NULL(匿名映射)
- vm_private_data: 私有數(shù)據(jù),由mmap的文件op->mmap函數(shù)設(shè)置,一般 unused
- vm_ops: 指向vm_operations_struct 結(jié)構(gòu)體,定義此VMA的操作如munmap、mprotect等
- anon_vma: 匿名VMA鏈表,將匿名VMA連接 together
- vm_policy: NUMA策略,用于在NUMA系統(tǒng)中調(diào)度頁(yè)表和cpu
這些字段記錄了每個(gè)虛擬內(nèi)存區(qū)域的詳細(xì)信息,如起止地址、權(quán)限、所屬文件、匿名鏈表等,使得內(nèi)核可以方便地管理和操作進(jìn)程的虛擬內(nèi)存。
4.2.3 struct vm_operations_struct
vm_area_struct中的vm_ops指針指向vm_operations_struct結(jié)構(gòu)體,定義了該VMA的操作方法,如munmap、mprotect等。
#include <linux/mm.h>
struct vm_operations_struct {
void (*open)(struct vm_area_struct * area);
void (*close)(struct vm_area_struct * area);
int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);
int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);
int (*access)(struct vm_area_struct *vma, unsigned long addr,
void *buf, int len, int write);
int (*mmap)(struct vm_area_struct *vma);
int (*mprotect)(struct vm_area_struct *vma, unsigned long start,
unsigned long end, unsigned long prot);
int (*mremap)(struct vm_area_struct *vma);
int (*remap_pages)(struct vm_area_struct *vma, unsigned long addr,
unsigned long size, unsigned long pgoff);
int (*populate)(struct vm_area_struct *vma, unsigned long addr,
unsigned long len);
void (*close)(struct vm_area_struct *vma);
int (*madvise)(struct vm_area_struct *vma, unsigned long start,
unsigned long end, int advice);
};
這些方法定義了針對(duì)該VMA的各種操作,如munmap對(duì)應(yīng)close方法,mprotect對(duì)應(yīng)mprotect方法,軟件缺頁(yè)錯(cuò)誤對(duì)應(yīng)fault方法等。
當(dāng)用戶空間調(diào)用mmap等系統(tǒng)調(diào)用操作VMA時(shí),內(nèi)核會(huì)調(diào)用對(duì)應(yīng)vm_ops的方法來(lái)完成操作。
內(nèi)核會(huì)在以下情況下調(diào)用vm_operations_struct的對(duì)應(yīng)方法:
- close方法:當(dāng)調(diào)用munmap系統(tǒng)調(diào)用釋放一個(gè)VMA時(shí),內(nèi)核會(huì)調(diào)用vm_ops->close方法。
- fault方法:當(dāng)一個(gè)VMA首次被訪問(wèn),且頁(yè)表項(xiàng)還未建立,會(huì)觸發(fā)缺頁(yè)異常,此時(shí)內(nèi)核會(huì)調(diào)用vm_ops->fault方法來(lái)處理異常,建立頁(yè)表項(xiàng)。
- page_mkwrite方法:當(dāng)一個(gè)只讀頁(yè)面在進(jìn)程嘗試寫訪問(wèn)時(shí),會(huì)調(diào)用vm_ops->page_mkwrite方法來(lái)設(shè)置頁(yè)表項(xiàng)為可寫,完成頁(yè)表同步。
- mprotect方法:當(dāng)一個(gè)VMA的訪問(wèn)權(quán)限發(fā)生變化時(shí),內(nèi)核會(huì)調(diào)用vm_ops->mprotect方法來(lái)更改頁(yè)表訪問(wèn)權(quán)限。
- mmap方法:當(dāng)一個(gè)文件在進(jìn)程初次訪問(wèn)時(shí),內(nèi)核需要建立文件和VMA之間的關(guān)聯(lián),會(huì)調(diào)用vm_ops->mmap方法。例如在文件映射區(qū)會(huì)調(diào)用文件系統(tǒng)的mmap方法。
- munmap方法:當(dāng)調(diào)用munmap系統(tǒng)調(diào)用釋放一段虛擬地址區(qū)間時(shí),內(nèi)核會(huì)調(diào)用vm_ops->munmap方法來(lái)完成操作。
- remap_pages方法:當(dāng)調(diào)用remap_file_pages系統(tǒng)調(diào)用將文件頁(yè)重映射到其他虛擬地址時(shí),內(nèi)核會(huì)調(diào)用vm_ops->remap_pages方法。
- 其他方法:open、access、populate、madvise等,分別在VMA打開、訪問(wèn)檢查、預(yù)讀等情況下調(diào)用。
所以,內(nèi)核會(huì)在需要對(duì)一個(gè)VMA進(jìn)行訪問(wèn)權(quán)限控制、地址翻譯、屬性變更等管理操作時(shí),調(diào)用vm_operations_struct中對(duì)應(yīng)的方法來(lái)完成。
這使得內(nèi)核可以根據(jù)VMA的類型選擇正確的操作方法,是Linux虛擬內(nèi)存管理的重要機(jī)制。
4.2.4 struct mm_struct *vm_mm;
vm_area_struct中的vm_mm指針指向mm_struct結(jié)構(gòu)體,表示該VMA所屬的進(jìn)程。
表示一個(gè)進(jìn)程的地址空間,包含了描述地址空間所需的所有信息。其定義如下:
#include <linux/mm_types.h>,
c
struct mm_struct {
struct vm_area_struct * mmap; /* list of VMAs */
struct rb_root mm_rb;
u32 vmacache_seqnum; /* per-thread vmacache */
unsigned long (*get_unmapped_area) (struct file *filp,
unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags);
unsigned long mmap_base; /* base of mmap area */
unsigned long mmap_legacy_base; /* base of mmap area in bottom-up allocations */
unsigned long task_size; /* size of task vm space */
...
};
主要包含:
- mmap: VMA雙向鏈表的頭,鏈接該mm_struct的所有VMA
- mm_rb: VMA紅黑樹的根,以地址排序VMA
- mmap_base和task_size: 模擬地址空間的起始和大小
- get_unmapped_area: 獲取未映射區(qū)域的方法
- 以及其他許多字段
所以mm_struct包含了描述一個(gè)進(jìn)程完整地址空間的所有信息:
- VMA鏈表和紅黑樹,記錄每個(gè)內(nèi)存映射區(qū)
- address space的邊界和大小
- 分配未映射空間的方法
- 頁(yè)表,swapper等信息
vm_area_struct的vm_mm指針指向所屬的mm_struct,這樣每個(gè)VMA都知道自己屬于哪個(gè)進(jìn)程的地址空間,并且內(nèi)核也可以通過(guò)vm_mm鏈接到描述整個(gè)地址空間的信息。
所以vm_mm是連接VMA和整個(gè)進(jìn)程地址空間信息的關(guān)鍵字段。內(nèi)核通過(guò)它可以方便地獲得某個(gè)VMA所屬進(jìn)程的完整地址空間信息。 (文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-672007.html
4.2.5 內(nèi)核自帶的驅(qū)動(dòng)實(shí)例
在linxux 4.15版本的目錄/drivers/video/fbdev/core/fbmem.c。是一個(gè)完整的驅(qū)動(dòng)程序,摘出其中的.mmap程序供參考文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-672007.html
static int
fb_mmap(struct file *file, struct vm_area_struct * vma)
{
struct fb_info *info = file_fb_info(file);
struct fb_ops *fb;
unsigned long mmio_pgoff;
unsigned long start;
u32 len;
if (!info)
return -ENODEV;
fb = info->fbops;
if (!fb)
return -ENODEV;
mutex_lock(&info->mm_lock);
if (fb->fb_mmap) {
int res;
/*
* The framebuffer needs to be accessed decrypted, be sure
* SME protection is removed ahead of the call
*/
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
res = fb->fb_mmap(info, vma);
mutex_unlock(&info->mm_lock);
return res;
}
/*
* Ugh. This can be either the frame buffer mapping, or
* if pgoff points past it, the mmio mapping.
*/
start = info->fix.smem_start;
len = info->fix.smem_len;
mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
if (vma->vm_pgoff >= mmio_pgoff) {
if (info->var.accel_flags) {
mutex_unlock(&info->mm_lock);
return -EINVAL;
}
vma->vm_pgoff -= mmio_pgoff;
start = info->fix.mmio_start;
len = info->fix.mmio_len;
}
mutex_unlock(&info->mm_lock);
vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);
/*
* The framebuffer needs to be accessed decrypted, be sure
* SME protection is removed
*/
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
fb_pgprotect(file, vma, start);
return vm_iomap_memory(vma, start, len);
}
到了這里,關(guān)于【嵌入式環(huán)境下linux內(nèi)核及驅(qū)動(dòng)學(xué)習(xí)筆記-(10-內(nèi)核內(nèi)存管理)】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!