Linux内核笔记--内存管理之用户态进程内存分配
內(nèi)核版本:linux-2.6.11
Linux在加載一個(gè)可執(zhí)行程序的時(shí)候做了種種復(fù)雜的工作,內(nèi)存分配是其中非常重要的一環(huán),作為一個(gè)linux程序員必然會(huì)想要知道這個(gè)過程到底是怎么樣的,內(nèi)核源碼會(huì)告訴你這一切。
線性區(qū)
一個(gè)可執(zhí)行程序,是經(jīng)過編譯器處理后的遵守一定規(guī)則的數(shù)據(jù)、符號(hào)表和指令序列的組合,當(dāng)linux加載一個(gè)可執(zhí)行程序的時(shí)候,會(huì)為其創(chuàng)建一個(gè)新的進(jìn)程,其對(duì)應(yīng)的進(jìn)程描述符task_struct中會(huì)保存許多資源的描述符,其中的mm_struct就是這個(gè)進(jìn)程的內(nèi)存描述符,用來管理該進(jìn)程擁有的所有內(nèi)存。
一個(gè)進(jìn)程擁有的內(nèi)存是動(dòng)態(tài)變化的,比如棧的擴(kuò)充、堆的擴(kuò)充、新的文件映射等等,出于這個(gè)原因,需要一個(gè)更細(xì)粒度的單位來實(shí)現(xiàn)內(nèi)存的動(dòng)態(tài)增加減少,這個(gè)單位叫線性地址區(qū)間,簡(jiǎn)稱線性區(qū),用vm_area_struct描述。
線性地址空間
線性地址空間是基于單個(gè)進(jìn)程的,暫時(shí)拋開寫時(shí)復(fù)制機(jī)制不談,不同進(jìn)程之間的線性地址空間是彼此隔離的,這是由linux的多級(jí)分頁機(jī)制實(shí)現(xiàn)。
一個(gè)進(jìn)程擁有的線性地址空間的具體表示就是這個(gè)進(jìn)程的內(nèi)存描述符中存儲(chǔ)的線性區(qū)的集合。
死程序,活進(jìn)程
現(xiàn)在,我們知道了,進(jìn)程是通過增加和減少線性區(qū)來管理自己擁有的內(nèi)存,并通過邏輯地址加上某一個(gè)線性區(qū)的基地址來進(jìn)行尋址操作,那么ok,這兩點(diǎn)已經(jīng)能夠保證一個(gè)四肢頭腦健全的進(jìn)程正常運(yùn)行,然而,一個(gè)可執(zhí)行程序是存在硬盤上的,是一個(gè)死的東西,linux加載器需要把它變成活的,需要給她四肢給她頭腦,即把她的代碼、數(shù)據(jù)、棧、依賴庫全部放到內(nèi)存中。
這個(gè)過程,從do_fork開始。
do_fork和寫時(shí)復(fù)制
Linux用do_fork來創(chuàng)建一個(gè)新的用戶態(tài)進(jìn)程,寫時(shí)復(fù)制機(jī)制讓新的子進(jìn)程在不進(jìn)行寫操作的前提下會(huì)擁有父進(jìn)程的所有頁框,相當(dāng)于父子進(jìn)程擁有相同的線性區(qū),當(dāng)子進(jìn)程對(duì)線性區(qū)寫操作或者執(zhí)行exec的時(shí)候,系統(tǒng)會(huì)將子進(jìn)程的mm_struct重新初始化,
簡(jiǎn)單說下寫時(shí)復(fù)制機(jī)制的實(shí)現(xiàn):主要函數(shù)調(diào)用流程do_fork-->copy_process-->copy_mm-->dup_mmap-->copy_page_range,copy_page_range將父進(jìn)程的多級(jí)頁表結(jié)構(gòu)整個(gè)復(fù)制一遍,此時(shí),父子進(jìn)程擁有彼此分離的多級(jí)頁表結(jié)構(gòu),但在最后一級(jí)頁表中存放的相同的頁描述符,即子進(jìn)程在進(jìn)行寫操作之前依然跟父進(jìn)程共享相同的頁,當(dāng)子進(jìn)程對(duì)某個(gè)共享頁進(jìn)行寫操作時(shí),系統(tǒng)會(huì)將執(zhí)行流定向到do_wp_page,這個(gè)函數(shù)將復(fù)制一個(gè)新的頁來替換要寫的頁。因此一個(gè)新的進(jìn)程在初始的時(shí)候跟父進(jìn)程共享相同的地址空間,但經(jīng)過一段時(shí)間后,父子進(jìn)程的地址空間將變得真正隔離開來。
分配線性區(qū)
然而運(yùn)行一個(gè)新的程序會(huì)干掉所有舊的內(nèi)存空間,并為新進(jìn)程重新分配新的線性地址空間,從sys_execve()即exec的系統(tǒng)調(diào)用例程開始,調(diào)用流程依次是sys_execve-->do_execve-->mm_alloc-->mm_init-->mm_alloc_pgd-->pgd_alloc。最后這個(gè)pgd_alloc為這個(gè)進(jìn)程分配了一個(gè)新的頁全局目錄(第一級(jí)頁表)。
此時(shí),該進(jìn)程的線性地址空間依然為空,因?yàn)檫€未曾為其分配任何線性區(qū)。
sys_execve()會(huì)在最后會(huì)調(diào)用這個(gè)可執(zhí)行程序?qū)?yīng)格式的load_binary函數(shù),這個(gè)函數(shù)完成了這種格式的可執(zhí)行程序的加載,其中最主要的過程就是多次調(diào)用do_mmap為該進(jìn)程分配一系列的線性區(qū)并存放不同的內(nèi)容,分配順序是,棧段->代碼段->數(shù)據(jù)段->bss->依賴庫,堆是在運(yùn)行過程中動(dòng)態(tài)分配的,由內(nèi)核中brk和mmap函數(shù)實(shí)現(xiàn),C庫將其封裝成我們熟知的malloc函數(shù)。
線性區(qū)的分配簡(jiǎn)單說就是掃描用戶態(tài)線性地址空間(32位系統(tǒng)下通常是從0x40000000開始的低3G的空間),查找一個(gè)足夠大的線性地址范圍。
經(jīng)過以上的過程,新進(jìn)程擁有了自己的線性地址空間,但是別忘了,系統(tǒng)從未給這個(gè)進(jìn)程分配任何可用的物理頁,
僅僅只初始化了一個(gè)頁全局目錄,那么,當(dāng)進(jìn)程尋址的時(shí)候,MMU如何正確進(jìn)行地址轉(zhuǎn)換呢。
分配頁框(填充頁表)
Linux順理成章的將新進(jìn)程物理頁的分配放在了缺頁異常處理程序中,進(jìn)程運(yùn)行前期會(huì)頻繁通過缺頁異常來請(qǐng)求分頁,缺頁異常處理程序最終會(huì)調(diào)用伙伴系統(tǒng)的一個(gè)入口alloc_pages來分配新的頁框并為缺頁的線性地址填充頁表,一段時(shí)間后,該進(jìn)程的運(yùn)行環(huán)境就會(huì)被完全載入內(nèi)存。
至此,死程序變活進(jìn)程。
插一段:sys_execve()第一步是調(diào)用getname()函數(shù),獲得程序名網(wǎng)上和一些書上說這個(gè)函數(shù)是用來得到一個(gè)新的頁框并從用戶空間拷貝程序名到這個(gè)頁框中,然而,2.6的源碼最終指向的一個(gè)函數(shù)是kmem_cache_alloc(cachep, flags),這個(gè)函數(shù)我在Linux內(nèi)核筆記——內(nèi)存管理之slab分配器里提到過,這是slab分配器的調(diào)用入口,所以從這里可以知道,getname其實(shí)是通過指定一個(gè)叫names_cachep的高速緩存描述符來分配一個(gè)這個(gè)類型的內(nèi)存對(duì)象,這個(gè)names_cachep則是一個(gè)kmem_cache_t類型的指針,是一種高速緩存類型,所以這里說獲得一個(gè)新的頁框是欠妥的,實(shí)際上getname是獲得了一個(gè)names_cachep這種高速緩存里注冊(cè)的構(gòu)造函數(shù)對(duì)應(yīng)的一個(gè)指定的可用內(nèi)存對(duì)象,然后再存入程序名到這個(gè)內(nèi)存對(duì)象中。雖然這個(gè)對(duì)象可能就是一個(gè)普通頁框,這依賴于這個(gè)注冊(cè)的構(gòu)造函數(shù),
詳細(xì)解釋見Linux內(nèi)核筆記——內(nèi)存管理之slab分配器。
PS: 個(gè)人理解,錯(cuò)誤難免,望能指出,萬分感謝
轉(zhuǎn)載于:https://www.cnblogs.com/JaSonS-toy/p/4998936.html
總結(jié)
以上是生活随笔為你收集整理的Linux内核笔记--内存管理之用户态进程内存分配的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BFS HDOJ 2102 A计划
- 下一篇: codeforces 600D Are