Linux分段
Linux以非常有限的方式使用分段。2.6 版本的Linux只有x86結構才需要分段。
四個主要的Linux段:
段 | Base | G | Limit | S | Type | DPL | D/B | P |
---|---|---|---|---|---|---|---|---|
用戶代碼段 | 0x00000000 | 1 | 0xfffff | 1 | 10 | 3 | 1 | 1 |
用戶數據段 | 0x00000000 | 1 | 0xfffff | 1 | 2 | 3 | 1 | 1 |
內核代碼段 | 0x00000000 | 1 | 0xfffff | 1 | 10 | 0 | 1 | 1 |
內核數據段 | 0x00000000 | 1 | 0xfffff | 1 | 2 | 0 | 1 | 1 |
所有段都是從0x00000000開始,說明Linux邏輯地址和線性地址是一致的,即偏移量字段和相關線性地址是一致的。
CPL反應了進程是在用戶態還是內核態。
GDT
每個CPU都有一個全局描述符表(GDT), 包含18個段描述符和14個空的。
LDT
大多數用戶態下的Linux程序不使用局部描述符,內核定義了一個缺省的LDT供大多數進程使用。
Linux分頁
2.6.11 版本後Linux採用四級分頁(爲了兼容)
- 頁全局目錄 Page Global Directory
- 頁上級目錄 Page Upper Directory
- 頁中間級目錄 Page Middle Directory
- 頁表 Page Table
分幾種情況
1. 32位系統無PAE,二級目錄 頁全局+頁表+offset;
2. 32位系統帶PAE,三級頁全局+中間級目錄+頁表+offset;
3. 64位系統採用三級還是四級依賴於對線性地址的劃分。
注:開啓大頁(4MB or 2MB)的話,頁表和offset合併爲offset.
內核頁表的建立
物理內存佈局
在初始化階段,內核必須建立一個物理地址來指定哪些物理地址對內核可用而哪些不可用。
一般來說,Linux內核安裝在RAM中從物理地址0x0010 0000開始的地方,也就是說從第二MB開始。典型的配置所得到的內核可以安裝在小於3MB的RAM中。(這麼做是因爲PC體系結構,RAM的前1MB留供BIOS使用)。
因爲內核剛被載入到RAM當中,CPU仍處於實模式,分頁功能還沒開啓。因此內核初始化自己的頁框,分爲兩個階段。
1. 內核創建一個有限的地址空間,包括內核的代碼段和數據段、初始頁表和用於存放動態數據結構的共128KB大小的空間。這個空間只夠內核裝入RAM和對其初始化的核心數據結構。
2. 內核充分利用剩餘的RAM並適當建立分頁頁表。
下面代碼是內核初始化頁表的源代碼(Linux kernel 4.0)。爲了能在保護模式和實模式下都能尋址,頁目錄項的0 和768, 1 和769 …. 指向同一頁表(未開啓PAE)。
#if PTRS_PER_PMD > 1
#define PAGE_TABLE_SIZE(pages) (((pages) / PTRS_PER_PMD) + PTRS_PER_PGD)
#else
#define PAGE_TABLE_SIZE(pages) ((pages) / PTRS_PER_PGD)
#endif
/*
* Number of possible pages in the lowmem region.
*
* We shift 2 by 31 instead of 1 by 32 to the left in order to avoid a
* gas warning about overflowing shift count when gas has been compiled
* with only a host target support using a 32-bit type for internal
* representation.
*/
LOWMEM_PAGES = (((2<<31) - __PAGE_OFFSET) >> PAGE_SHIFT)
MAPPING_BEYOND_END = PAGE_TABLE_SIZE(LOWMEM_PAGES) << PAGE_SHIFT
/* Enough space to fit pagetables for the low memory linear map */
MAPPING_BEYOND_END = PAGE_TABLE_SIZE(LOWMEM_PAGES) << PAGE_SHIFT
可以算出 MAPPING_BEYOND_END = 0x 10 0000 // 1MB
/*
* Initialize page tables. This creates a PDE and a set of page
* tables, which are located immediately beyond __brk_base. The variable
* _brk_end is set up to point to the first "safe" location.
* Mappings are created both at virtual address 0 (identity mapping)
* and PAGE_OFFSET for up to _end.
*/
#ifdef CONFIG_X86_PAE
/*
* In PAE mode initial_page_table is statically defined to contain
* enough entries to cover the VMSPLIT option (that is the top 1, 2 or 3
* entries). The identity mapping is handled by pointing two PGD entries
* to the first kernel PMD.
*
* Note the upper half of each PMD or PTE are always zero at this stage.
*/
#define KPMDS (((-__PAGE_OFFSET) >> 30) & 3) /* Number of kernel PMDs */
xorl %ebx,%ebx /* %ebx is kept at zero */
movl $pa(__brk_base), %edi
movl $pa(initial_pg_pmd), %edx
movl $PTE_IDENT_ATTR, %eax
10:
leal PDE_IDENT_ATTR(%edi),%ecx /* Create PMD entry */
movl %ecx,(%edx) /* Store PMD entry */
/* Upper half already zero */
addl $8,%edx
movl $512,%ecx
11:
stosl
xchgl %eax,%ebx
stosl
xchgl %eax,%ebx
addl $0x1000,%eax
loop 11b
/*
* End condition: we must map up to the end + MAPPING_BEYOND_END.
*/
movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp
cmpl %ebp,%eax
jb 10b
1:
addl $__PAGE_OFFSET, %edi
movl %edi, pa(_brk_end)
shrl $12, %eax
movl %eax, pa(max_pfn_mapped)
/* Do early initialization of the fixmap area */
movl $pa(initial_pg_fixmap)+PDE_IDENT_ATTR,%eax
movl %eax,pa(initial_pg_pmd+0x1000*KPMDS-8)
#else /* Not PAE */
page_pde_offset = (__PAGE_OFFSET >> 20); // 0xc0000000 >> 20 ???
movl $pa(__brk_base), %edi //__brk_base??? /*__brk_base==PTD*/ // pg0的地址 __end
movl $pa(initial_page_table), %edx //initial_page_table 和 __brk_base 不再一個位置
movl $PTE_IDENT_ATTR, %eax //???
10:
leal PDE_IDENT_ATTR(%edi),%ecx /* Create PDE entry */ #define PDE_IDENT_ATTR 0x063 /* PRESENT+RW+DIRTY+ACCESSED */ 頁目錄項
movl %ecx,(%edx) /* Store identity PDE entry */
movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry */ // ?? 0xc00 / 4 = 0x300 -> 768 實模式和保護模式指向同一頁表
addl $4,%edx // next page ??
movl $1024, %ecx //循環 1024次 建立1024個表項
11: //初始化頁表 1024項
stosl ///eax的內容放入 edi指向的物理地址 edi+=4
addl $0x1000,%eax // 所有項都填0 /*eax: 0x1063, 0x2063, 0x3063 …, 0x3ff063*/ /* PRESENT+RW+DIRTY+ACCESSED */
loop 11b // 內循環填充表目錄
/*
* End condition: we must map up to the end + MAPPING_BEYOND_END.
*/
movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp //這個地方不太明白。。。 1MB
cmpl %ebp,%eax ebp >= eax jump 繼續映射下個目錄項
jb 10b
addl $__PAGE_OFFSET, %edi // + 0xc000 0000 線性地址
movl %edi, pa(_brk_end) // 存入 brk 末端
shrl $12, %eax // 右移12位 最終頁表地址--> 得到目錄項
movl %eax, pa(max_pfn_mapped) //計算最大頁數 max_pfn_mapped
/* Do early initialization of the fixmap area */ //高端映射??
movl $pa(initial_pg_fixmap)+PDE_IDENT_ATTR,%eax
movl %eax,pa(initial_page_table+0xffc) //目錄最後一項 保存 initial_pag_fixmap 表項 固定映射??
#endif
#endif
內核編譯後的可執行代碼中的地址都是虛擬地址,也就是說地址都大於0xc0000000, 目標是要內核映象在虛擬內核空間中運行。在 分頁機制開啓前這些地址是無效的。不能直接被送到cpu的外部地址總線上,用於直接尋址對應的物理內存。
enable_paging:
/*
* Enable paging
*/
movl $pa(initial_page_table), %eax
movl %eax,%cr3 /* set the page table pointer.. */
movl $CR0_STATE,%eax
movl %eax,%cr0 /* ..and set paging (PG) bit */
ljmp $__BOOT_CS,$1f /* Clear prefetch and normalize %eip */
1:
/* Shift the stack pointer to a virtual address */
addl $__PAGE_OFFSET, %esp
/*
* BSS section /***bss 區**/
*/
__PAGE_ALIGNED_BSS
.align PAGE_SIZE
#ifdef CONFIG_X86_PAE
initial_pg_pmd:
.fill 1024*KPMDS,4,0
#else
ENTRY(initial_page_table)
.fill 1024,4,0
#endif
initial_pg_fixmap:
.fill 1024,4,0
ENTRY(empty_zero_page)
.fill 4096,1,0
ENTRY(swapper_pg_dir)
.fill 1024,4,0