1. 程式人生 > >arm-linux記憶體頁表建立

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前面有對這四個成員的初始化,它們規定了核心映象的程式碼段起始、程式碼段結尾、資料段起始、資料段結尾(資料段結尾也是整個核心映象的結尾),給這四個成員賦的地址值都是在vmlinux.lds.S連結指令碼中規定的(即虛擬地址),界定它們的意義在於能夠正確的界定核心映象的執行需要在虛擬地址佔用的空間位置及大小,以利於其他內容在核心空間位置的確定。

Paging_init函式首先呼叫的是build_mem_type_table,這個函式做的事情就是給靜態全域性變數mem_types賦值,這個變數就在本檔案(arch/arm/mm/mmu.c)

定義,它的用處就是在create_mapping函式建立對映時配置MMU硬體時需要;build_mem_type_table函式裡面是完全與本arm晶片自身體系結構相關的配置,我還沒完全搞明白。。。後續再補充吧。

接下來呼叫的是sanity_check_meminfo,這個函式主要做兩件事情,首先是確定本裝置實體記憶體的各個node各個bank中到底有沒有高階記憶體,根據是否存在高階記憶體決定每個bankhighmem成員值;然後是對於每個bank的正確性進行檢測;下面分別描述:

由下面程式碼判斷每個實體記憶體bank是否屬於高階記憶體:

if (__va(bank->start) > VMALLOC_MIN || __va(bank->start) < (void *)PAGE_OFFSET)

         highmem = 1;

即:該bank的實體記憶體起始虛擬地址大於VMALLOC_MIN,或者小於PAGE_OFFSETPAGE_OFFSET是核心使用者空間的交界,在這裡定義為0xc0000000也就是arm-linux普遍適用值3G/1GVMALLOC_MIN就定義在本檔案(arch/arm/mm/mmu.c),如下:

#define VMALLOC_MIN  (void *)(VMALLOC_END - vmalloc_reserve)

VMALLOC_ENDarch/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、  起始地址不小於核心使用者交界的虛擬地址;

當屬於高階記憶體時,該bankhighmem成員將置1

除了界定實體記憶體bank是否屬於高階記憶體,sanity_check_meminfo函式還對每個實體記憶體bank的正確性進行檢測,這部分個人認為不是重點,主要注意下在存在高階記憶體情況下(程式碼中定義巨集CONFIG_HIGHMEM情況下),若低端記憶體太大(起始位置在VMALLOC_MIN之前,結尾位置超過VMALLOC_MIN),則超過VMALLOC_MIN的部分將被算進另一個bank並且判定為高階記憶體。

接下來呼叫的是prepare_page_table,它的作用是清除在核心程式碼執行到start_kernel之前時建立的大部分臨時記憶體頁表,這裡需要對arm-linux記憶體頁表的機制原理進行理解:

首先一個是,什麼是記憶體頁表,都有哪些屬於記憶體(注意這裡的記憶體是廣義上的記憶體,不單單是實體記憶體)

具體的說,記憶體頁表,更應該叫記憶體對映,對於有MMUCPU來說,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裝置實體記憶體為256MPHYS_OFFSET0

函式呼叫順序是: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)的情況;這些判斷暫無需關注;

2type = &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的空間才需要二級對映

5pgd = pgd_offset_k(addr);,一級一級的看這個函式的實現,最後這個函式相當於(init_mm)->pgd + ((addr) >> 21) = (pgd_t *)0xc0004000 + (addr >> 21),這是在找這個虛擬地址在核心程序的頁表中的位置,由前面已知核心程序的頁表從虛擬地址0xc0004000開始,到0xc0008000結束,頁表大小為0x4000,這個範圍標識0-0xffffffff4G的範圍,現在addr的值為0xc0000000,那麼它在核心程序的頁表中的位置可以算出來是0xc0004000 + 0x4000*3/4 = 0xc0007000,為什麼是3/4?因為0xc00000000-0xffffffff4G的範圍是整好3/4的位置即4G中的最後一個Gaddr值為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 *),一會再說先關注段式對映的情況;

6end = addr + length;end指的是要對映的虛擬地址的結尾,它的值為0xc0000000 + 0x10000000(256M) = 0xd0000000

7、至此,要對映的空間的虛擬地址起始值addr0xc0000000,虛擬地址結尾值end0xd0000000,長度為0x10000000,實體地址起始值為0,對映型別為變數type的值(暫不關心細節),接下來的do while迴圈是真正映射了:

8do {

        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_sectionnext引數,要麼與addr相差一整段(2MB),要麼是end則不足一整段(2MB),我們這裡的記憶體256M,不存在endaddr相差不足2M的情況;

進入函式alloc_init_section,它的引數分別是“一級頁表(段頁表)地址pgd、虛擬起始地址addr、虛擬結尾地址end(現在就是addr + 2MB)、物理起始地址phys、記憶體型別type”;

9pmd_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的實體記憶體的的對映部分在第0xc000x3fc部分,對應頁表本身所在地址從0xc00070000xc00073fc,這部分頁表填充的內容是實體記憶體和對映型別的或運算結果,它們實際上對應虛擬地址的0xc00000000xd0000000

如果把一個虛擬地址0xc1234567CPU去訪問,那麼CPU把它發給MMUMMU會根據已經建立的對映關係發現這個地址對應的是0x012這個段,然後把後面低20位的部分0x345670x012拼接起來,結果是0x01234567

事實上這裡還有很多細節,比如MMU到底是怎麼能夠識別是0x012段的細節,這牽扯到arm硬體體系結構內容,如果不是特殊需要可不特別關心,關注到核心這步即可。

上面是實體記憶體頁表建立的結果,但還有個問題沒有說,就是arm頁表在linux中的融合問題,這部分不理解將影響對全域性的理解:

要知道,linux要實現高效的記憶體管理,是不可能按1M的區間管理的,這樣很容易產生問題,前面說過這個問題,事實上linux是以4K為一頁作為管理單位即粒度,這是怎麼定的呢?在arch/arm/include/asm/page.h檔案中規定巨集PAGE_SHIFT12導致巨集PAGE_SIZE40964K

既然是4K為一頁,那麼按理說linux需要描述4G虛擬記憶體的話,需要多少個這樣的4K呢?很簡單:2^20 * 2^12 = 4G,所以是2^201M,即頁表的條目個數為1M個,每個條目佔用4位元組,即頁表大小為4M,每個程序包括核心程序都需要一個記憶體頁表,這就大量消耗實體記憶體在頁表上;

所以這裡將引入多級頁表的概念,linux核心定義的標準是這樣的:最高階pgd為頁目錄表,它找到每個程序mm-_struct結構的pgd成員,用它定位到下一級pmd;第二級pmd為中間頁表,它定位到下一級pte;第三級pte是頁表,它就能定位到哪個頁了;最後虛擬地址的最後12位定位的是該頁的偏移量;

為什麼搞的這麼麻煩?因為linux還要適配64位處理器,到那時是真的需要這麼麻煩,因為否則頁表佔用空間太大了,所以核心必須搞的級數多一些。

那麼arm呢,arm體系結構的MMU實際上支援兩級頁表,一級是剛才描述的段式對映即一級對映,再就是支援第二級對映,包括1K4K64K的頁實際上使用的是4K頁,這裡就牽扯到arm頁表機制和linux頁表機制融合的問題;這裡記住,arm的第一級頁表條目數為4096個,對於4K頁第二級目錄條目個數為256個,一級二級條目都是每個條目4位元組;

像這種物理的級數支援少的,砍掉中間目錄pmd就可以,從本質看就是函式pmd_offset(pgd, addr)的實現是pgd,即可什麼pmdarm-linux形同虛設。ARMlinux下二級分頁如下:

虛擬地址——> PGD轉換——> PTE轉換——>實體地址

此外linux的記憶體管理中,有對頁的置屬性為“access”、“dirty”的需求,可是armMMU沒有提供這種屬性可以設定;

綜合各種原因,最終arm-linux假裝第一級目錄只有2048個條目,但其實每個條目是2ulong大小即8位元組,所以最終設定MMU的還是4096個條目,只是每訪問1pgd條目將可以訪問到2pte條目,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_PGD2048、為什麼PGDIR_SHIFT21等等,下面是完全原始碼註釋:

void __init paging_init(struct machine_desc *mdesc)

{

         void *zero_page;

    /*根據不同的arm版本,設定不同mem_type*/

         build_mem_type_table();

    /*判斷各個bank是否有highmem即高階記憶體,根據是否存在高階記憶體置位或復位各個bankhighmem

同時檢查是否存在某bank的記憶體與vmalloc區域重合現象,如存在則需要去除(通過削減該banksize的方式)*/

         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在一級頁表中的表項位置,

地址0xffff00004G之間為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 = 0size = 128MBnr_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 == 0node = 1){底下程式碼}的結果,即底下程式碼只執行一次*/

         for_each_node(node) {

                   unsigned long node_low, node_high;

        /*由該bank引數,給minnode_lownode_high引數賦值startendend,正常情況下:

          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 = 0mimeminfo的地址,min = 0node_low = 0x8000*/

        /*為主(物理)記憶體的指定節點node建立對映,start_pfn是物理起始地址,end_pfn是物理結尾地址

這時mimeminfonode成員等於0

相關推薦

arm-linux記憶體建立

linux的記憶體(正式)頁表是在核心程式碼執行到start_kernel函式後執行paging _init函式建立的,這裡要注意一個事情就是說,這裡paging_init函式可以正常建立記憶體頁表的條件有兩個: 1、              meminfo已

arm linux 建立

本文對arm linux頁表建立函式進行說明。在http://blog.csdn.net/flaoter/article/details/73381695中對MMU使能之前的臨時頁表進行了說明,此文是對kernel中正式頁表建立過程進行說明。文中使用的kerne

深入淺出記憶體管理-Linux核心

核心頁表實現 新版本的Linux核心程式碼中支援4級對映,那麼一個虛擬地址是包含有如下幾個部分: PGD:Page Global Directory,L0級別頁表 PUD:Page Upper Directory,L1級別頁表 PMD : Page Middle Direc

虛擬記憶體 && && pcb

1,虛擬記憶體 電腦中所執行的程式均需經過記憶體執行,若執行的程式佔用的記憶體很大很多,則會導致記憶體消耗殆盡, 為解決該問題,WINDOWS運用了虛擬記憶體技術,即拿出一部分硬碟空間來充當記憶體使用,這部分空間即稱為虛擬記憶體。 每個程序執行 每個程序執行的時候

linux的簡單理解!!!

在使用的計算機記憶體(4G)上面使用者可以使用的記憶體只有0~3G,3~4G是系統核心使用的區域。 但是在實體記憶體上面,任何的區域和位置都是可讀可寫操作的,假如程序直接訪問的是實體記憶體,那麼系統就會存在很大的不安全性。 頁表 頁表的存在很好的協調了

linux多級結構

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),由於頁表常駐記憶體,佔用記憶體

ARM-Linux (臨時,正式) 建立的比較

 很久沒有寫部落格了,由於之前的寫關於OMAP3530文章還沒有整理。再加上一直在找工作,找到工作後又投入到另外的平臺去工作。始終在忙忙碌碌,但是對於程式碼確實漸漸疏遠。 在做專案的時候要使用DDR3分配記憶體,不經意間使用要和MMU以及TLB打交道。因此特地寫下這

記憶體知識梳理2. Linux 建立過程-x86

Linux的定址機制的正常執行機制的正常執行,有賴於在啟動過程中足部建立的核心頁和頁表。本節講述核心頁表的建立過程。 bootloader載入核心映象 作業系統啟動前時啟動bootloader,並用bootloader載入和解壓核心映象到記憶體中。真實模式下bootloader(示例中是grub)不能訪問1

arm的2級Linux核心建立過程解析

系統DDR的基地址為0x0,記憶體為1GB,所以TTB的基地址為0x4000。下面要建立虛擬地址0xfe700000到實體地址0xffff0000之間的對映,對映大小為64KB,即16頁。由於實體地址不是1MB位元組對齊,所以必須建立兩級對映。   使用者空間/核心空間

ARM Linux 核心啟動總結 之 建立臨時

硬體平臺:S5PV210   核心版本:Linux2.6.32   檔案:head.S(linux/arch/arm/kernel/) #include <**********> #define KERNEL_RAM_VADDR    (PAGE_OFF

linux iotable_init 靜態對映與核心建立

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使用者程序記憶體分配及二級PTE的二三事

我們在用偵錯程式看Linux使用者程序程式碼時,發現了一件很有意思的事情,在一段記憶體空間中,有一整頁(4K)都是data abort,如下:第一頁4011c000資料正常... ...4011cfec [0xe28dd014]   add      r13,r13,#0x144011cff0 [0xe8bd

linux iotable_init 靜態對映與核心建立

arm32 linux3.18 mach-vexpress 常用的ioremap或者of_iomap都是動態對映,靜態對映的介面是iotable_init void __init iotable_init(struct map_desc *io_desc, int nr)

linux記憶體管理解析----linux物理,線性記憶體佈局及的初始化

早就想搞一下記憶體問題了!這次正趁著搞bigmemory核心,可以寫一篇文章了。本文旨在記錄,不包含細節,細節的話,google,百度均可,很多人已經寫了不少了。我只是按照自己的理解記錄一下記憶體的點點滴滴而已,沒有一家之言,不討論,不較真。 1.最簡單的記憶體使用 最簡單的模型是馮.諾依曼提出的原始模型,

Linux機制之概述--Linux記憶體管理(六)

1 分頁機制 在虛擬記憶體中,頁表是個對映表的概念, 即從程序能理解的線性地址(linear address)對映到儲存器上的實體地址(phisical address). 很顯然,這個頁表是需要常駐記憶體的東西, 以應對頻繁的查詢對映需要(實際上,現代支援VM的處理器都有一個叫TLB的硬體級頁表快取部件

linux記憶體不足,swap交換分割槽建立

為什麼需要swap 根 據Redhat公司的建議,Linux系統swap分割槽最適合的大小是實體記憶體的1-2倍.不過Linux上有些軟體對swap分割槽得需求較大,例如要順 利執行Oracle資料庫軟體,swap分割槽至少需要400MB或者實體記憶體2倍以上的空間.當然我們在安裝Linux的時候,就能直接

Linux機制之分機制的演變--Linux記憶體管理(七)

1 頁式管理 1.1 分段機制存在的問題 分段,是指將程式所需要的記憶體空間大小的虛擬空間,通過對映機制對映到某個實體地址空間(對映的操作由硬體完成)。分段對映機制解決了之前作業系統存在的兩個問題: 地址空間沒有隔離 程式執行的地址不確定 不過分段方法存在一個嚴重的問題:記憶體的使用效率

[問答] 在arm-linux上如何修改系統記憶體的大小?

http://bbs.elecfans.com/jishu_1603506_1_1.html demo板上預設的系統記憶體是512M,我修改了一下boot啟動引數為256M後,發現記憶體啟動過程中掛掉了 而且發現dts檔案中的 memory [ device_type