閱碼場(chǎng)用戶程磊對(duì)《Linux內(nèi)核深度解析》推薦如下:
1.語(yǔ)言淺顯易懂,內(nèi)容深入淺出。
2.邏輯清晰,條理分明,逐步深入,層層遞進(jìn)。
3.基于較新的4.12內(nèi)核版本,很多經(jīng)典內(nèi)核書(shū)籍雖然寫(xiě)的都非常好,但是都是基于2.6內(nèi)核,很多在2.6之后引入的新技術(shù)并沒(méi)有講到,而本書(shū)對(duì)這些新技術(shù)都有非常詳細(xì)的講解。
3.1 內(nèi)存管理概述
內(nèi)存管理子系統(tǒng)的架構(gòu)如圖 3.1 所示,分為用戶空間、內(nèi)核空間和硬件 3 個(gè)層面。
圖3.1 內(nèi)存管理架構(gòu)
3.1.1.用戶空間
應(yīng)用程序使用 malloc()申請(qǐng)內(nèi)存,使用 free()釋放內(nèi)存。
malloc()和 free()是 glibc 庫(kù)的內(nèi)存分配器 ptmalloc 提供的接口,ptmalloc 使用系統(tǒng)調(diào)用brk 或 mmap 向內(nèi)核以頁(yè)為單位申請(qǐng)內(nèi)存,然后劃分成小內(nèi)存塊分配給應(yīng)用程序。
用戶空間的內(nèi)存分配器,除了 glibc 庫(kù)的 ptmalloc,還有谷歌公司的 tcmalloc 和 FreeBSD的 jemalloc。
3.1.2.內(nèi)核空間
(1)內(nèi)核空間的基本功能。
虛擬內(nèi)存管理負(fù)責(zé)從進(jìn)程的虛擬地址空間分配虛擬頁(yè),sys_brk 用來(lái)擴(kuò)大或收縮堆,sys_mmap 用來(lái)在內(nèi)存映射區(qū)域分配虛擬頁(yè),sys_munmap 用來(lái)釋放虛擬頁(yè)。
內(nèi)核使用延遲分配物理內(nèi)存的策略,進(jìn)程第一次訪問(wèn)虛擬頁(yè)的時(shí)候,觸發(fā)頁(yè)錯(cuò)誤異常,頁(yè)錯(cuò)誤異常處理程序從頁(yè)分配器申請(qǐng)物理頁(yè),在進(jìn)程的頁(yè)表中把虛擬頁(yè)映射到物理頁(yè)。
頁(yè)分配器負(fù)責(zé)分配物理頁(yè),當(dāng)前使用的頁(yè)分配器是伙伴分配器。
內(nèi)核空間提供了把頁(yè)劃分成小內(nèi)存塊分配的塊分配器,提供分配內(nèi)存的接口 kmalloc()和釋放內(nèi)存的接口 kfree(),支持 3 種塊分配器:SLAB 分配器、SLUB 分配器和 SLOB分配器。
在內(nèi)核初始化的過(guò)程中,頁(yè)分配器還沒(méi)準(zhǔn)備好,需要使用臨時(shí)的引導(dǎo)內(nèi)存分配器分配內(nèi)存。
(2)內(nèi)核空間的擴(kuò)展功能。
不連續(xù)頁(yè)分配器提供了分配內(nèi)存的接口 vmalloc 和釋放內(nèi)存的接口 vfree,在內(nèi)存碎片化的時(shí)候,申請(qǐng)連續(xù)物理頁(yè)的成功率很低,可以申請(qǐng)不連續(xù)的物理頁(yè),映射到連續(xù)的虛擬頁(yè),即虛擬地址連續(xù)而物理地址不連續(xù)。
每處理器內(nèi)存分配器用來(lái)為每處理器變量分配內(nèi)存。
連續(xù)內(nèi)存分配器(Contiguous Memory Allocator,CMA)用來(lái)給驅(qū)動(dòng)程序預(yù)留一段連續(xù)的內(nèi)存,當(dāng)驅(qū)動(dòng)程序不用的時(shí)候,可以給進(jìn)程使用;當(dāng)驅(qū)動(dòng)程序需要使用的時(shí)候,把進(jìn)程占用的內(nèi)存通過(guò)回收或遷移的方式讓出來(lái),給驅(qū)動(dòng)程序使用。
內(nèi)存控制組用來(lái)控制進(jìn)程占用的內(nèi)存資源。
當(dāng)內(nèi)存碎片化的時(shí)候,找不到連續(xù)的物理頁(yè),內(nèi)存碎片整理(“memory compaction”的意譯,直譯為“內(nèi)存緊縮”)通過(guò)遷移的方式得到連續(xù)的物理頁(yè)。
在內(nèi)存不足的時(shí)候,頁(yè)回收負(fù)責(zé)回收物理頁(yè),對(duì)于沒(méi)有后備存儲(chǔ)設(shè)備支持的匿名頁(yè),把數(shù)據(jù)換出到交換區(qū),然后釋放物理頁(yè);對(duì)于有后備存儲(chǔ)設(shè)備支持的文件頁(yè),把數(shù)據(jù)寫(xiě)回存儲(chǔ)設(shè)備,然后釋放物理頁(yè)。如果頁(yè)回收失敗,使用最后一招:內(nèi)存耗盡殺手(OOM killer,Out-of-Memory killer),選擇進(jìn)程殺掉。
3.1.3.硬件層面
處理器包含一個(gè)稱為內(nèi)存管理單元(Memory Management Unit,MMU)的部件,負(fù)責(zé)把虛擬地址轉(zhuǎn)換成物理地址。
內(nèi)存管理單元包含一個(gè)稱為頁(yè)表緩存(Translation Lookaside Buffer,TLB)的部件,保存最近使用過(guò)的頁(yè)表映射,避免每次把虛擬地址轉(zhuǎn)換成物理地址都需要查詢內(nèi)存中的頁(yè)表。
為了解決處理器的執(zhí)行速度和內(nèi)存的訪問(wèn)速度不匹配的問(wèn)題,在處理器和內(nèi)存之間增加了緩存。緩存通常分為一級(jí)緩存和二級(jí)緩存,為了支持并行地取指令和取數(shù)據(jù),一級(jí)緩存分為數(shù)據(jù)緩存和指令緩存。
3.2 虛擬地址空間布局
3.2.1 虛擬地址空間劃分
因?yàn)槟壳皯?yīng)用程序沒(méi)有那么大的內(nèi)存需求,所以 ARM64 處理器不支持完全的 64 位虛擬地址,實(shí)際支持情況如下。
圖3.2 ARM64內(nèi)核/用戶虛擬地址空間劃分
(1)虛擬地址的最大寬度是 48 位,如圖 3.2 所示。內(nèi)核虛擬地址在 64 位地址空間的頂部,高 16 位是全 1,范圍是[0xFFFF 0000 00000000,0xFFFF FFFF FFFF FFFF];用戶虛擬地址在 64 位地址空間的底部,高 16 位是全 0,范圍是[0x0000 0000 0000 0000,0x0000 FFFF FFFF FFFF];高 16 位是全 1 或全 0 的地址稱為規(guī)范的地址,兩者之間是不規(guī)范的地址,不允許使用。
(2)如果處理器實(shí)現(xiàn)了 ARMv8.2 標(biāo)準(zhǔn)的大虛擬地址(Large Virtual Address,LVA)支持,并且頁(yè)長(zhǎng)度是 64KB,那么虛擬地址的最大寬度是 52 位。
(3)可以為虛擬地址配置比最大寬度小的寬度,并且可以為內(nèi)核虛擬地址和用戶虛擬地址配置不同的寬度。轉(zhuǎn)換控制寄存器(Translation Control Register)TCR_EL1 的字段 T0SZ 定義了必須是全 0 的最高位的數(shù)量,字段 T1SZ 定義了必須是全 1 的最高位的數(shù)量,用戶虛擬地址的寬度是(64-TCR_EL1.T0SZ),內(nèi)核虛擬地址的寬度是(64-TCR_EL1.T1SZ)。
在編譯 ARM64 架構(gòu)的 Linux 內(nèi)核時(shí),可以選擇虛擬地址寬度。
(1)如果選擇頁(yè)長(zhǎng)度 4KB,默認(rèn)的虛擬地址寬度是 39 位。
(2)如果選擇頁(yè)長(zhǎng)度 16KB,默認(rèn)的虛擬地址寬度是 47 位。
(3)如果選擇頁(yè)長(zhǎng)度 64KB,默認(rèn)的虛擬地址寬度是 42 位。
(4)可以選擇 48 位虛擬地址。
在 ARM64 架構(gòu)的 Linux 內(nèi)核中,內(nèi)核虛擬地址和用戶虛擬地址的寬度相同。
所有進(jìn)程共享內(nèi)核虛擬地址空間,每個(gè)進(jìn)程有獨(dú)立的用戶虛擬地址空間,同一個(gè)線程組的用戶線程共享用戶虛擬地址空間,內(nèi)核線程沒(méi)有用戶虛擬地址空間。
3.2.2 用戶虛擬地址空間布局
進(jìn)程的用戶虛擬地址空間的起始地址是 0,長(zhǎng)度是 TASK_SIZE,由每種處理器架構(gòu)定義自己的宏 TASK_SIZE。ARM64 架構(gòu)定義的宏 TASK_SIZE 如下所示。
(1)32 位用戶空間程序:TASK_SIZE 的值是TASK_SIZE_32,即0x100000000,等于4GB。
(2)64 位用戶空間程序:TASK_SIZE 的值是 TASK_SIZE_64,即 2VA_BITS字節(jié),VA_BITS是編譯內(nèi)核時(shí)選擇的虛擬地址位數(shù)。
arch/arm64/include/asm/memory.h
#define VA_BITS (CONFIG_ARM64_VA_BITS)
#define TASK_SIZE_64 (UL(1) << VA_BITS)
#ifdef CONFIG_COMPAT /* 支持執(zhí)行32位用戶空間程序 */
#define TASK_SIZE_32 UL(0x100000000)
/* test_thread_flag(TIF_32BIT)判斷用戶空間程序是不是32位 */
#define TASK_SIZE (test_thread_flag(TIF_32BIT) ?
TASK_SIZE_32 : TASK_SIZE_64)
#define TASK_SIZE_OF(tsk) (test_tsk_thread_flag(tsk, TIF_32BIT) ?
TASK_SIZE_32 : TASK_SIZE_64)
#else
#define TASK_SIZE TASK_SIZE_64
#endif /* CONFIG_COMPAT */
進(jìn)程的用戶虛擬地址空間包含以下區(qū)域。
(1)代碼段、數(shù)據(jù)段和未初始化數(shù)據(jù)段。
(2)動(dòng)態(tài)庫(kù)的代碼段、數(shù)據(jù)段和未初始化數(shù)據(jù)段。
(3)存放動(dòng)態(tài)生成的數(shù)據(jù)的堆。
(4)存放局部變量和實(shí)現(xiàn)函數(shù)調(diào)用的棧。
(5)存放在棧底部的環(huán)境變量和參數(shù)字符串。
(6)把文件區(qū)間映射到虛擬地址空間的內(nèi)存映射區(qū)域。
內(nèi)核使用內(nèi)存描述符 mm_struct 描述進(jìn)程的用戶虛擬地址空間,內(nèi)存描述符的主要成員如表 3.1 所示。
進(jìn)程描述符(task_struct)中和內(nèi)存描述符相關(guān)的成員如表 3.2 所示。
如果進(jìn)程不屬于線程組,那么進(jìn)程描述符和內(nèi)存描述符的關(guān)系如圖 3.3 所示,進(jìn)程描述符的成員 mm 和 active_mm 都指向同一個(gè)內(nèi)存描述符,內(nèi)存描述符的成員 mm_users 是 1、成員 mm_count 是 1。
如果兩個(gè)進(jìn)程屬于同一個(gè)線程組,那么進(jìn)程描述符和內(nèi)存描述符的關(guān)系如圖 3.4 所示,每個(gè)進(jìn)程的進(jìn)程描述符的成員 mm 和 active_mm 都指向同一個(gè)內(nèi)存描述符,內(nèi)存描述符的成員 mm_users 是 2、成員 mm_count 是 1。
內(nèi)核線程的進(jìn)程描述符和內(nèi)存描述符的關(guān)系如圖 3.5 所示,內(nèi)核線程沒(méi)有用戶虛擬地址空間,當(dāng)內(nèi)核線程沒(méi)有運(yùn)行的時(shí)候,進(jìn)程描述符的成員 mm 和 active_mm 都是空指針;當(dāng)內(nèi)核線程運(yùn)行的時(shí)候,借用上一個(gè)進(jìn)程的內(nèi)存描述符,在被借用進(jìn)程的用戶虛擬地址空間的上方運(yùn)行,進(jìn)程描述符的成員 active_mm 指向借用的內(nèi)存描述符,假設(shè)被借用的內(nèi)存描述符所屬的進(jìn)程不屬于線程組,那么內(nèi)存描述符的成員 mm_users 不變,仍然是 1,成員mm_count 加 1 變成 2。
為了使緩沖區(qū)溢出攻擊更加困難,內(nèi)核支持為內(nèi)存映射區(qū)域、棧和堆選擇隨機(jī)的起始地址。進(jìn)程是否使用虛擬地址空間隨機(jī)化的功能,由以下兩個(gè)因素共同決定。
(1)進(jìn)程描述符的成員 personality(個(gè)性化)是否設(shè)置 ADDR_NO_RANDOMIZE。
(2)全局變量 randomize_va_space:0 表示關(guān)閉虛擬地址空間隨機(jī)化,1 表示使內(nèi)存映射區(qū)域和棧的起始地址隨機(jī)化,2 表示使內(nèi)存映射區(qū)域、棧和堆的起始地址隨機(jī)化??梢酝ㄟ^(guò)文件“/proc/sys/kernel/randomize_va_space”修改。
mm/memory.c
int randomize_va_space __read_mostly =
#ifdef CONFIG_COMPAT_BRK
1;
#else
2;
#endif
為了使舊的應(yīng)用程序(基于 libc5)正常運(yùn)行,默認(rèn)打開(kāi)配置宏 CONFIG_COMPAT_BRK,禁止堆隨機(jī)化。所以默認(rèn)配置是使內(nèi)存映射區(qū)域和棧的起始地址隨機(jī)化。
棧通常自頂向下增長(zhǎng),當(dāng)前只有惠普公司的 PA-RISC 處理器的棧是自底向上增長(zhǎng)。棧的起始地址是 STACK_TOP,默認(rèn)啟用棧隨機(jī)化,需要把起始地址減去一個(gè)隨機(jī)值。STACK_TOP是每種處理器架構(gòu)自定義的宏,ARM64 架構(gòu)定義的 STACK_TOP 如下所示:如果是 64 位用戶空間程序,STACK_TOP 的值是 TASK_SIZE_64;如果是 32 位用戶空間程序,STACK_TOP的值是異常向量的基準(zhǔn)地址 0xFFFF0000。
arch/arm64/include/asm/processor.h
#define STACK_TOP_MAX TASK_SIZE_64
#ifdef CONFIG_COMPAT /* 支持執(zhí)行32位用戶空間程序 */
#define AARCH32_VECTORS_BASE 0xffff0000
#define STACK_TOP (test_thread_flag(TIF_32BIT) ?
AARCH32_VECTORS_BASE : STACK_TOP_MAX)
#else
#define STACK_TOP STACK_TOP_MAX
#endif /* CONFIG_COMPAT */
內(nèi)存映射區(qū)域的起始地址是內(nèi)存描述符的成員 mmap_base。如圖 3.6 所示,用戶虛擬
地址空間有兩種布局,區(qū)別是內(nèi)存映射區(qū)域的起始位置和增長(zhǎng)方向不同。
(1)傳統(tǒng)布局:內(nèi)存映射區(qū)域自底向上增長(zhǎng),起始地址是 TASK_UNMAPPED_BASE,每種處理器架構(gòu)都要定義這個(gè)宏,ARM64 架構(gòu)定義為 TASK_SIZE/4。默認(rèn)啟用內(nèi)存映射區(qū)域隨機(jī)化,需要把起始地址加上一個(gè)隨機(jī)值。傳統(tǒng)布局的缺點(diǎn)是堆的最大長(zhǎng)度受到限制,在 32 位系統(tǒng)中影響比較大,但是在 64 位系統(tǒng)中這不是問(wèn)題。
(2)新布局:內(nèi)存映射區(qū)域自頂向下增長(zhǎng),起始地址是(STACK_TOP ? 棧的最大長(zhǎng)度? 間隙)。默認(rèn)啟用內(nèi)存映射區(qū)域隨機(jī)化,需要把起始地址減去一個(gè)隨機(jī)值。當(dāng)進(jìn)程調(diào)用 execve 以裝載 ELF 文件的時(shí)候,函數(shù) load_elf_binary 將會(huì)創(chuàng)建進(jìn)程的用戶虛擬地址空間。函數(shù) load_elf_binary 創(chuàng)建用戶虛擬地址空間的過(guò)程如圖 3.7 所示。
如果沒(méi)有給進(jìn)程描述符的成員 personality 設(shè)置標(biāo)志位ADDR_NO_RANDOMIZE(該標(biāo)志位表示禁止虛擬地址空間隨機(jī)化),并且全局變量 randomize_va_space 是非零值,那么給進(jìn)程設(shè)置標(biāo)志 PF_RANDOMIZE,允許虛擬地址空間隨機(jī)化。
各種處理器架構(gòu)自定義的函數(shù) arch_pick_mmap_layout 負(fù)責(zé)選擇內(nèi)存映射區(qū)域的布局。ARM64 架構(gòu)定義的函數(shù) arch_pick_mmap_layout 如下:
arch/arm64/mm/mmap.c
1 void arch_pick_mmap_layout(struct mm_struct *mm)
2 {
3 unsigned long random_factor = 0UL;
4
5 if (current->flags & PF_RANDOMIZE)
6 random_factor = arch_mmap_rnd();
7
8if(mmap_is_legacy()){
9 mm->mmap_base = TASK_UNMAPPED_BASE + random_factor;
10 mm->get_unmapped_area = arch_get_unmapped_area;
11 } else {
12 mm->mmap_base = mmap_base(random_factor);
13 mm->get_unmapped_area = arch_get_unmapped_area_topdown;
14 }
15 }
16
17 static int mmap_is_legacy(void)
18 {
19 if (current->personality & ADDR_COMPAT_LAYOUT)
20 return 1;
21
22 if (rlimit(RLIMIT_STACK) == RLIM_INFINITY)
23 return 1;
24
25 return sysctl_legacy_va_layout;
26 }
第 8~10 行代碼,如果給進(jìn)程描述符的成員 personality 設(shè)置標(biāo)志位 ADDR_COMPAT_LAYOUT 表示使用傳統(tǒng)的虛擬地址空間布局,或者用戶棧可以無(wú)限增長(zhǎng),或者通過(guò)文件“/proc/sys/vm/legacy_va_layout”指定,那么使用傳統(tǒng)的自底向上增長(zhǎng)的布局,內(nèi)存映射區(qū)域的起始地址是 TASK_UNMAPPED_BASE 加上隨機(jī)值,分配未映射區(qū)域的函數(shù)是arch_get_unmapped_area。
第 11~13 行代碼,如果使用自頂向下增長(zhǎng)的布局,那么分配未映射區(qū)域的函數(shù)是 arch_get_unmapped_area_topdown,內(nèi)存映射區(qū)域的起始地址的計(jì)算方法如下:
arch/arm64/include/asm/elf.h
#ifdef CONFIG_COMPAT
#define STACK_RND_MASK (test_thread_flag(TIF_32BIT) ?
0x7ff >> (PAGE_SHIFT - 12) :
0x3ffff >> (PAGE_SHIFT - 12))
#else
#define STACK_RND_MASK (0x3ffff >> (PAGE_SHIFT - 12))
#endif
arch/arm64/mm/mmap.c
#define MIN_GAP (SZ_128M + ((STACK_RND_MASK << PAGE_SHIFT) + 1))
#define MAX_GAP (STACK_TOP/6*5)
static unsigned long mmap_base(unsigned long rnd)
{
unsigned long gap = rlimit(RLIMIT_STACK);
if (gap < MIN_GAP)
gap = MIN_GAP;
else if (gap > MAX_GAP)
gap = MAX_GAP;
return PAGE_ALIGN(STACK_TOP - gap - rnd);
}
先計(jì)算內(nèi)存映射區(qū)域的起始地址和棧頂?shù)拈g隙:初始值取用戶棧的最大長(zhǎng)度,限定不能小于“128MB + 棧的最大隨機(jī)偏移值 + 1”,確保用戶棧最大可以達(dá)到 128MB;限定不能超過(guò) STACK_TOP 的 5/6。內(nèi)存映射區(qū)域的起始地址等于“STACK_TOP?間隙?隨機(jī)值”,然后向下對(duì)齊到頁(yè)長(zhǎng)度。
回到函數(shù)load_elf_binary:函數(shù) setup_arg_pages 把棧頂設(shè)置為 STACK_TOP 減去隨機(jī)120 3.2 虛擬地址空間布局值,然后把環(huán)境變量和參數(shù)從臨時(shí)棧移到最終的用戶棧;函數(shù) set_brk 設(shè)置堆的起始地址,如果啟用堆隨機(jī)化,把堆的起始地址加上隨機(jī)值。
fs/binfmt_elf.c
static int load_elf_binary(struct linux_binprm *bprm)
{
…
retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),
executable_stack);
…
retval = set_brk(elf_bss, elf_brk, bss_prot);
…
if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
current->mm->brk = current->mm->start_brk =
arch_randomize_brk(current->mm);
}
…
}
3.2.3 內(nèi)核地址空間布局
ARM64 處理器架構(gòu)的內(nèi)核地址空間布局如圖 3.8 所示。
(1)線性映射區(qū)域的范圍是[PAGE_OFFSET,],起始位置是 PAGE_OFFSET =(0xFFFF FFFF FFFF FFFF << (VA_BITS-1)),長(zhǎng)度是內(nèi)核虛擬地址空間的一半。稱為線性映射區(qū)域的原因是虛擬地址和物理地址是線性關(guān)系:
虛擬地址 =((物理地址 ? PHYS_OFFSET)+ PAGE_OFFSET),其中 PHYS_OFFSET是內(nèi)存的起始物理地址。
(2)vmemmap 區(qū)域的范圍是 [VMEMMAP_START, PAGE_OFFSET),長(zhǎng)度是
VMEMMAP_SIZE =(線性映射區(qū)域的長(zhǎng)度 / 頁(yè)長(zhǎng)度 * page 結(jié)構(gòu)體的長(zhǎng)度上限)。
內(nèi)核使用 page 結(jié)構(gòu)體描述一個(gè)物理頁(yè),內(nèi)存的所有物理頁(yè)對(duì)應(yīng)一個(gè) page 結(jié)構(gòu)體數(shù)組。如果內(nèi)存的物理地址空間不連續(xù),存在很多空洞,稱為稀疏內(nèi)存。vmemmap 區(qū)域是稀疏內(nèi)存的 page 結(jié)構(gòu)體數(shù)組的虛擬地址空間。
(3)PCI I/O 區(qū)域的范圍是[PCI_IO_START, PCI_IO_END),長(zhǎng)度是 16MB,結(jié)束地址是PCI_IO_END = (VMEMMAP_START ? 2MB)。
外圍組件互聯(lián)(Peripheral Component Interconnect,PCI)是一種總線標(biāo)準(zhǔn),PCI I/O 區(qū)域是 PCI 設(shè)備的 I/O 地址空間。
(4)固定映射區(qū)域的范圍是[FIXADDR_START, FIXADDR_TOP),長(zhǎng)度是FIXADDR_SIZE,結(jié)束地址是 FIXADDR_TOP = (PCI_IO_START ? 2MB)。
固定地址是編譯時(shí)的特殊虛擬地址,編譯的時(shí)候是一個(gè)常量,在內(nèi)核初始化的時(shí)候映射到物理地址。
(5)vmalloc 區(qū)域的范圍是[VMALLOC_START, VMALLOC_END),起始地址是VMALLOC_START,等于內(nèi)核模塊區(qū)域的結(jié)束地址,結(jié)束地址是 VMALLOC_END = (PAGE_OFFSET ?PUD_SIZE ? VMEMMAP_SIZE ? 64KB),其中 PUD_SIZE 是頁(yè)上級(jí)目錄表項(xiàng)映射的地址空間的長(zhǎng)度。
vmalloc 區(qū)域是函數(shù) vmalloc 使用的虛擬地址空間,內(nèi)核使用 vmalloc 分配虛擬地址連續(xù)但物理地址不連續(xù)的內(nèi)存。內(nèi)核鏡像在 vmalloc 區(qū)域,起始虛擬地址是(KIMAGE_VADDR + TEXT_OFFSET) ,其中 KIMAGE_VADDR 是內(nèi)核鏡像的虛擬地址的基準(zhǔn)值,等于內(nèi)核模塊區(qū)域的結(jié)束地址MODULES_END;TEXT_OFFSET 是內(nèi)存中的內(nèi)核鏡像相對(duì)內(nèi)存起始位置的偏移。
(6)內(nèi)核模塊區(qū)域的范圍是[MODULES_VADDR, MODULES_END),長(zhǎng)度是 128MB,起始地址是 MODULES_VADDR =(內(nèi)核虛擬地址空間的起始地址 + KASAN 影子區(qū)域的長(zhǎng)度)。
內(nèi)核模塊區(qū)域是內(nèi)核模塊使用的虛擬地址空間。
(7)KASAN 影子區(qū)域的起始地址是內(nèi)核虛擬地址空間的起始地址,長(zhǎng)度是內(nèi)核虛擬地址空間長(zhǎng)度的 1/8。
內(nèi)核地址消毒劑(Kernel Address SANitizer,KASAN)是一個(gè)動(dòng)態(tài)的內(nèi)存錯(cuò)誤檢查工具。它為發(fā)現(xiàn)釋放后使用和越界訪問(wèn)這兩類缺陷提供了快速和綜合的解決方案。
3.3 物理地址空間
物理地址是處理器在系統(tǒng)總線上看到的地址。使用精簡(jiǎn)指令集(Reduced Instruction SetComputer,RISC)的處理器通常只實(shí)現(xiàn)一個(gè)物理地址空間,外圍設(shè)備和物理內(nèi)存使用統(tǒng)一的物理地址空間。有些處理器架構(gòu)把分配給外圍設(shè)備的物理地址區(qū)域稱為設(shè)備內(nèi)存。
處理器通過(guò)外圍設(shè)備控制器的寄存器訪問(wèn)外圍設(shè)備,寄存器分為控制寄存器、狀態(tài)寄存器和數(shù)據(jù)寄存器三大類,外圍設(shè)備的寄存器通常被連續(xù)地編址。處理器對(duì)外圍設(shè)備寄存器的編址方式有兩種。
(1)I/O 映射方式(I/O-mapped):英特爾的 x86 處理器為外圍設(shè)備專門實(shí)現(xiàn)了一個(gè)單獨(dú)的地址空間,稱為“I/O 地址空間”或“I/O 端口空間”,處理器通過(guò)專門的 I/O 指令(如x86 的 in 和 out 指令)來(lái)訪問(wèn)這一空間中的地址單元。
(2)內(nèi)存映射方式(memory-mapped):使用精簡(jiǎn)指令集的處理器通常只實(shí)現(xiàn)一個(gè)物理地址空間,外圍設(shè)備和物理內(nèi)存使用統(tǒng)一的物理地址空間,處理器可以像訪問(wèn)一個(gè)內(nèi)存單元那樣訪問(wèn)外圍設(shè)備,不需要提供專門的 I/O 指令。
程序只能通過(guò)虛擬地址訪問(wèn)外設(shè)寄存器,內(nèi)核提供了以下函數(shù)來(lái)把外設(shè)寄存器的物理地址映射到虛擬地址空間。
(1)函數(shù) ioremap()把外設(shè)寄存器的物理地址映射到內(nèi)核虛擬地址空間。
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
(2)函數(shù) io_remap_pfn_range()把外設(shè)寄存器的物理地址映射到進(jìn)程的用戶虛擬地址空間。
int io_remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,unsigned long pfn,
unsigned long size, pgprot_t prot);
除了 SPARC 處理器以外,在其他處理器架構(gòu)中函數(shù) io_remap_pfn_range()和函數(shù) remap_pfn_range()等價(jià)。函數(shù) remap_pfn_range()用于把內(nèi)存的物理頁(yè)映射到進(jìn)程的用戶虛擬地址空間。
內(nèi)核提供了函數(shù) iounmap(),它用來(lái)刪除函數(shù) ioremap()創(chuàng)建的映射。
void iounmap(void *addr);
ARM64 架構(gòu)的實(shí)現(xiàn)
ARM64 架構(gòu)定義了兩種內(nèi)存類型。
(1)正常內(nèi)存(Normal Memory):包括物理內(nèi)存和只讀存儲(chǔ)器(ROM)。
(2)設(shè)備內(nèi)存(Device Memory):指分配給外圍設(shè)備寄存器的物理地址區(qū)域。
對(duì)于正常內(nèi)存,可以設(shè)置共享屬性和緩存屬性。共享屬性用來(lái)定義一個(gè)位置是否可以被多個(gè)核共享,分為不可共享、內(nèi)部共享和外部共享。不可共享是指只被處理器的一個(gè)核使用,內(nèi)部共享是指一個(gè)處理器的所有核共享或者多個(gè)處理器共享,外部共享是指處理器和其他觀察者(比如圖形處理單元或 DMA 控制器)共享。緩存屬性用來(lái)定義訪問(wèn)時(shí)是否通過(guò)處理器的緩存。
設(shè)備內(nèi)存的共享屬性總是外部共享,緩存屬性總是不可緩存(即必須繞過(guò)處理器的緩存)。
ARM64 架構(gòu)根據(jù) 3 種屬性把設(shè)備內(nèi)存分為 4 種類型。
(1)Device-nGnRnE,這種類型限制最嚴(yán)格。
(2)Device-nGnRE。
(3)Device-nGRE。
(4)Device-GRE,這種類型限制最少。
3 種屬性分別如下。
(1)聚集屬性:G 表示聚集(Gathering),nG 表示不聚集(non Gathering)。聚集屬性決定對(duì)內(nèi)存區(qū)域的多個(gè)訪問(wèn)是否可以被合并為一個(gè)總線事務(wù)。如果地址被標(biāo)記為“不聚集”,那么必須按照程序里面的地址和長(zhǎng)度訪問(wèn)。如果地址被標(biāo)記為“聚集”,處理器可以把兩個(gè)“寫(xiě)一個(gè)字節(jié)”的訪問(wèn)合并成一個(gè)“寫(xiě)兩個(gè)字節(jié)”的訪問(wèn),可以把對(duì)相同內(nèi)存位置的多個(gè)訪問(wèn)合并,例如讀相同位置兩次,處理器只需要讀一次,為兩條指令返回相同的結(jié)果。
(2)重排序?qū)傩裕?/span>R 表示重排序(Re-ordering),nR 表示不重排序(non Re-ordering)。這個(gè)屬性決定對(duì)相同設(shè)備的多個(gè)訪問(wèn)是否可以重新排序。如果地址被標(biāo)記為“不重排序”,那么對(duì)同一個(gè)塊的訪問(wèn)總是按照程序順序執(zhí)行。
(3)早期寫(xiě)確認(rèn)屬性:E 表示早期寫(xiě)確認(rèn)(Early Write Acknowledgement),nE 表示不執(zhí)行早期寫(xiě)確認(rèn)(non Early Write Acknowledgement)。
這個(gè)屬性決定是否允許處理器和從屬設(shè)備之間的中間寫(xiě)緩沖區(qū)發(fā)送“寫(xiě)完成”確認(rèn)。如果地址被標(biāo)記為“不執(zhí)行早期寫(xiě)確認(rèn)”,那么必須由外圍設(shè)備發(fā)送“寫(xiě)完成”確認(rèn)。如果地址被標(biāo)記為“早期寫(xiě)確認(rèn)”,那么允許寫(xiě)緩沖區(qū)在外圍設(shè)備收到數(shù)據(jù)之前發(fā)送“寫(xiě)完成”確認(rèn)。
物理地址寬度
目前 ARM64 處理器支持的最大物理地址寬度是 48 位,如果實(shí)現(xiàn)了 ARMv8.2 標(biāo)準(zhǔn)的大物理地址(Large Physical Address,LPA)支持,并且頁(yè)長(zhǎng)度是 64KB,那么物理地址的最大寬度是 52 位。
可以使用寄存器 TCR_EL1(Translation Control Register for Exception Level 1,異常級(jí)別 1 的轉(zhuǎn)換控制寄存器)的字段 IPS(Intermediate Physical Address Size,中間物理地址長(zhǎng)度)控制物理地址的寬度,IPS 字段的長(zhǎng)度是 3 位,IPS 字段的值和物理地址寬度的對(duì)應(yīng)關(guān)系如表 3.3 所示。
審核編輯 :李倩
-
Linux
+關(guān)注
關(guān)注
87文章
11207瀏覽量
208717 -
分配器
+關(guān)注
關(guān)注
0文章
193瀏覽量
25694 -
內(nèi)存管理
+關(guān)注
關(guān)注
0文章
168瀏覽量
14115
原文標(biāo)題:《Linux內(nèi)核深度解析》選載之內(nèi)存地址空間
文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論