国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【嵌入式環(huán)境下linux內(nèi)核及驅(qū)動(dòng)學(xué)習(xí)筆記-(10-內(nèi)核內(nèi)存管理)】

這篇具有很好參考價(jià)值的文章主要介紹了【嵌入式環(huán)境下linux內(nèi)核及驅(qū)動(dòng)學(xué)習(xí)筆記-(10-內(nèi)核內(nèi)存管理)】。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

對(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)核空間。如下圖。

嵌入式linux內(nèi)核,Linux內(nèi)核與驅(qū)動(dòng),linux,驅(qū)動(dòng)開發(fā),嵌入式,內(nèi)核與驅(qū)動(dòng)
用戶進(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ū):

嵌入式linux內(nèi)核,Linux內(nèi)核與驅(qū)動(dòng),linux,驅(qū)動(dòng)開發(fā),嵌入式,內(nèi)核與驅(qū)動(dòng)

內(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ū)的分配指令:
    1. kmalloc:小內(nèi)存分配,slab算法
    2. 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ū):

嵌入式linux內(nèi)核,Linux內(nèi)核與驅(qū)動(dòng),linux,驅(qū)動(dòng)開發(fā),嵌入式,內(nèi)核與驅(qū)動(dòng)

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):

  1. 只能向kfree()傳入kmalloc()分配的內(nèi)存地址,否則會(huì)導(dǎo)致未定義行為。
  2. 同一塊內(nèi)存只能free一次,多次free同一地址會(huì)導(dǎo)致未定義行為。
  3. free已free的內(nèi)存地址會(huì)導(dǎo)致未定義行為。
  4. kmalloc()的內(nèi)存不會(huì)自動(dòng)釋放,必須手動(dòng)調(diào)用kfree()釋放。否則會(huì)導(dǎo)致內(nèi)存泄漏。
  5. kfree()時(shí)傳入的地址必須是kmalloc()的原始地址,不能在kmalloc()的基礎(chǔ)上偏移一定字節(jié)后傳入kfree()。
  6. 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()有以下不同:

  1. kzalloc()會(huì)在返回內(nèi)存之前清零內(nèi)存,kmalloc()返回的內(nèi)存是未初始化的。
  2. 其他屬性基本相同,如:
  • 使用相同的伙伴系統(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()基本相同:

  1. 也是直接分配物理內(nèi)存,性能高于kmalloc()但內(nèi)存分配大小較固定。
  2. 使用不當(dāng)也會(huì)導(dǎo)致內(nèi)存泄漏和未定義行為,需要謹(jǐn)慎使用。
  3. 也有對(duì)應(yīng)的free_pages()函數(shù)用于釋放分配的內(nèi)存。
  4. 也有其它類似函數(shù),如__get_dma_pages()等。
    所以,__get_free_pages()函數(shù)在以下情況下會(huì)非常有用:
  5. 需要分配較大的連續(xù)內(nèi)存塊(多個(gè)物理頁(yè)面)。
  6. 對(duì)性能有較高要求,不希望通過(guò)kmalloc()分配內(nèi)存后再手動(dòng)連接成較大塊。
  7. 需要的內(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()也需要注意:

  1. 返回的內(nèi)存頁(yè)面需要手動(dòng)通過(guò)free_page()釋放,防止內(nèi)存泄漏。
  2. 傳入free_page()的地址必須是get_zeroed_page()的返回地址,否則會(huì)導(dǎo)致未定義行為。
  3. 同一內(nèi)存頁(yè)面不能重復(fù)free,否則也會(huì)導(dǎo)致未定義行為。
  4. 要積極釋放物理內(nèi)存頁(yè)面,否則容易因分配和釋放不均衡導(dǎo)致OOM。
  5. 內(nèi)存頁(yè)面類型必須匹配(內(nèi)核空間與用戶空間內(nèi)存不能混用)。
  6. 直接操作物理內(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()也需要注意:

  1. 返回的內(nèi)存需要通過(guò)free_pages()釋放,以防止內(nèi)存泄漏。
  2. 傳入free_pages()的地址和order必須與__get_dma_pages()相同,否則會(huì)導(dǎo)致未定義行為。
  3. 同一內(nèi)存不能重復(fù)釋放,否則也會(huì)導(dǎo)致未定義行為。
  4. 要及時(shí)調(diào)用free_pages()釋放內(nèi)存,否則容易OOM。
  5. __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ù)在以下情況下很有用:

  1. 需要分配較大塊內(nèi)存(通過(guò)較大的order),并且對(duì)性能要求不高。
  2. 普通內(nèi)存空間不足,需要使用高端內(nèi)存。
  3. 僅用于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ù)在以下情況下很有用:

  1. 需要較快速度訪問(wèn)內(nèi)存,對(duì)性能有要求。
  2. 普通內(nèi)存空間足夠,不需要使用高端內(nèi)存。
  3. 訪問(wèn)頻繁,需要較快的速度。

與其他類似函數(shù)一樣,__get_low_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_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()有以下主要特征:

  1. 分配的物理內(nèi)存不連續(xù),但提供連續(xù)的虛擬地址空間,方便使用。
  2. 使用頁(yè)表來(lái)建立虛擬地址到物理地址的映射,所以分配和訪問(wèn)速度較慢。
  3. 分配的內(nèi)存需要通過(guò)vfree()釋放,否則會(huì)產(chǎn)生內(nèi)存泄漏。
  4. 傳入vfree()的地址必須是vmalloc()的返回地址,否則會(huì)導(dǎo)致未定義行為。
  5. 同一內(nèi)存塊不能重復(fù)釋放,否則也會(huì)導(dǎo)致未定義行為。
  6. 要及時(shí)調(diào)用vfree()釋放內(nèi)存,否則容易導(dǎo)致OOM。
  7. 分配的內(nèi)存類型必須匹配(內(nèi)核空間與用戶空間內(nèi)存不能混用)。

vmalloc()適用于以下情況:

  1. 需要較大塊內(nèi)存(超過(guò)128K)時(shí),kmalloc()就難以滿足需求。
  2. 內(nèi)核映像大小(initrd大小)較大時(shí),也需要用vmalloc()分配。
  3. 在ioremap()之后,也會(huì)使用vmalloc()映射分配的I/O內(nèi)存。
  4. 訪問(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()有以下主要特征:

  1. 會(huì)釋放vmalloc()分配的虛擬連續(xù)地址到物理不連續(xù)地址的映射。
  2. addr傳入的地址必須正確,否則會(huì)導(dǎo)致未定義行為。
  3. 同一內(nèi)存塊不能重復(fù)釋放,否則也會(huì)導(dǎo)致未定義行為。
  4. 要及時(shí)調(diào)用vfree()釋放內(nèi)存,否則會(huì)產(chǎn)生內(nèi)存泄漏,導(dǎo)致OOM。
  5. 只能釋放與vmalloc()相匹配的內(nèi)存,否則結(jié)果未定義。
  6. 會(huì)釋放vmalloc()為映射創(chuàng)建的頁(yè)表等數(shù)據(jù)結(jié)構(gòu),所以調(diào)用vfree()后不能再訪問(wèn)該內(nèi)存。

vfree()主要用于釋放較大塊的內(nèi)存,一般在以下情況使用:

  1. 不再需要vmalloc()分配的大塊內(nèi)存時(shí),需要將其釋放。
  2. 加載結(jié)束后的initrd需要釋放內(nèi)存。
  3. 模塊卸載時(shí)需要釋放內(nèi)核映像等占用的大量?jī)?nèi)存。
  4. 其它分配大塊連續(xù)虛擬內(nèi)存后,使用結(jié)束需要釋放場(chǎng)景。

2.4 kmalloc & vmalloc 的比較

kmalloc()、kzalloc()、vmalloc() 的共同特點(diǎn)是:

  1. 用于申請(qǐng)內(nèi)核空間的內(nèi)存;
  2. 內(nèi)存以字節(jié)為單位進(jìn)行分配;
  3. 所分配的內(nèi)存虛擬地址上連續(xù);

kmalloc()、kzalloc()、vmalloc() 的區(qū)別是:

  1. kzalloc 是強(qiáng)制清零的 kmalloc 操作;(以下描述不區(qū)分 kmalloc 和 kzalloc)
  2. kmalloc 分配的內(nèi)存大小有限制(128KB),而 vmalloc 沒(méi)有限制;
  3. kmalloc 可以保證分配的內(nèi)存物理地址是連續(xù)的,但是 vmalloc 不能保證;
  4. kmalloc 分配內(nèi)存的過(guò)程可以是原子過(guò)程(使用 GFP_ATOMIC),而 vmalloc 分配內(nèi)存時(shí)則可能產(chǎn)生阻塞;
  5. 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()有以下主要特征:

  1. 它只能映射物理I/O內(nèi)存,不能映射普通內(nèi)存。
  2. 返回的虛擬地址可以像普通內(nèi)存一樣訪問(wèn)I/O內(nèi)存,但實(shí)際上是通過(guò)映射實(shí)現(xiàn)的。
  3. 需要通過(guò)iounmap()釋放映射,否則會(huì)產(chǎn)生內(nèi)存泄漏。
  4. 傳入iounmap()的地址必須是ioremap()的返回地址,否則會(huì)導(dǎo)致未定義行為。
  5. 同一I/O內(nèi)存區(qū)不能重復(fù)映射,否則也會(huì)導(dǎo)致未定義行為。
  6. 要及時(shí)調(diào)用iounmap()釋放映射,否則容易導(dǎo)致OOM。
  7. 映射過(guò)程需要消耗一定的CPU和內(nèi)存資源。

ioremap()主要用于以下場(chǎng)景:

  1. 需要從用戶空間訪問(wèn)物理I/O內(nèi)存時(shí),可以先通過(guò)ioremap()映射,然后像訪問(wèn)普通內(nèi)存一樣進(jìn)行訪問(wèn)。
  2. 驅(qū)動(dòng)程序需要在中斷/底半部里訪問(wèn)I/O內(nèi)存,這時(shí)候也需要先建立映射。
  3. 需要長(zhǎng)期頻繁訪問(wèn)I/O內(nèi)存,映射后可以提高訪問(wèn)效率。
  4. 當(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()有以下主要特征:

  1. 它只能釋放通過(guò)ioremap()建立的I/O內(nèi)存映射,不能釋放普通內(nèi)存。
  2. addr傳入的地址必須正確,否則會(huì)導(dǎo)致未定義行為。
  3. 同一I/O內(nèi)存區(qū)域的映射不能重復(fù)釋放,否則也會(huì)導(dǎo)致未定義行為。
  4. 要及時(shí)調(diào)用iounmap()釋放映射,否則會(huì)產(chǎn)生內(nèi)存泄漏,并占用MMU資源。
  5. 釋放映射后,該虛擬地址范圍不可訪問(wèn),否則未定義。
  6. 會(huì)釋放映射過(guò)程中占用的頁(yè)表、TLB等資源。

iounmap()主要用于以下場(chǎng)景:

  1. 不再需要訪問(wèn)I/O內(nèi)存時(shí),需要將其映射釋放。
  2. 模塊卸載時(shí)需要釋放建立的I/O內(nèi)存映射。
  3. 釋放驅(qū)動(dòng)程序不再使用的I/O內(nèi)存虛擬地址。
  4. 卸載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ù)主要用于:

  1. 訪問(wèn)I/O內(nèi)存:當(dāng)某段物理內(nèi)存被映射為I/O內(nèi)存時(shí),可以使用這些函數(shù)讀/寫。
  2. 訪問(wèn)I/O端口:I/O端口的內(nèi)存映射基址也被定義為__iomem類型,可以用這些函數(shù)讀寫端口。
  3. 訪問(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ù)需要注意的地方:

  1. addr必須是I/O內(nèi)存或I/O端口的地址,否則會(huì)產(chǎn)生未定義行為。
  2. value的類型必須與讀/寫寬度匹配,否則也會(huì)產(chǎn)生未定義行為。
  3. 要按正確的對(duì)齊 accessing I/O registers 要按正確字節(jié)對(duì)齊方式訪問(wèn)I/O寄存器和內(nèi)存。
  4. 如果編寫模塊驅(qū)動(dòng),記得聲明為__init和__iomem等,提高可移植性。
  5. 要考慮端序問(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。

電路原理圖
嵌入式linux內(nèi)核,Linux內(nèi)核與驅(qū)動(dòng),linux,驅(qū)動(dòng)開發(fā),嵌入式,內(nèi)核與驅(qū)動(dòng)

這里每個(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)如下:
嵌入式linux內(nèi)核,Linux內(nèi)核與驅(qū)動(dòng),linux,驅(qū)動(dòng)開發(fā),嵌入式,內(nèi)核與驅(qū)動(dòng)
以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è)作用:

  1. 指定文件映射時(shí)的起始位置。例如文件大小為100字節(jié),offset設(shè)置為20,那么mapping的區(qū)域就從文件偏移量20開始,長(zhǎng)度為length指定的大小。
  2. 可以實(shí)現(xiàn)對(duì)同一個(gè)文件映射多次的目的。例如第一次mapping offset設(shè)置為0,length為60,得到一塊內(nèi)存區(qū)域。第二次mapping offset設(shè)置為60,length也為60,就可以獲取文件剩余部分的映射。
  3. 實(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)核做了以下:

嵌入式linux內(nèi)核,Linux內(nèi)核與驅(qū)動(dòng),linux,驅(qū)動(dòng)開發(fā),嵌入式,內(nèi)核與驅(qū)動(dòng)

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)方法:

  1. close方法:當(dāng)調(diào)用munmap系統(tǒng)調(diào)用釋放一個(gè)VMA時(shí),內(nèi)核會(huì)調(diào)用vm_ops->close方法。
  2. 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)。
  3. 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è)表同步。
  4. 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)限。
  5. 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方法。
  6. munmap方法:當(dāng)調(diào)用munmap系統(tǒng)調(diào)用釋放一段虛擬地址區(qū)間時(shí),內(nèi)核會(huì)調(diào)用vm_ops->munmap方法來(lái)完成操作。
  7. remap_pages方法:當(dāng)調(diào)用remap_file_pages系統(tǒng)調(diào)用將文件頁(yè)重映射到其他虛擬地址時(shí),內(nèi)核會(huì)調(diào)用vm_ops->remap_pages方法。
  8. 其他方法: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)程的完整地址空間信息。 (

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)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 嵌入式Linux驅(qū)動(dòng)開發(fā) 02:將驅(qū)動(dòng)程序添加到內(nèi)核中

    嵌入式Linux驅(qū)動(dòng)開發(fā) 02:將驅(qū)動(dòng)程序添加到內(nèi)核中

    在上一篇文章 《嵌入式Linux驅(qū)動(dòng)開發(fā) 01:基礎(chǔ)開發(fā)與使用》 中我們已經(jīng)實(shí)現(xiàn)了最基礎(chǔ)的驅(qū)動(dòng)功能。在那篇文章中我們的驅(qū)動(dòng)代碼是獨(dú)立于內(nèi)核代碼存放的,并且我們的驅(qū)動(dòng)編譯后也是一個(gè)獨(dú)立的模塊。在實(shí)際使用中將驅(qū)動(dòng)代碼放在內(nèi)核代碼中,并將驅(qū)動(dòng)編譯到內(nèi)核中也是比較

    2023年04月09日
    瀏覽(51)
  • 【嵌入式Linux學(xué)習(xí)筆記】platform設(shè)備驅(qū)動(dòng)和input子系統(tǒng)

    【嵌入式Linux學(xué)習(xí)筆記】platform設(shè)備驅(qū)動(dòng)和input子系統(tǒng)

    對(duì)于Linux這種龐大的操作系統(tǒng),代碼重用性非常重要,所以需要有相關(guān)的機(jī)制來(lái)提升效率,去除重復(fù)無(wú)意義的代碼,尤其是對(duì)于驅(qū)動(dòng)程序,所以就有了platform和INPUT子系統(tǒng)這兩種工作機(jī)制。 學(xué)習(xí)視頻地址:【正點(diǎn)原子】STM32MP157開發(fā)板 platform 驅(qū)動(dòng)框架分為總線、設(shè)備和驅(qū)動(dòng)???/p>

    2024年02月07日
    瀏覽(25)
  • 【嵌入式Linux內(nèi)核驅(qū)動(dòng)】05_IIC子系統(tǒng) | 硬件原理與常見面試問(wèn)題 | 應(yīng)用編程 | 內(nèi)核驅(qū)動(dòng) | 總體框架

    【嵌入式Linux內(nèi)核驅(qū)動(dòng)】05_IIC子系統(tǒng) | 硬件原理與常見面試問(wèn)題 | 應(yīng)用編程 | 內(nèi)核驅(qū)動(dòng) | 總體框架

    1.1 IIC 基礎(chǔ) IIC協(xié)議簡(jiǎn)介—學(xué)習(xí)筆記_iic標(biāo)準(zhǔn)協(xié)議_越吃越胖的黃的博客-CSDN博客 I2C(Inter-Integrated Circuit)是一種串行通信協(xié)議,用于連接微控制器、傳感器、存儲(chǔ)器和其他外設(shè)。 I2C使用兩條線(SDA和SCL)進(jìn)行通信,可以連接多個(gè)設(shè)備,每個(gè)設(shè)備都有一個(gè)唯一的地址。I2C總線上的

    2024年02月09日
    瀏覽(93)
  • 【嵌入式Linux內(nèi)核驅(qū)動(dòng)】04_Jetson nano GPIO應(yīng)用 | 驅(qū)動(dòng)開發(fā) | 官方gpiolib、設(shè)備樹與chip_driver

    【嵌入式Linux內(nèi)核驅(qū)動(dòng)】04_Jetson nano GPIO應(yīng)用 | 驅(qū)動(dòng)開發(fā) | 官方gpiolib、設(shè)備樹與chip_driver

    0.暴露給應(yīng)用層 應(yīng)用 解決調(diào)試目錄為空的問(wèn)題 調(diào)試信息 1.最簡(jiǎn)讀寫文件(在/SYS下) 設(shè)備樹 驗(yàn)證測(cè)試 編譯文件 驅(qū)動(dòng) of_get_named_gpio_flags //獲取設(shè)備樹節(jié)點(diǎn)的屬性 gpio_is_valid //判斷是否合法 devm_gpio_request //申請(qǐng)使用gpio,并調(diào)用設(shè)置pinctrl device_create_file //根據(jù)設(shè)備樹節(jié)點(diǎn)屬性,創(chuàng)建

    2024年02月07日
    瀏覽(53)
  • 嵌入式內(nèi)核及驅(qū)動(dòng)開發(fā)高級(jí)

    嵌入式內(nèi)核及驅(qū)動(dòng)開發(fā)高級(jí)

    僅devfs,導(dǎo)致開發(fā)不方便以及一些功能難以支持: 熱插拔 不支持一些針對(duì)所有設(shè)備的統(tǒng)一操作(如電源管理) 不能自動(dòng)mknod 用戶查看不了設(shè)備信息 設(shè)備信息硬編碼,導(dǎo)致驅(qū)動(dòng)代碼通用性差,即沒(méi)有分離設(shè)備和驅(qū)動(dòng) uevent機(jī)制:sysfs + uevent + udevd(上層app) sysfs用途:(類似于

    2024年02月16日
    瀏覽(36)
  • 嵌入式開發(fā)之linux內(nèi)核移植

    嵌入式開發(fā)之linux內(nèi)核移植

    目錄 ?前言 一、下載內(nèi)核源碼 1.1 下載linux-3.0.1 1.2 解壓源碼文件 二、 內(nèi)核添加yaffs2文件系統(tǒng)支持 2.1 下載yaffs2 2.2 內(nèi)核添加yaffs2文件補(bǔ)丁 三、配置開發(fā)板 3.1 修改機(jī)器ID 3.2 添加開發(fā)板初始化文件 3.3 配置NandFalsh 3.3.1 添加NandFlash設(shè)備 3.3.2 添加NandFlash驅(qū)動(dòng) 3.3 修改Kconfig(支持

    2024年02月07日
    瀏覽(103)
  • 嵌入式培訓(xùn)機(jī)構(gòu)四個(gè)月實(shí)訓(xùn)課程筆記(完整版)-Linux ARM驅(qū)動(dòng)編程第五天-ARM Linux編程之字符設(shè)備驅(qū)動(dòng)(物聯(lián)技術(shù)666)

    嵌入式培訓(xùn)機(jī)構(gòu)四個(gè)月實(shí)訓(xùn)課程筆記(完整版)-Linux ARM驅(qū)動(dòng)編程第五天-ARM Linux編程之字符設(shè)備驅(qū)動(dòng)(物聯(lián)技術(shù)666)

    鏈接:https://pan.baidu.com/s/1V0E9IHSoLbpiWJsncmFgdA?pwd=1688 提取碼:1688 教學(xué)內(nèi)容: 1 、內(nèi)核模塊的簡(jiǎn)單框架: __init __exit 執(zhí)行完后就釋放空間 簡(jiǎn)單框架:包含三個(gè)部分 1)模塊初始化和模塊退出函數(shù) 2)注冊(cè)模塊函數(shù) 3)模塊許可 //*************************************************** #include linux

    2024年02月21日
    瀏覽(21)
  • 嵌入式Linux底層系統(tǒng)開發(fā) +系統(tǒng)移植+內(nèi)核文件系統(tǒng)(基礎(chǔ))

    嵌入式Linux底層系統(tǒng)開發(fā) +系統(tǒng)移植+內(nèi)核文件系統(tǒng)(基礎(chǔ))

    搭建交叉編譯開發(fā)環(huán)境 bootloader的選擇和移植 kernel的配置、編譯、移植和調(diào)試 根文件系統(tǒng)的制作 前兩個(gè)要點(diǎn)通常芯片廠家提供。后邊兩個(gè)要點(diǎn)是公司的工作重點(diǎn)。 學(xué)習(xí)方法:先整體后局部,層層推進(jìn) 如何編譯—如何添加命令和功能—如何定義自己的開發(fā)板。 移植的基本步

    2024年02月03日
    瀏覽(101)
  • 修改嵌入式 ARM Linux 內(nèi)核映像中的文件系統(tǒng)

    修改嵌入式 ARM Linux 內(nèi)核映像中的文件系統(tǒng)

    zImage 是編譯內(nèi)核后在 arch/arm/boot 目錄下生成的一個(gè)已經(jīng)壓縮過(guò)的內(nèi)核映像。通常我們不會(huì)使用編譯生成的原始內(nèi)核映像 vmlinux ,因其體積很大。因此, zImage 是我們最常見的內(nèi)核二進(jìn)制,可以直接嵌入到固件,也可以直接使用 qemu 進(jìn)行調(diào)試。當(dāng)然,在 32 位嵌入式領(lǐng)域還能見到

    2024年02月10日
    瀏覽(35)
  • 【嵌入式Linux】編譯應(yīng)用和ko內(nèi)核模塊Makefile使用記錄

    【嵌入式Linux】編譯應(yīng)用和ko內(nèi)核模塊Makefile使用記錄

    在Makefile中,變量的賦值可以使用以下幾種方式: = :最基本的賦值符號(hào),表示簡(jiǎn)單的延遲展開(lazy expansion)方式。變量的值將會(huì)在使用變量的時(shí)候進(jìn)行展開。 := :立即展開(immediate expansion)的賦值方式。變量的值在賦值的時(shí)候立即展開,并且在后續(xù)的使用中不再改變。

    2024年02月08日
    瀏覽(24)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包