linux內存管理解析1----linux物理,線性內存佈局及頁表的初始化

主要議題:linux

1分頁,分段模式及實模式ios

2Linux分頁 編程

3linux內存線性地址空間佈局及物理內存空間佈局數組

4linux頁表初始化及代碼解析數據結構


1.1.1內存尋址和保護模式
在X86平臺上,內存控制單元經過分段單元電路把邏輯地址轉換爲線性地址,又經過分頁單元把線性地址轉換爲物理地址。
 




    一個邏輯地址由段標識符和段內偏移地址組成。段標示符是一個16位長度的字段,稱爲段選擇符,而偏移地址是32位的字段。app


 
     通常用段寄存器來保存段選擇符,如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,在實際的代碼中對應的頁上級目錄和頁中間目錄都只有一項,其地址和其所屬的頁全局目錄的項是同樣的.......oop


 
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); }

相關文章
相關標籤/搜索