arm-linux記憶體頁表建立
linux的記憶體(正式)頁表是在核心程式碼執行到start_kernel函式後執行paging _init函式建立的,這裡要注意一個事情就是說,這裡paging_init函式可以正常建立記憶體頁表的條件有兩個:
1、 meminfo已初始化:即初始化實體記憶體各個node的各個bank,一般對於小型arm嵌入式裝置,不涉及多個記憶體就是一個node和一個bank;這部分初始化是在paging_init函式前面的對uboot所傳引數的解析中完成的(可在核心的arm_add_memory函式中加入列印資訊驗證);
2、
全域性變數init_mm
Paging_init函式首先呼叫的是build_mem_type_table,這個函式做的事情就是給靜態全域性變數mem_types賦值,這個變數就在本檔案(arch/arm/mm/mmu.c)
接下來呼叫的是sanity_check_meminfo,這個函式主要做兩件事情,首先是確定本裝置實體記憶體的各個node各個bank中到底有沒有高階記憶體,根據是否存在高階記憶體決定每個bank的highmem成員值;然後是對於每個bank的正確性進行檢測;下面分別描述:
由下面程式碼判斷每個實體記憶體bank是否屬於高階記憶體:
if (__va(bank->start) > VMALLOC_MIN || __va(bank->start) < (void *)PAGE_OFFSET)
highmem = 1;
即:該bank的實體記憶體起始虛擬地址大於VMALLOC_MIN,或者小於PAGE_OFFSET;PAGE_OFFSET是核心使用者空間的交界,在這裡定義為0xc0000000也就是arm-linux普遍適用值3G/1G;VMALLOC_MIN就定義在本檔案(arch/arm/mm/mmu.c),如下:
#define VMALLOC_MIN (void *)(VMALLOC_END - vmalloc_reserve)
VMALLOC_END在arch/arm/mach-XXX/include/mach/vmalloc.h檔案中定義,可見是不同arm裝置可以不同的,它標誌著vmalloc區域的結尾在哪裡,這裡定義為3G+768M,如下:
#define VMALLOC_END (PAGE_OFFSET + 0x30000000)
靜態全域性變數vmalloc_reserve定義在本檔案(arch/arm/mm/mmu.c),它是可以由使用者指定的,指示了vmalloc區域的大小,預設值為128M,使用者指定的方式是通過uboot中指定“命令列”引數的vmalloc引數(“vmalloc=”,通過paging_init前的__early_param方式指定)修改核心中該變數的值,這裡採用的就是預設值128M;
用vmalloc區域的結尾(3G+768M)減去該區域的大小(128M)即得到了vmalloc區域的起始,3G + 768M - 128M = 3G + 640M(0xe8000000);
所以,一個bank的實體記憶體屬於高階記憶體的條件是:
1、 起始地址不大於vmalloc區域的起始虛擬地址;
2、 起始地址不小於核心使用者交界的虛擬地址;
當屬於高階記憶體時,該bank的highmem成員將置1。
除了界定實體記憶體bank是否屬於高階記憶體,sanity_check_meminfo函式還對每個實體記憶體bank的正確性進行檢測,這部分個人認為不是重點,主要注意下在存在高階記憶體情況下(程式碼中定義巨集CONFIG_HIGHMEM情況下),若低端記憶體太大(起始位置在VMALLOC_MIN之前,結尾位置超過VMALLOC_MIN),則超過VMALLOC_MIN的部分將被算進另一個bank並且判定為高階記憶體。
接下來呼叫的是prepare_page_table,它的作用是清除在核心程式碼執行到start_kernel之前時建立的大部分臨時記憶體頁表,這裡需要對arm-linux記憶體頁表的機制原理進行理解:
首先一個是,什麼是記憶體頁表,都有哪些屬於記憶體(注意這裡的記憶體是廣義上的記憶體,不單單是實體記憶體)?
具體的說,記憶體頁表,更應該叫記憶體對映,對於有MMU的CPU來說,CPU訪問實體記憶體或某個SOC硬體暫存器,所做的操作並非是直接把它們的實體地址放在CPU的地址匯流排,而是把一個虛擬地址交給MMU,如果MMU硬體存在這個虛擬地址對應的實體地址(這個對映關係就是需要建立的內容,也就是記憶體對映!),那麼它就會把對應的實體地址放在地址總線上。這樣做最大的好處是,避免了軟體程式直接訪問一個不存在的地址導致出現問題 + 使用者程式可使用的“記憶體”很大。
再說哪些東西屬於記憶體,很簡單,不僅僅實體記憶體,所有通過CPU地址匯流排連線的都屬於記憶體,比如SOC的硬體暫存器,這裡有個方法驗證這個道理,函式create_mapping是最終建立記憶體對映的函式,看看它都被哪些函式呼叫:
map_memory_bank:這是為實體記憶體建立記憶體對映
devicemaps_init:這是為中斷向量建立記憶體對映
iotable_init:這是為SOC硬體暫存器建立記憶體對映
就以上三個呼叫需求。
函式create_mapping是最終建立記憶體對映的函式,先不管哪些需求去呼叫這個函式,先看這個函式本身:
這個函式只需要一個引數struct map_desc *md,這個結構體的定義在檔案arch/arm/include/asm/mach/map.h中,只有4個引數非常簡單:
unsigned long virtual; /*虛擬地址起始*/
unsigned long pfn; /*實體地址起始*/
unsigned long length; /*長度*/
unsigned int type; /*說明這個區間所屬的域,以及是否可讀、寫、可快取記憶體等屬性,arm硬體相關*/
第一個引數非常好理解,虛擬地址起始;第二個引數是實體地址起始;第三個引數是要對映的長度,最後一個比較複雜,但實際用到的往往只有MT_MEMORY(代表實體記憶體)、MT_HIGH_VECTORS/MT_LOW_VECTORS(代表中斷向量)、MT_DEVICE(代表硬體IO暫存器),實際含義和arm硬體相關,指的是MMU不同頁表的對映許可權暫可先不關心。
那麼給定這四個引數,create_mapping函式是怎麼建立對映呢?現在必須描述一下arm-linux的分頁機制:
32位的arm晶片的定址能力就是2^32 = 4G,地址範圍即0-0xffffffff,如果按照1M大小為單位進行對映,則4G = 4096 * 1M也就是需要4096個條目,每個條目負責1M大小的地址範圍;比如需要對映一個大小為128M的實體記憶體,那麼就需要填寫128個條目即可;事實上這就是所謂的段式對映或所謂一級對映,使用1MB的粗頁表,優點是佔用條目較少僅4096個條目,每個條目4位元組即每個程序(包括核心程序自己)僅佔用16K空間,但缺點是粒度太大了不利於linux記憶體管理,比如某程序或某核心程式碼(如模組)申請一些較小的空間不足1M,卻也得分配這麼大空間,當申請頻繁時,實體記憶體消耗將很快。這個缺點是必須要克服的,arm-linux肯定要引入二級頁表,但首先要理解這種段式對映或稱為一級對映的頁表是怎麼樣的,理解了一級對映才能理解二級對映,如下:
現在正式看核心程序的頁表建立,先分析道理,再對應原始碼(後面有足量的程式碼註釋),在mm/init-mm.c檔案中,有全域性變數init_mm,如下:
struct mm_struct init_mm = {
.mm_rb = RB_ROOT,
.pgd = swapper_pg_dir,
.mm_users = ATOMIC_INIT(2),
.mm_count = ATOMIC_INIT(1),
.mmap_sem = __RWSEM_INITIALIZER(init_mm.mmap_sem),
.page_table_lock = __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),
.mmlist = LIST_HEAD_INIT(init_mm.mmlist),
.cpu_vm_mask = CPU_MASK_ALL,
};
每個程序都有描述自己記憶體使用情況的結構mm_struct,核心程序也不例外,從這個檔案的目錄可見這個變數是不以平臺區分的,現在重點看第二個成員pgd,這是該程序記憶體頁表的虛擬地址,值為swapper_pg_dir,這是個在head.S中定義的變數,值設定為KERNEL_RAM_VADDR - 0x4000 = 0xc0004000,由前面已知段式記憶體頁表的大小為16K,所以核心的記憶體頁表的虛擬地址範圍是[0xc0004000: 0xc0008000]。
核心的記憶體頁表,在原始碼中paging_init函式都映射了什麼?由前面已知,映射了三方面內容,分別是:實體記憶體、中斷向量、硬體IO暫存器,先看實體記憶體的情況,這裡marvell裝置實體記憶體為256M,PHYS_OFFSET為0,
函式呼叫順序是:bootmem_init-> bootmem_init_node-> map_memory_bank,關注函式map_memory_bank,把實體記憶體的引數填充到struct map_desc結構體變數map,並用它呼叫函式create_mapping,正式開始:
1、首先一個判斷(md->virtual != vectors_base() && md->virtual < TASK_SIZE),這是為了防止虛擬地址不是中斷表地址並且在使用者區(0~3G)的情況;然後又是一個判斷((md->type == MT_DEVICE || md->type == MT_ROM) && md->virtual >= PAGE_OFFSET && md->virtual < VMALLOC_END),這是為了防止記憶體型別為IO型或ROM但虛擬地址為低端記憶體申請區(3G~3G + 768MB)的情況;這些判斷暫無需關注;
2、type = &mem_types[md->type];由前面可知這是獲取所對映記憶體區間所屬的域,以及是否可讀、寫、可快取記憶體等屬性,暫無需關注;
3、判斷(md->pfn >= 0x100000)的情況,這個就不要關注了(超過4G的情況);
4、判斷(type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK))的情況,意思是本區間不為段式對映(type->prot_l1 == 0)但該區間可以按1M對齊((addr | phys | length) & ~SECTION_MASK),記住這是一個原則,能按1M對齊的對映空間就按段式對映,不足1M的空間才需要二級對映;
5、pgd = pgd_offset_k(addr);,一級一級的看這個函式的實現,最後這個函式相當於(init_mm)->pgd + ((addr) >> 21) = (pgd_t *)0xc0004000 + (addr >> 21),這是在找這個虛擬地址在核心程序的頁表中的位置,由前面已知核心程序的頁表從虛擬地址0xc0004000開始,到0xc0008000結束,頁表大小為0x4000,這個範圍標識0-0xffffffff這4G的範圍,現在addr的值為0xc0000000,那麼它在核心程序的頁表中的位置可以算出來是0xc0004000 + 0x4000*3/4 = 0xc0007000,為什麼是3/4?因為0xc0000000在0-0xffffffff這4G的範圍是整好3/4的位置即4G中的最後一個G,addr值為0xc0000000,它右移21位值為0x600,(pgd_t *)0xc0004000 + 0x600 = 0xc0007000,貌似很奇怪為什麼不是0xc0004600?這是因為前面有強制型別轉換(pgd_t *),而這個結構的定義是兩個ulong,所以(pgd_t *)0xc0004000 + 0x600 =(pgd_t *)0xc0004000 + 0x600*8 = 0xc0007000,至於為什麼addr要右移21位,以及為什麼有強制型別轉換(pgd_t *),一會再說先關注段式對映的情況;
6、end = addr + length;,end指的是要對映的虛擬地址的結尾,它的值為0xc0000000 + 0x10000000(256M) = 0xd0000000;
7、至此,要對映的空間的虛擬地址起始值addr為0xc0000000,虛擬地址結尾值end為0xd0000000,長度為0x10000000,實體地址起始值為0,對映型別為變數type的值(暫不關心細節),接下來的do while迴圈是真正映射了:
8、do {
unsigned long next = pgd_addr_end(addr, end);
alloc_init_section(pgd, addr, next, phys, type);
phys += next - addr;
addr = next;
} while (pgd++, addr != end);
第一行的意思是,只要不超過end,就獲得下一個2MB的虛擬起始地址,所以傳給alloc_init_section的next引數,要麼與addr相差一整段(2MB),要麼是end則不足一整段(2MB),我們這裡的記憶體256M,不存在end與addr相差不足2M的情況;
進入函式alloc_init_section,它的引數分別是“一級頁表(段頁表)地址pgd、虛擬起始地址addr、虛擬結尾地址end(現在就是addr + 2MB)、物理起始地址phys、記憶體型別type”;
9、pmd_t *pmd = pmd_offset(pgd, addr);,這個pmd_t結構就只是一個ulong大小了,這裡函式pmd_offset的實現就是pmd = (pmd_t *)pgd,地址不變,但型別轉變,意思很明顯;
10、判斷(((addr | end | phys) & ~SECTION_MASK) == 0),我們這裡的都是2M對齊的,必然1M也對齊,底下的(addr & SECTION_SIZE)對於我們這裡不會成立,我們這裡都是2M對齊,即addr值的第21位一直都會是偶數;
11、下面的內容是配置段式頁表的值和寫頁表:
do {
*pmd = __pmd(phys | type->prot_sect);
phys += SECTION_SIZE;
} while (pmd++, addr += SECTION_SIZE, addr != end);
flush_pmd_entry(p);
第一行,頁表的這個條目pmd,寫入的值是什麼,可見,把實體地址和type的對映方式(prot_sect)寫進去了;
第二行,累加1M的實體地址值;
第三行,只要虛擬地址起始值addr再累加1M,沒有超過end(這裡是addr+2M),那麼寫下一個頁表條目(pmd+1)的值,很明顯,我們這裡會再迴圈一次,即do裡的內容總共執行過兩次;
第四行,最終寫入MMU,這個p指向pmd。
先不要管上面為什麼這麼實現,先看結果,總共256M的實體記憶體,最後結果是:
頁表條目索引 |
條目所在地址 |
頁表填的內容 |
對應的虛擬地址 |
0xfff |
0xc0008000 |
0xffffffff |
|
…… |
…… |
…… |
…… |
0x3fc |
0xc00073fc |
0x10000000+type |
0xd0000000 |
…… |
…… |
…… |
…… |
0xc05 |
0xc0007014 |
0x00500000+type |
0xc0500000 |
0xc04 |
0xc0007010 |
0x00400000+type |
0xc0400000 |
0xc03 |
0xc000700c |
0x00300000+type |
0xc0300000 |
0xc02 |
0xc0007008 |
0x00200000+type |
0xc0200000 |
0xc01 |
0xc0007004 |
0x00100000+type |
0xc0100000 |
0xc00 |
0xc0007000 |
0x00000000+type |
0xc0000000 |
…… |
…… |
…… |
…… |
0x000 |
0xc0004000 |
0x00000000 |
上面就是對實體記憶體對映後的情況(紅色部分),第一列是頁表的索引,256M的實體記憶體的的對映部分在第0xc00到0x3fc部分,對應頁表本身所在地址從0xc0007000到0xc00073fc,這部分頁表填充的內容是實體記憶體和對映型別的或運算結果,它們實際上對應虛擬地址的0xc0000000到0xd0000000。
如果把一個虛擬地址0xc1234567給CPU去訪問,那麼CPU把它發給MMU,MMU會根據已經建立的對映關係發現這個地址對應的是0x012這個段,然後把後面低20位的部分0x34567和0x012拼接起來,結果是0x01234567。
事實上這裡還有很多細節,比如MMU到底是怎麼能夠識別是0x012段的細節,這牽扯到arm硬體體系結構內容,如果不是特殊需要可不特別關心,關注到核心這步即可。
上面是實體記憶體頁表建立的結果,但還有個問題沒有說,就是arm頁表在linux中的融合問題,這部分不理解將影響對全域性的理解:
要知道,linux要實現高效的記憶體管理,是不可能按1M的區間管理的,這樣很容易產生問題,前面說過這個問題,事實上linux是以4K為一頁作為管理單位即粒度,這是怎麼定的呢?在arch/arm/include/asm/page.h檔案中規定巨集PAGE_SHIFT為12導致巨集PAGE_SIZE為4096即4K;
既然是4K為一頁,那麼按理說linux需要描述4G虛擬記憶體的話,需要多少個這樣的4K呢?很簡單:2^20 * 2^12 = 4G,所以是2^20為1M,即頁表的條目個數為1M個,每個條目佔用4位元組,即頁表大小為4M,每個程序包括核心程序都需要一個記憶體頁表,這就大量消耗實體記憶體在頁表上;
所以這裡將引入多級頁表的概念,linux核心定義的標準是這樣的:最高階pgd為頁目錄表,它找到每個程序mm-_struct結構的pgd成員,用它定位到下一級pmd;第二級pmd為中間頁表,它定位到下一級pte;第三級pte是頁表,它就能定位到哪個頁了;最後虛擬地址的最後12位定位的是該頁的偏移量;
為什麼搞的這麼麻煩?因為linux還要適配64位處理器,到那時是真的需要這麼麻煩,因為否則頁表佔用空間太大了,所以核心必須搞的級數多一些。
那麼arm呢,arm體系結構的MMU實際上支援兩級頁表,一級是剛才描述的段式對映即一級對映,再就是支援第二級對映,包括1K、4K、64K的頁實際上使用的是4K頁,這裡就牽扯到arm頁表機制和linux頁表機制融合的問題;這裡記住,arm的第一級頁表條目數為4096個,對於4K頁第二級目錄條目個數為256個,一級二級條目都是每個條目4位元組;
像這種物理的級數支援少的,砍掉中間目錄pmd就可以,從本質看就是函式pmd_offset(pgd, addr)的實現是pgd,即可什麼pmd在arm-linux形同虛設。ARM在linux下二級分頁如下:
虛擬地址——> PGD轉換——> PTE轉換——>實體地址
此外linux的記憶體管理中,有對頁的置屬性為“access”、“dirty”的需求,可是arm的MMU沒有提供這種屬性可以設定;
綜合各種原因,最終arm-linux假裝第一級目錄只有2048個條目,但其實每個條目是2個ulong大小即8位元組,所以最終設定MMU的還是4096個條目,只是每訪問1個pgd條目將可以訪問到2個pte條目,linux為了實現其記憶體管理功能又在後面加上2個對應的假pte表,這個假pte表專門給linux核心程式碼自己用的,不會影響arm硬體(事實上還有一個重要原因是,linux要求pte表長度為4K即一頁),在arch/arm/include/asm/pgtable.h中:
* pgd pte
* | |
* +--------+ +0
* | |-----> +------------+ +0
* +- - - - + +4 | h/w pt 0 |
* | |-----> +------------+ +1024
* +--------+ +8 | h/w pt 1 |
* | | +------------+ +2048
* +- - - - + | Linux pt 0 |
* | | +------------+ +3072
* +--------+ | Linux pt 1 |
* | | +------------+ +4096
這個東西可以看看英文註釋,比較容易懂。
看懂這個就很容易弄懂所有的建立記憶體對映的內容了,像為什麼PTRS_PER_PTE定義為512、為什麼PTRS_PER_PGD是2048、為什麼PGDIR_SHIFT是21等等,下面是完全原始碼註釋:
void __init paging_init(struct machine_desc *mdesc)
{
void *zero_page;
/*根據不同的arm版本,設定不同mem_type*/
build_mem_type_table();
/*判斷各個bank是否有highmem即高階記憶體,根據是否存在高階記憶體置位或復位各個bank的highmem,
同時檢查是否存在某bank的記憶體與vmalloc區域重合現象,如存在則需要去除(通過削減該bank的size的方式)*/
sanity_check_meminfo();
/*清除臨時的記憶體對映(不包括vmalloc_end以上的高階記憶體區域)*/
prepare_page_table();
/*為主(物理)記憶體建立對映;建立bootmem分配器;初始化重要全域性變數*/
bootmem_init();
/*為裝置I/O記憶體建立對映,附帶首先要清除vmalloc_end以上的高階記憶體區域的臨時頁表表項*/
devicemaps_init(mdesc);
/*對於marvell,沒有高階記憶體,無需關心,
對於高階記憶體對映初始化,也是從bootmem獲取頁表表項佔用的空間,
大小為1頁(4K),並在表項中填入表項地址值和屬性內容即可*/
kmap_init();
/*給全域性變數top_pmd賦值,值為0xffff0000在一級頁表中的表項位置,
地址0xffff0000到4G之間為copy_user_page/clear_user_page等函式使用*/
top_pmd = pmd_off_k(0xffff0000);
/*
* allocate the zero page. Note that this always succeeds and
* returns a zeroed result.
*/
/*由bootmem分配器獲取一頁實體記憶體,轉換為虛擬地址後賦給zero_page,
把對應的物理頁地址賦值給empty_zero_page並重新整理該頁*/
zero_page = alloc_bootmem_low_pages(PAGE_SIZE);
empty_zero_page = virt_to_page(zero_page);
flush_dcache_page(empty_zero_page);
}
***************************************************************************
/*清除彙編階段建立的臨時頁表的全部的段對映設定(即16K一級頁表全部寫0)*/
static inline void prepare_page_table(void)
{
unsigned long addr;
/*
* Clear out all the mappings below the kernel image.
*/
/*MODULES_VADDR(模組區)值為(PAGE_OFFSET - 16*1024*1024)即核心下16MB,其實就是核心以下的對映,
前兩個for迴圈將清除核心空間以下的對映
pmd_clear是將()中的頁表項地址中的Cache無效,以及清除段開始的兩個位元組
pmd_off_k為獲取addr在一級頁表中的位置
要注意pmd_clear的實現,注意傳進來的地址是什麼,為什麼操作其後兩個成員*/
for (addr = 0; addr < MODULES_VADDR; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
#ifdef CONFIG_XIP_KERNEL
/* The XIP kernel is mapped in the module area -- skip over it */
addr = ((unsigned long)_etext + PGDIR_SIZE - 1) & PGDIR_MASK;
#endif
/*再完成模組區MODULES_VADDR到核心起始的這16MB的清除*/
for ( ; addr < PAGE_OFFSET; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
/*
* Clear out all the kernel space mappings, except for the first
* memory bank, up to the end of the vmalloc region.
*/
/*從實體記憶體對應的虛擬起始地址開始,直到VMALLOC區結尾,清除*/
for (addr = __phys_to_virt(bank_phys_end(&meminfo.bank[0]));
addr < VMALLOC_END; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
}
***************************************************************************
/*為主(物理)記憶體建立對映;建立bootmem分配器;初始化重要全域性變數*/
void __init bootmem_init(void)
{
/*對於marvell,此時meminfo已初始化,start = 0,size = 128MB,nr_banks = 1,其他應該是均為0*/
struct meminfo *mi = &meminfo;
unsigned long min, max_low, max_high;
int node, initrd_node;
sort(&mi->bank, mi->nr_banks, sizeof(mi->bank[0]), meminfo_cmp, NULL);
/*
* Locate which node contains the ramdisk image, if any.
*/
/*絕大多數情況下,無用*/
initrd_node = check_initrd(mi);
max_low = max_high = 0;
/*
* Run through each node initialising the bootmem allocator.
*/
/*檢視for_each_node的實現,也就理解了底下的程式碼是什麼意思,如果有多於一個的記憶體node,
如何判斷記憶體node個數?就是巨集NODES_SHIFT(其引申巨集MAX_NUMNODES,單node時這兩個巨集值均為1),
在單node情況下,就是個for(node = 0; node == 0;node = 1){底下程式碼}的結果,即底下程式碼只執行一次*/
for_each_node(node) {
unsigned long node_low, node_high;
/*由該bank引數,給min、node_low、node_high引數賦值start、end、end,正常情況下:
min: start,物理的頁地址起始,0
node_low: end,物理的頁地址結尾,0x8000
node_high: end,,物理的頁地址結尾,0x8000*/
find_node_limits(node, mi, &min, &node_low, &node_high);
/*max_low = max_high = node_low = node_high = 0x8000*/
if (node_low > max_low)
max_low = node_low;
if (node_high > max_high)
max_high = node_high;
/*
* If there is no memory in this node, ignore it.
* (We can't have nodes which have no lowmem)
*/
/*這個node裡面實際沒有實體記憶體,返回*/
if (node_low == 0)
continue;
/*為主(物理)記憶體的指定節點建立對映,
node = 0,mi是meminfo的地址,min = 0,node_low = 0x8000*/
/*為主(物理)記憶體的指定節點node建立對映,start_pfn是物理起始地址,end_pfn是物理結尾地址
這時mi即meminfo的node成員等於0,
linux的記憶體(正式)頁表是在核心程式碼執行到start_kernel函式後執行paging
_init函式建立的,這裡要注意一個事情就是說,這裡paging_init函式可以正常建立記憶體頁表的條件有兩個:
1、
meminfo已
本文對arm linux頁表建立函式進行說明。在http://blog.csdn.net/flaoter/article/details/73381695中對MMU使能之前的臨時頁表進行了說明,此文是對kernel中正式頁表建立過程進行說明。文中使用的kerne
核心頁表實現
新版本的Linux核心程式碼中支援4級對映,那麼一個虛擬地址是包含有如下幾個部分:
PGD:Page Global Directory,L0級別頁表
PUD:Page Upper Directory,L1級別頁表
PMD : Page Middle Direc
1,虛擬記憶體
電腦中所執行的程式均需經過記憶體執行,若執行的程式佔用的記憶體很大很多,則會導致記憶體消耗殆盡,
為解決該問題,WINDOWS運用了虛擬記憶體技術,即拿出一部分硬碟空間來充當記憶體使用,這部分空間即稱為虛擬記憶體。
每個程序執行
每個程序執行的時候
在使用的計算機記憶體(4G)上面使用者可以使用的記憶體只有0~3G,3~4G是系統核心使用的區域。
但是在實體記憶體上面,任何的區域和位置都是可讀可寫操作的,假如程序直接訪問的是實體記憶體,那麼系統就會存在很大的不安全性。
頁表
頁表的存在很好的協調了
linux的頁表結構是為了節省地址轉換所需要的空間。分為PGD/PUD/PMD/PTE,P代表page,G代表global,D代表目錄(Director),U代表上級,M代表中間,T代表Table,E代表Entry。PTE是頁表項。他們之間的關係是層級結構,通過PGD訪問到 前言
在一個擁有32位的地址空間,4KB的頁面(212),並且每個PTE為4個位元組,那麼頁表大小為4MB(4 * 232 / 212),但若為64位地址空間,4KB的頁面(212)且每個PTE為4位元組,那麼頁表大小為16TB(4 * 264 / 212),由於頁表常駐記憶體,佔用記憶體
很久沒有寫部落格了,由於之前的寫關於OMAP3530文章還沒有整理。再加上一直在找工作,找到工作後又投入到另外的平臺去工作。始終在忙忙碌碌,但是對於程式碼確實漸漸疏遠。
在做專案的時候要使用DDR3分配記憶體,不經意間使用要和MMU以及TLB打交道。因此特地寫下這
Linux的定址機制的正常執行機制的正常執行,有賴於在啟動過程中足部建立的核心頁和頁表。本節講述核心頁表的建立過程。
bootloader載入核心映象
作業系統啟動前時啟動bootloader,並用bootloader載入和解壓核心映象到記憶體中。真實模式下bootloader(示例中是grub)不能訪問1
系統DDR的基地址為0x0,記憶體為1GB,所以TTB的基地址為0x4000。下面要建立虛擬地址0xfe700000到實體地址0xffff0000之間的對映,對映大小為64KB,即16頁。由於實體地址不是1MB位元組對齊,所以必須建立兩級對映。
使用者空間/核心空間
硬體平臺:S5PV210 核心版本:Linux2.6.32 檔案:head.S(linux/arch/arm/kernel/)
#include <**********>
#define KERNEL_RAM_VADDR (PAGE_OFF
arm32 linux3.18 mach-vexpress
常用的ioremap或者of_iomap都是動態對映,靜態對映的介面是iotable_init
void __init iotable_init(struct map_desc *io_desc, int nr)
struct
頁表的建立
Linux在啟動過程中,要首先進行記憶體的初始化,那麼就一定要首先建立頁表。我們知道每個程序都擁有各自的程序空間,而每個程序空間又分為核心空間和使用者空間。 以arm32為例,每個程序有4G的虛擬空間,其中0-3G屬於使用者地址空間,3G-4G屬於核心地址空間,核心地址空 我們在用偵錯程式看Linux使用者程序程式碼時,發現了一件很有意思的事情,在一段記憶體空間中,有一整頁(4K)都是data abort,如下:第一頁4011c000資料正常... ...4011cfec [0xe28dd014] add r13,r13,#0x144011cff0 [0xe8bd
arm32 linux3.18 mach-vexpress
常用的ioremap或者of_iomap都是動態對映,靜態對映的介面是iotable_init
void __init iotable_init(struct map_desc *io_desc, int nr)
早就想搞一下記憶體問題了!這次正趁著搞bigmemory核心,可以寫一篇文章了。本文旨在記錄,不包含細節,細節的話,google,百度均可,很多人已經寫了不少了。我只是按照自己的理解記錄一下記憶體的點點滴滴而已,沒有一家之言,不討論,不較真。
1.最簡單的記憶體使用
最簡單的模型是馮.諾依曼提出的原始模型, 1 分頁機制
在虛擬記憶體中,頁表是個對映表的概念, 即從程序能理解的線性地址(linear address)對映到儲存器上的實體地址(phisical address).
很顯然,這個頁表是需要常駐記憶體的東西, 以應對頻繁的查詢對映需要(實際上,現代支援VM的處理器都有一個叫TLB的硬體級頁表快取部件 為什麼需要swap
根 據Redhat公司的建議,Linux系統swap分割槽最適合的大小是實體記憶體的1-2倍.不過Linux上有些軟體對swap分割槽得需求較大,例如要順 利執行Oracle資料庫軟體,swap分割槽至少需要400MB或者實體記憶體2倍以上的空間.當然我們在安裝Linux的時候,就能直接 1 頁式管理
1.1 分段機制存在的問題
分段,是指將程式所需要的記憶體空間大小的虛擬空間,通過對映機制對映到某個實體地址空間(對映的操作由硬體完成)。分段對映機制解決了之前作業系統存在的兩個問題:
地址空間沒有隔離
程式執行的地址不確定
不過分段方法存在一個嚴重的問題:記憶體的使用效率
http://bbs.elecfans.com/jishu_1603506_1_1.html demo板上預設的系統記憶體是512M,我修改了一下boot啟動引數為256M後,發現記憶體啟動過程中掛掉了 而且發現dts檔案中的
memory [
device_type 相關推薦
arm-linux記憶體頁表建立
arm linux 頁表建立
深入淺出記憶體管理-Linux核心頁表
虛擬記憶體 && 頁表 && pcb
【linux】頁表的簡單理解!!!
linux多級頁表結構
讀懂作業系統之虛擬記憶體頁表(五)
ARM-Linux (臨時,正式) 建立頁表的比較
記憶體知識梳理2. Linux 頁表的建立過程-x86
arm的2級頁表在Linux核心建立過程解析
ARM Linux 核心啟動總結 之 建立臨時頁表
linux iotable_init 靜態對映與核心頁表的建立
深入淺出記憶體管理--頁表的建立
Linux使用者程序記憶體分配及二級頁表PTE的二三事
linux iotable_init 靜態對映與核心頁表的建立
linux記憶體管理解析----linux物理,線性記憶體佈局及頁表的初始化
Linux分頁機制之概述--Linux記憶體管理(六)
linux記憶體不足,swap交換分割槽建立
Linux分頁機制之分頁機制的演變--Linux記憶體管理(七)
[問答] 在arm-linux上如何修改系統記憶體的大小?