下面對arm64 linux核心記憶體初始化專欄相關内容進行一次彙總,該總結中列出的部分函數前面還未詳細講解,後續會持續更新(arm64架構,sparse記憶體模型,linux 4.9):
stext//彙編代碼運作階段
|--->__create_page_tables:該函數主要完成linux記憶體中兩塊記憶體區域的頁表建立和出事化:
| | (1):把idmap_text區域的實體位址映射到相等的虛拟位址上,并建立初始化相關頁表.
| | 這種映射完成後,其虛拟位址等于實體位址。idmap_text區域都是一些打開MMU
| | 相關的代碼(一緻性映射).
| | (2)将kernel運作需要的記憶體區域(kernel txt、rodata、data、bss等等)進行映
| | 射,并建立初始化相關頁表(swapper程序頁表).
|--->__primary_switch
| |--->__enable_mmu:使能mmu(在idmap_text區域),linux進入記憶體虛拟位址通路的世界(以後cpu隻能通過cpu訪
| | 問記憶體空間)
start_kernel()//進入c代碼運作階段
|--->page_address_init()
| |
|--->setup_arch()
| |--->early_fixmap_init():給靜态定義的fixmap虛拟空間建立中間level的頁表entry.但是沒有映射對應的實體
| | 位址,也就是說映射隻做一部分,沒有為其pte頁表項指派.由于目前linux内
| | 核記憶體處于初始化階段MMU已經打開,cpu隻能通過虛拟位址通路實體空間内容,但目
| | 前linux核心記憶體頁表映射機制還未完全建立,記憶體管理的夥伴系統還沒有ready,整
| | 個記憶體空間由memblock子產品管理,該記憶體管理系統不能動态為實體位址關聯虛拟地
| | 址.是以在執行完early_fixmap_init函數後,linux核心可以可以從fixmap虛拟地
| | 址空間配置設定一段虛拟地,并與memblock配置設定的實體位址空間建立映射.這樣cpu就可
| | 以通過fixmap機制臨時配置設定的虛拟位址通路membloc配置設定的實體空間.
| |--->early_ioremap_init():該函數完成early_ioremap機制初始化工作.一般傳統驅動子產品都是使用ioremap函
| | 數來完成位址映射的,但是該函數必須依賴夥伴系統來建立某個level的
| | Translation table(若該轉換表不存在)。于是在核心啟動初期需要通過
| | early_ioremap機制讓裝置寄存器對記憶體進行通路。一些硬體需要在記憶體管理系統
| | 運作起來之前就需要工作,核心采early_ioremap機制來映射記憶體給這些硬體驅動
| | 使用。并且這些硬體驅動在使用完early_ioremap的位址後需要盡快的釋放掉這些
| | 記憶體,這樣才能保證其他硬體子產品繼續使用。是以early_ioremap采用的是Fixed
| | map的temporary fixmap段虛拟位址(是以在early_fixmap_init函數完成後執行
| | 該函數)。
| |--->setup_machine_fdt():該函數完成以下幾個工作:1.fixmap機制為dtb實體記憶體提供了固定的虛拟位址FDT,
| | 函數先将dtb實體位址對應的pfn填入到FDT虛拟位址對應的pte entry中去(頁表内 | | 核靜态配置設定,中間層頁表entry在early_fixmap_init函數中被初始化),這樣cpu | | 能通過FDT虛拟位址通路到dtb檔案内容.2.掃描dtb檔案,擷取linux os實體記憶體的 | | 所有布局資訊,并将dtb描述的記憶體區域添加到memblock子產品中,後續該子產品具有物 | | 理記憶體配置設定能力(傳回的都是記憶體空間的實體位址需要結合fixmap機制提供的虛拟
| | 位址才能被cpu通路)
| |--->parse_early_param():解析核心啟動時傳入的參數,有些參數用于描述核心記憶體布局資訊:主要關注類似
| | mem=XXX[KkmM],highmem=XXX[kKmM] 或 memmap=XXX[KkmM]" "@XXX[KkmM] 之類
| | 的參數。
| |--->arm64_memblock_init():當實體記憶體都添加進系統之後,linux通過該函數對實體記憶體進行一些整理,主要
| | 是将一些特殊的區域添加到memblock的reversed記憶體管理子產品中去,比如:a.将
| | 核心啟動參數中傳入的預留記憶體劃分到memblock管理系統的reversed區域(設定為
| | reversed類型), b.将核心代碼段設定位reserved類型 c.将dtb中的reserved-
| | memory區域設定位reserved類型, d.移除在dts檔案中具有no-map屬性的
| | reserved-memory記憶體區域,f.申請公共區域CMA(通dma_contiguous_reserve)
| | 等
| |--->paging_init():在paging_init函數執行前,cpu隻能通路Kernel Image和DTB的兩段實體記憶體區域(該區域
| | 建立了相應的頁表機制).其他記憶體區域雖然已經通過掃描dtb檔案被memblock_add函數添加
| | 到核心系統,但是實體記憶體到虛拟位址的頁表映射還未建立,是以對于大部分記憶體區域,内
| | 核隻能通過memblock子產品配置設定實體記憶體,但不能通路(雖然能用fixmap機制為少數的實體内
| | 存臨時配置設定虛拟位址,但這隻能覆寫很少一部分記憶體區域,并不能對整個實體記憶體進行虛拟
| | 位址映射,涉及到大塊記憶體配置設定時,fixmap機制顯得捉襟見肘).執行pageing_init函數就
| | 是為整個記憶體區域建立頁表,實作實體位址到虛拟位址的映射:
| | (1)利用early_pgtable_alloc函數通過memblock和fixmap機制配置設定一頁實體記憶體并關聯
| | 虛拟位址,用于存放臨時核心記憶體pgd頁表.
| | (2)map_kernel函數先将核心image的各個段進行虛實映射(代碼段,隻讀資料段,init段
| | 和資料段),建立并填充頁表.該段映射虛拟位址位于Vmalloc區域。接着map_kernel
| | 函數還會将以前靜态定義的fixmap段的pud頁表項直接複制到新建立的pud頁表中,在新
| | 核心頁表中儲存fixmap段實體位址的虛實映射關系(fixmap段與kernel image段共用
| | 一個pgd頁表項,但在pud頁表中屬于不同的pud頁表項,是以将以前靜态定義的fixmap
| | pud頁表項拷貝到新pud頁表對應位置複用即可).
| | (3)利用map_mem函數将memblock添加的所有實體記憶體進行虛實映射,建立并填充頁表。該
| | 段虛拟位址位于linux核心虛拟位址的線性映射區域,虛拟位址以PAGE_OFFSET為開端
| | (注意:a.映射過程中會剔除memblock系統中标有nomap标志的實體記憶體區域,b.線性映
| | 射區會再次對kernel image對應的段實體記憶體建立虛實映射關系,是以在核心态
| | kernel image段對應的實體位址會有兩個虛拟位址一個線上性映射區一個在vmalloc
| | 區).
| | (4)替換頁表,用新建立的pgd頁表内容去替換掉swappper_pg_dir頁表中的内容,後續
| | swappper_pg_dir實體位址會存放在ttbr1寄存器中.接着會釋放掉新建立的pgd頁表和
| | swappper_pg_dir以前對應的pud,pmd,pte頁表(就在swappper_pg_dir頁表下方).
| | 到此linux核心整個記憶體的頁表機制初始化完成,以後cpu能通過對于虛拟位址通路到所有
| | 線上的記憶體區域(cpu獲得虛拟位址,從ttbr1寄存器擷取核心頁表位址,通過mmu擷取虛拟
| | 位址對應的實體位址并通路對應的記憶體空間)
| |--->bootmem_init():paging_init函數完成了核心記憶體頁表的建立,實作了核心記憶體分頁機制的初始化.接着調用
| | bootmem_init函數對與linux核心記憶體相關的一些資料結構進行初始化,包括:記憶體節
| | 點(struct pglist_data),記憶體section(struct mem_section),記憶體域zone(struct
| | zone)和頁描述符(strcut page),主要針對SPARSE記憶體模
| | 型).
| | (1).調用arm64_memory_present函數周遊meiblock中的每個region,将每個region
| | 劃分為1G大小的section,并為初始化每個section對應資料結構struct
| | mem_section的section_mem_map成員(主要設定section_mem_map成員的
| | present位和nid位).
| | (2).調用sparse_init變量記憶體區域完成如下兩個任務:
| | a).給每個section配置設定一段實體記憶體用于存儲section中每個pageblock的
| | 位圖資料.位圖資料存儲區對應的虛拟位址儲存在struct mem_section
| | 的pageblock_bitmap成員中
| | b).為每個section記憶體區域中的每個實體頁配置設定實體記憶體空間,用于存儲其
| | 所有頁的結構體描述符structpage,并将linux核心vmmemmap虛拟位址區
| | 域中對應虛拟位址與剛配置設定的實體記憶體空間的實體位址建立頁表映射關
| | 系,section首頁對應的struct page的虛拟位址存儲在
| | struct section的section_mem_map成員中(由此可以看出struct
| | mem_section的section_mem_map存儲着該section區域的多個資料:該
| | section是否線上,section的編号,section所在的節點和section首
| | 頁結構體描述符的虛拟位址等)。
| | (3).調用zone_sizes_init函數周遊記憶體區域:
| | a.對記憶體區域中所有節點對應的結構體描述符struct pglist_data的相關成員
| | 進行初始化
| | b.對每個記憶體節點中所有zone區域對應的結構體描述符struct zone中相關成員
| | 進行初始化.
| | c.對每個zone區域中所有實體頁對應的結構體描述符struct page中相關成員進
| | 行初始化
|--->setup_per_cpu_areas():setup_per_cpu_areas函數完成每cpu高速緩存初始化
| |
|--->build_all_zonelists():build_all_zonlists函數主要是為node建立一個記憶體配置設定時的優先級的次序。将系統中各
| | 個節點的各個zone,按照備選節點的優先級順序依次填寫到對應節點結構體描述符的
| | strcut zonelist node_zonelists[]數組中.某node的zonlist可以按下面的優先級進行
| | 指派:
| | (1).對于不同節點,本地node記憶體放在zonelist的最前面,其它node的記憶體根據其與
| | 本節點的distance的值從小到大依次排列。
| | (2).對于node内部不同的zone也存在優先級關系,normal zone排在dma zone的前面。
|--->page_alloc_init()
| |
|--->pidhash_init()
| |
|--->vfs_caches_init_early()
| |
|--->mm_init()
| |--->mem_init():夥伴系統初始化
| |--->kmem_cache_init():slab系統初始化
| |--->vmalloc_init():vmalloc初始化