linux 进程装入 物理内存 页表,linux内存管理解析----linux物理,线性内存布局及页表的初始化...
主要議題:
1分頁,分段模式及實模式
2Linux分頁
3linux內存線性地址空間布局及物理內存空間布局
4linux頁表初始化及代碼解析
1.1.1內存尋址和保護模式
在X86平臺上,內存控制單元通過分段單元電路把邏輯地址轉換為線性地址,又通過分頁單元把線性地址轉換為物理地址。
一個邏輯地址由段標識符和段內偏移地址組成。段標示符是一個16位長度的字段,稱為段選擇符,而偏移地址是32位的字段。
一般用段寄存器來保存段選擇符,如CS,DS,ES,SS等,CS段選擇符中用RPL來表示CPU當前的特權級別,0表示工作在內核態,3標示工作在用戶態。每個段由一個8個字節的描述符進行管理,段描述符表放在GDT或者LDT中,通常只定義一個GDT,而每個進程除了GDT中的段外還需要創建附加的段,就可以有自己的LDT段,通常GDT段存放在GDTR控制寄存器中。
每當一個段選擇符被加入到段寄存器時,段描述符就被自動加載到非編程寄存器中.
實模式由于是由8086/8088發展而來因此他更像是一個運行單片機的簡單模式,計算機啟動后首先進入的就是實模式,通過8086/8088只有20根 地址線所以它的尋址范圍只有2的20次冪,即1M。內存的訪問方式就是我們熟悉的seg:offset邏輯地址方式,例如我們給出地址邏輯地址它將在 cpu內轉換為20的物理地址,即將seg左移4位再加上offset值。例如地址1000h:5678h,則物理地址為 10000h+5678h=15678h。實模式在后續的cpu中被保留了下來,但實模式的局限性是很明顯的,由于使用seg:offset邏輯地址只能 訪問1M多一點的內存空間,在擁有32根地址線的cpu中訪問1M以上的空間則變得很困難。而且隨著計算機的不斷發展實模式的工作方式越來越不能滿足計算機對資源(存儲資源和cpu資源等等)的管理,由此產生了新的管理方式——保護模式。
存儲方式主要體現在內存訪問方式上,由于兼容和IA32框架的限制,保護模式在內存訪問上延用了實模式下的seg:offset的形式(即:邏輯地址), 其實seg:offset的形式在保護模式下只是一個軀殼,內部的存儲方式與實模式截然不同。在保護模式下邏輯地址并不是直接轉換為物理地址,而是將邏輯 地址首先轉換為線性地址,再將線性地址轉換為物理地址。
1.1.2linux分段:
運行在用戶態的所有linux進程都使用同一對相同的段對指令和數據尋址,這兩個段就是所謂的用戶代碼段和用戶數據段,類似的,運行在內核態的所有linux進程都使用一對相同的段進行指令和數據的尋址:分別叫做內核代碼段和內核數據段。從下圖中可以看出linux下邏輯地址和線性地址其實是一致的。
每個處理器都有一個gdtr的寄存器,所有的gdt都存放在cpu_gdt_table數組里面,而所有GDT的地址和他們的大小都被存放在cpu_gdt_descr數組中。
1.1.3linux分頁:
在cpu中通過cr3寄存器來切換對應的頁表。
下面是線性地址和頁表之間的關系,反應了如何從一個線性地址找到一個物理頁面,并定位到相關字節。這個表反應的是32位86x86的映射機制:
對于64位cpu的頁表管理,一般使用三級或者四級頁表,X86_64使用的是四級頁表,幾級頁表主要是根據CPU硬件規格來制定的。
在linux內核中,統一使用四級頁表的數據結構來描述cpu的頁表結構,以達到代碼的統一。請注意,這里僅僅是用了四級頁表來進行描述cpu的頁表結構,不代表硬件上就是四級頁表,這里是邏輯上的四級。比如,32位的X86是兩級頁表,它要用四級頁表來表示的話,頁上級和頁中間目錄的位數就是為0,在實際的代碼中對應的頁上級目錄和頁中間目錄都只有一項,其地址和其所屬的頁全局目錄的項是一樣的.......
1.1.4linux物理內存布局
其中,不可用頁框(頁框0)主要是用來存放bios加電自檢期間檢測到的硬件配置,0x9f~0x100頁框即(640K~1M)留給bios例程,用來映射ISA圖形卡上的部分內存,_text表示地址0x100000,即1M用來存放內核的代碼段,_etext和_edata之間存放的是內核的已初始化的數據,_edata到_eend之間存放的是內核未初始化的數據,從_end到第768個頁框會之間映射到對應的內核空間使用,至于768個頁框以后的物理頁框,要在內核中直接使用的話,必須進行高端內存映射,或使用vmalloc()將他們映射到內核空間的3G+896~~4G的內核線性地址空間。這部分可以配合1.1.4中的linux虛擬內存布局來看。
1.1.5linux虛擬內存布局
內核通過內核頁全局目錄來管理所有的物理內存,由于線形地址前3G空間為用戶使用,內核頁全局目錄前768項(剛好3G)除0、1兩項外全部為0,后256項(1G)屬于linux內核的地址空間,用來管理所有的物理內存。內核頁全局目錄在編譯時靜態地定義為swapper_pg_dir數組,該數組從物理內存地址0x101000處開始存放。
由圖可見:
(1) 內核線形地址空間部分從PAGE_OFFSET(通常定義為3G)開始,為了將內核裝入內存,從PAGE_OFFSET開始8M線形地址用來映射內核所在的物理內存地址;(此處映射的物理地址是否包含了物理存儲布局中的內存中最開始的1M?)
(2)接下來是mem_map數組,mem_map的起始線形地址與體系結構相關,比如對于UMA結構,由于從PAGE_SIZE開始16M線形地址空間對應的16M物理地址空間是DMA區,mem_map數組通常開始于PAGE_SIZE+16M的線形地址;
(3)從PAGE_SIZE開始到VMALLOC_START – VMALLOC_OFFSET的線形地址空間直接映射到物理內存空間(一一對應映射,物理地址=線形地址-PAGE_OFFSET),這段區域的大小和機器實際擁有的物理內存大小有關,這兒VMALLOC_OFFSET在x86上為8M,主要用來防止越界錯誤;(這一段其實就是對DMA_ZONE和DMA_NORMAL區的物理內存進行直接映射)
(4)在內存比較小的系統上,余下的線形地址空間(還要再減去空白區即VMALLOC_OFFSET)被vmalloc()函數用來把不連續的物理地址空間映射到連續的線形地址空間上,在內存比較大的系統上,vmalloc()使用從VMALLOC_START到VMALLOC_END(也即PKMAP_BASE減去2頁的空白頁大小PAGE_SIZE)的線形地址空間
(5)此時余下的線形地址空間(還要再減去2頁的空白區即VMALLOC_OFFSET)又可以分成2部分:
第一部分從PKMAP_BASE到FIXADDR_START用來由kmap()函數映射高端內存;
第二部分,從FIXADDR_START到FIXADDR_TOP,這是一個固定大小的線形地址空間,(引用:Fixed virtual addresses are needed for subsystems that need to know the virtual address at compile time such as the APIC),在x86體系結構上,FIXADDR_TOP被靜態定義為0xFFFFE000,此時這個固定大小空間結束于整個線形地址空間最后4K前面,該固定大小空間大小是在編譯時計算出來并存儲在__FIXADDR_SIZE變量中。
正是由于vmalloc()使用區、kmap()使用區及固定大小區的存在才使ZONE_NORMAL區大小受到限制,由于內核在運行時需要這些函數,因此在線形地址空間中至少要VMALLOC_RESERVE大小的空間。VMALLOC_RESERVE的大小與體系結構相關,在x86上,VMALLOC_RESERVE定義為128M,這就是為什么我們看到ZONE_NORMAL大小通常是16M到896M的原因。
1.1.6內核頁表的初始化過程
主要分為兩個階段:
1第一個階段,內核需要創建一個有限的地址空間,用來存放內核的代碼段,數據段,初始頁表,和一些動態數據,這個最小限度地址空間的目的是僅僅能將內核加載進去以及讓內核做一些初始化的操作。一般可以認為這個最小限度地址空間大小為8MB。臨時業全局目錄在swap_pg_dir數組中,臨時頁表在pg0中存放。
當處于第一個階段時,cpu尚處于實模式的尋址模式,第一個階段的目標是讓實模式和保護模式下都能對著8MB的內存進行尋址。為此,需要把0x00000000~0x007fffff和0x0c000000~0xc7fffff的線性地址空間映射到0~0x7fffff的物理地址空間。在內核中,用swap_pg_dir來存放臨時頁全局目錄,可以將所有的頁全局目錄表項清0,然后把0,1,768,769這四項來進行設置,來達到我們的目的。(將0x00000000~0x007fffff線性地址也需要對應的在頁表里面進行設置,應該是為了兼容當前運行實模式的代碼,這樣在開啟了頁尋址模式后,通過分段+分頁尋址,尋到的物理地址會仍是運行在實模式時操作的物理地址)
臨時頁表由startup_32()來進行初始化,臨時的頁全局目錄是在編譯時初始化的。在startup_32()中建立臨時頁表:
//頁表初始化
page_pde_offset = (__PAGE_OFFSET >> 20);
movl $pa(__brk_base), %edi //第一張頁表的物理地址
movl $pa(swapper_pg_dir), %edx //頁目錄的物理地址
movl $PTE_IDENT_ATTR, %eax //頁目錄中項的標識位
10:
leal PDE_IDENT_ATTR(%edi),%ecx //PDE_IDENT_ATTR其實是0x007,這里是為了算出頁全//局目錄目錄項里應該被放入什么值
movl %ecx,(%edx) //存入對應的頁全局目錄項里面0,1
movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry *///存入對應的頁全局目錄項、、里面768,769
addl $4,%edx //下一個頁表項的地址
movl $1024, %ecx //每個頁表有1024項需要初始化
11:
stosl //存到頁表里,edi指向的地方
loop 11b //這個循環對每張頁表都會循環1024次, edi會自增。
movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp
cmpl %ebp,%eax
jb 10b
建立完頁表,啟用保護模式:
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
movl %eax,%cr3/* set the page table pointer.. */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0/* ..and set paging (PG) bit */
ljmp $__BOOT_CS,$1f/* Clear prefetch and normalize %eip */
2第二個階段,內核充分利用物理內存并適當的建立頁表。
內核在啟動后需要對內核頁表進行初始化(即對應上面的第二階段),對應代碼主要在函數kernel_physical_mappin g_init()中。以下是32位x86內核對于頁表進行的初始化代碼。
static void __init kernel_physical_mapping_init(pgd_t *pgd_base)
{
unsigned long pfn;
pgd_t *pgd;
pmd_t *pmd;
pte_t *pte;
int pgd_idx, pmd_idx, pte_ofs;
//計算linux內核態空間起始地址(3G) 在頁全局表中的索引
pgd_idx = pgd_index(PAGE_OFFSET);
pgd = pgd_base + pgd_idx;
pfn = 0;
//每個pgd對應有1024個表項,每個表項指向一個頁表
for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {
//在二級頁表的情形中,pmd和pgd的值是相等的
pmd = one_md_table_init(pgd);
if (pfn >= max_low_pfn)
continue;
//在二級頁表中,該PTRS_PER_PMD值為1
for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++, pmd_idx++) {
unsigned int address = pfn * PAGE_SIZE + PAGE_OFFSET;
/* Map with big pages if possible, otherwise create normal page tables. */
if (cpu_has_pse) {
unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1;
if (is_kernel_text(address) || is_kernel_text(address2))
set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));
else
set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE));
pfn += PTRS_PER_PTE;
} else {
//該pmd指向該page table
pte = one_page_table_init(pmd);
//每個頁表有1024個頁表項,指向1024個物理頁
for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte++, pfn++, pte_ofs++) {
//地址為kernel代碼區,設置對應頁表項,填入
//對應的物理頁的地址
if (is_kernel_text(address))
set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));
else
set_pte(pte, pfn_pte(pfn, PAGE_KERNEL));
}
}
}
}
}
static pte_t * __init one_page_table_init(pmd_t *pmd)
{
if (pmd_none(*pmd)) {
//分配頁表
pte_t *page_table = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);
//設置頁表地址到對應的目錄項中
set_pmd(pmd, __pmd(__pa(page_table) | _PAGE_TABLE));
if (page_table != pte_offset_kernel(pmd, 0))
BUG();
return page_table;
}
return pte_offset_kernel(pmd, 0);
}
總結
以上是生活随笔為你收集整理的linux 进程装入 物理内存 页表,linux内存管理解析----linux物理,线性内存布局及页表的初始化...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux crm高可用网卡,Linux
- 下一篇: linux apache gzip fi