在多任務操作系統(tǒng)中,每個進程都運行在屬于自己的內(nèi)存沙盤中,這個沙盤就是虛擬地址空間(Virtual Address Space)废士,在32位模式下它是一個4GB的內(nèi)存地址塊。在Linux系統(tǒng)中, 內(nèi)核進程和用戶進程所占的虛擬內(nèi)存比例是1:3蝇完,而Windows系統(tǒng)為2:2(通過設置Large-Address-Aware Executables標志也可為1:3)官硝。這并不意味著內(nèi)核使用那么多物理內(nèi)存矗蕊,僅表示它可支配這部分地址空間,根據(jù)需要將其映射到物理內(nèi)存氢架。
虛擬地址通過頁表(Page Table)映射到物理內(nèi)存傻咖,頁表由操作系統(tǒng)維護并被處理器引用。內(nèi)核空間在頁表中擁有較高特權級岖研,因此用戶態(tài)程序試圖訪問這些頁時會導致一個頁錯誤(page fault)卿操。在Linux中,內(nèi)核空間是持續(xù)存在的孙援,并且在所有進程中都映射到同樣的物理內(nèi)存硬纤。內(nèi)核代碼和數(shù)據(jù)總是可尋址,隨時準備處理中斷和系統(tǒng)調用赃磨。與此相反筝家,用戶模式地址空間的映射隨進程切換的發(fā)生而不斷變化。
Linux進程在虛擬內(nèi)存中的標準內(nèi)存段布局如下圖所示:(參考《程序員的自我修養(yǎng)》的第10章)
其中邻辉,用戶地址空間中的藍色條帶對應于映射到物理內(nèi)存的不同內(nèi)存段溪王,灰白區(qū)域表示未映射的部分。這些段只是簡單的內(nèi)存地址范圍值骇,與Intel處理器的段沒有關系莹菱。
上圖中Random stack offset和Random mmap offset等隨機值意在防止惡意程序。Linux通過對棧吱瘩、內(nèi)存映射段道伟、堆的起始地址加上隨機偏移量來打亂布局,以免惡意程序通過計算訪問棧使碾、庫函數(shù)等地址蜜徽。execve(2)負責為進程代碼段和數(shù)據(jù)段建立映射,真正將代碼段和數(shù)據(jù)段的內(nèi)容讀入內(nèi)存是由系統(tǒng)的缺頁異常處理程序按需完成的票摇。另外拘鞋,execve(2)還會將BSS段清零。
用戶進程部分分段存儲內(nèi)容如下表所示(按地址遞減順序):
名稱 | 存儲內(nèi)容 |
---|---|
棧(Stack) | 局部變量矢门、函數(shù)參數(shù)盆色、返回地址等 |
堆(Heap) | 動態(tài)分配的內(nèi)存 |
BSS段(BSS Segment) | 未初始化或初值為0的全局變量和靜態(tài)局部變量 |
數(shù)據(jù)段(Data Segment) | 已初始化且初值非0的全局變量和靜態(tài)局部變量 |
代碼段(Text Segment) | 可執(zhí)行代碼、字符串字面值祟剔、只讀變量 |
在將應用程序加載到內(nèi)存空間執(zhí)行時隔躲,操作系統(tǒng)負責代碼段、數(shù)據(jù)段和BSS段的加載物延,并在內(nèi)存中為這些段分配空間宣旱。棧也由操作系統(tǒng)分配和管理;堆由程序員自己管理教届,即顯式地申請和釋放空間响鹃。
BSS段、數(shù)據(jù)段和代碼段是可執(zhí)行程序編譯時的分段案训,運行時還需要棧和堆买置。
一、內(nèi)核空間(Kernel Space)
內(nèi)核總是駐留在內(nèi)存中强霎,是操作系統(tǒng)的一部分忿项。內(nèi)核空間為內(nèi)核保留,不允許應用程序讀寫該區(qū)域的內(nèi)容或直接調用內(nèi)核代碼定義的函數(shù)城舞。
二轩触、棧(Stack)
由編譯器自動分配釋放,行為類似數(shù)據(jù)結構中的棧(先進后出)家夺,主要有三個用途:
- 函數(shù)的返回地址(以便從被調用者返回)和參數(shù)
- 臨時變量:包括函數(shù)的非靜態(tài)局部變量以及編譯器自動生成的其他臨時變量
- 保存上下文:包括在函數(shù)調用前后需要保持不變的寄存器
入棧順序為:參數(shù)(C語言參數(shù)的由右向左入棧倒序脱柱,pascal 語言正序)、返回地址拉馋、Old EBP榨为、保存的寄存器、局部變量煌茴、其他數(shù)據(jù)
ARM CPU不成立随闺,ARM體系缺省前兩個參數(shù)通過 r0,r1蔓腐,兩個寄存器傳矩乐,從第三個開始才用棧傳。有同學說回论,Intel也未必散罕,沒錯,有些編譯器支持用寄存器傳參數(shù)傀蓉,函數(shù)需用特殊關鍵字聲明笨使。靠僚害,我估計好多人聲明和定義也分不清楚硫椰。
還有返回值?NO萨蚕,它是由寄存器返回的靶草。返回結果存入 intel是eax,arm是r0 寄存器岳遥。
為了清晰的表示奕翔,來一張分布圖:
持續(xù)地重用棧空間浩蓉,有助于使活躍的棧內(nèi)存保持在CPU緩存中派继,從而加速訪問宾袜。進程中的每個線程都有屬于自己的棧。向棧中不斷壓入數(shù)據(jù)時驾窟,若超出其容量就會耗盡棧對應的內(nèi)存區(qū)域庆猫,從而觸發(fā)一個頁錯誤。此時若棧的大小低于棧最大值RLIMIT_STACK(通常是8M)绅络,則棧會動態(tài)增長月培,程序繼續(xù)運行。映射的棧區(qū)擴展到所需大小后恩急,不再收縮杉畜。
Linux中ulimit -s命令可查看和設置棧最大值,當程序使用的棧超過該值時衷恭,發(fā)生棧溢出(Stack Overflow)此叠,程序收到一個段錯誤(Segmentation Fault)。注意随珠,調高棧容量可能會增加內(nèi)存開銷和啟動時間拌蜘。
棧既可向下增長(向內(nèi)存低地址)也可向上增長, 這依賴于具體的實現(xiàn)。本文所述棧向下增長牙丽。
棧的大小在運行時由內(nèi)核動態(tài)調整简卧。
關于C語言函數(shù)調用過程中使用棧的具體細節(jié)可以參考:《C語言函數(shù)調用棧》
三烤芦、內(nèi)存映射段(Memory Mapping Segment)
此處举娩,內(nèi)核將硬盤文件的內(nèi)容直接映射到內(nèi)存, 任何應用程序都可通過Linux的mmap()系統(tǒng)調用或Windows的CreateFileMapping()/MapViewOfFile()請求這種映射。內(nèi)存映射是一種方便高效的文件I/O方式构罗, 因而被用于裝載動態(tài)共享庫铜涉。用戶也可創(chuàng)建匿名內(nèi)存映射,該映射沒有對應的文件, 可用于存放程序數(shù)據(jù)遂唧。在 Linux中芙代,若通過malloc()請求一大塊內(nèi)存,C運行庫將創(chuàng)建一個匿名內(nèi)存映射盖彭,而不使用堆內(nèi)存纹烹。“大塊” 意味著比閾值 MMAP_THRESHOLD還大召边,缺省為128KB铺呵,可通過mallopt()調整。
該區(qū)域用于映射可執(zhí)行文件用到的動態(tài)鏈接庫隧熙。在Linux 2.4版本中片挂,若可執(zhí)行文件依賴共享庫,則系統(tǒng)會為這些動態(tài)庫在從0x40000000開始的地址分配相應空間,并在程序裝載時將其載入到該空間音念。在Linux 2.6內(nèi)核中沪饺,共享庫的起始地址被往上移動至更靠近棧區(qū)的位置。
從進程地址空間的布局可以看到闷愤,在有共享庫的情況下整葡,留給堆的可用空間還有兩處:一處是從.bss段到0x40000000,約不到1GB的空間肝谭;另一處是從共享庫到棧之間的空間掘宪,約不到2GB蛾扇。這兩塊空間大小取決于棧攘烛、共享庫的大小和數(shù)量。這樣來看镀首,是否應用程序可申請的最大堆空間只有2GB坟漱?事實上,這與Linux內(nèi)核版本有關更哄。在上面給出的進程地址空間經(jīng)典布局圖中芋齿,共享庫的裝載地址為0x40000000,這實際上是Linux kernel 2.6版本之前的情況了成翩,在2.6版本里觅捆,共享庫的裝載地址已經(jīng)被挪到靠近棧的位置,即位于0xBFxxxxxx附近麻敌,因此栅炒,此時的堆范圍就不會被共享庫分割成2個“碎片”,故kernel 2.6的32位Linux系統(tǒng)中术羔,malloc申請的最大內(nèi)存理論值在2.9GB左右(測試值也可以達到2.9G)赢赊。
四、堆(Heap)
堆用于存放進程運行時動態(tài)分配的內(nèi)存段级历,可動態(tài)擴張或縮減释移。堆中內(nèi)容是匿名的,不能按名字直接訪問寥殖,只能通過指針間接訪問玩讳。當進程調用malloc(C)/new(C++)等函數(shù)分配內(nèi)存時,新分配的內(nèi)存動態(tài)添加到堆上(擴張)嚼贡;當調用free(C)/delete(C++)等函數(shù)釋放內(nèi)存時锋边,被釋放的內(nèi)存從堆中剔除(縮減) 。
分配的堆內(nèi)存是經(jīng)過字節(jié)對齊的空間编曼,以適合原子操作豆巨。堆管理器通過鏈表管理每個申請的內(nèi)存,由于堆申請和釋放是無序的掐场,最終會產(chǎn)生內(nèi)存碎片往扔。堆內(nèi)存一般由應用程序分配釋放贩猎,回收的內(nèi)存可供重新使用。若程序員不釋放萍膛,程序結束時操作系統(tǒng)可能會自動回收吭服。
我們用到的是用戶堆,每個進程有一個蝗罗,進程中的每個線程都從這個堆申請內(nèi)存艇棕,這個堆在用戶空間。所謂內(nèi)存耗光串塑,一般就是這個用戶堆申請不到內(nèi)存了沼琉,申請不到,分兩種情況桩匪,一種是你 malloc 的比剩余的總數(shù)還大打瘪,這個是肯定不會給你了。第二種是剩余的還有傻昙,但是都不連續(xù)闺骚,最大的一塊都沒有你 malloc 的大,也不會給你妆档。這種情況是怎么造成的呢僻爽,比如 p1 = malloc(10), p2 = malloc(3), p3 = malloc(30), free(p2)。很好理解吧贾惦,出現(xiàn)碎片了胸梆,如果繼續(xù) free(p1), p1 和 p2 會合并纤虽,free(p3) 就全合并到一起了乳绕。所以如果你的程序都是雞零狗碎的內(nèi)存,又是服務器逼纸,占的內(nèi)存又很大洋措,人品又不好,長的又不帥杰刽,時間久了就會出現(xiàn)這種情況菠发,解決辦法,直接申請一塊兒大內(nèi)存贺嫂,自己管理滓鸠。
另外講個常識性問題,除非特殊設計第喳,一般你申請的內(nèi)存首地址都是偶地址糜俗,也就是說你向堆申請一個字節(jié),堆也會給你至少4個字節(jié)或者8個字節(jié),只是因為這樣好管理并且快悠抹,舉個極端的例子珠月,比如ARM 的 cpu 根本不能訪問奇地址,硬件會報錯楔敌,它訪問奇地址的方法是分兩次從相鄰的偶地址把數(shù)據(jù)拿出來給你拼成一個奇地址的數(shù)據(jù)啤挎,有興趣看的,一下ARM編程手冊就明白了卵凑。
堆的末端由break指針標識庆聘,當堆管理器需要更多內(nèi)存時,可通過系統(tǒng)調用brk()和sbrk()來移動break指針以擴張堆勺卢,一般由系統(tǒng)自動調用伙判。
使用堆時經(jīng)常出現(xiàn)兩種問題:
- 釋放或改寫仍在使用的內(nèi)存(“內(nèi)存破壞”);
- 未釋放不再使用的內(nèi)存(“內(nèi)存泄漏”)值漫。當釋放次數(shù)少于申請次數(shù)時澳腹,可能已造成內(nèi)存泄漏织盼。泄漏的內(nèi)存往往比忘記釋放的數(shù)據(jù)結構更大杨何,因為所分配的內(nèi)存通常會圓整為下個大于申請數(shù)量的2的冪次(如申請212B,會取整為256B)沥邻。
注意危虱,堆不同于數(shù)據(jù)結構中的”堆”,其行為類似鏈表唐全。
【擴展閱讀】棧和堆的區(qū)別
- 管理方式:棧由編譯器自動管理埃跷;堆由程序員控制,使用方便邮利,但易產(chǎn)生內(nèi)存泄露弥雹。
- 生長方向:棧向低地址擴展(向下生長),是連續(xù)的內(nèi)存區(qū)域延届;堆向高地址擴展(向上生長)剪勿,是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)用鏈表來存儲空閑內(nèi)存地址方庭,自然不連續(xù)厕吉,而鏈表從低地址向高地址遍歷。
- 空間大行的睢:棧頂?shù)刂泛蜅5淖畲笕萘坑上到y(tǒng)預先規(guī)定(通常默認2M或10M)头朱;堆的大小則受限于計算機系統(tǒng)中有效的虛擬內(nèi)存,32位Linux系統(tǒng)中堆內(nèi)存可達2.9G空間龄减。
- 存儲內(nèi)容:棧在函數(shù)調用時项钮,首先壓入主調函數(shù)中下條指令(函數(shù)調用語句的下條可執(zhí)行語句)的地址,然后是函數(shù)實參,然后是被調函數(shù)的局部變量烁巫。本次調用結束后鳖敷,局部變量先出棧,然后是參數(shù)程拭,最后棧頂指針指向最開始存的指令地址定踱,程序由該點繼續(xù)運行下條可執(zhí)行語句;堆通常在頭部用一個字節(jié)存放其大小恃鞋,堆用于存儲生存期與函數(shù)調用無關的數(shù)據(jù)崖媚,具體內(nèi)容由程序員安排。
- 分配方式:椥衾耍可靜態(tài)分配或動態(tài)分配畅哑。靜態(tài)分配由編譯器完成,如局部變量的分配水由。動態(tài)分配由alloca函數(shù)在棧上申請空間荠呐,用完后自動釋放;堆只能動態(tài)分配且手工釋放砂客。
- 分配效率:棧由計算機底層提供支持泥张,分配專門的寄存器存放棧地址,壓棧鞠值、出棧由專門的指令執(zhí)行媚创,因此效率較高;堆由函數(shù)庫提供彤恶,機制復雜钞钙,效率比棧低得多。Windows系統(tǒng)中VirtualAlloc可直接在進程地址空間中分配一塊內(nèi)存声离,快速且靈活芒炼。
- 分配后系統(tǒng)響應:只要棧剩余空間大于所申請空間,系統(tǒng)將為程序提供內(nèi)存术徊,否則報告異常提示棧溢出本刽;操作系統(tǒng)為堆維護一個記錄空閑內(nèi)存地址的鏈表。當系統(tǒng)收到程序的內(nèi)存分配申請時弧关,會遍歷該鏈表尋找第一個空間大于所申請空間的堆結點盅安,然后將該結點從空閑結點鏈表中刪除,并將該結點空間分配給程序世囊。若無足夠大小的空間(可能由于內(nèi)存碎片太多)别瞭,有可能調用系統(tǒng)功能去增加程序數(shù)據(jù)段的內(nèi)存空間,以便有機會分到足夠大小的內(nèi)存株憾,然后進行返回蝙寨。大多數(shù)系統(tǒng)會在該內(nèi)存空間首地址處晒衩,記錄本次分配的內(nèi)存大小,供后續(xù)的釋放函數(shù)(如free/delete)正確釋放本內(nèi)存空間墙歪。此外听系,由于找到的堆結點大小不一定正好等于申請的大小,系統(tǒng)會自動將多余的部分重新放入空閑鏈表中虹菲。
- 碎片問題:棧不會存在碎片問題靠胜,因為棧是先進后出的隊列,內(nèi)存塊彈出棧之前毕源,在其上面的后進的棧內(nèi)容已彈出浪漠;而頻繁申請釋放操作會造成堆內(nèi)存空間的不連續(xù),從而造成大量碎片霎褐,使程序效率降低址愿。
可見,堆容易造成內(nèi)存碎片冻璃,由于沒有專門的系統(tǒng)支持响谓,效率很低。由于可能引發(fā)用戶態(tài)和內(nèi)核態(tài)切換省艳,內(nèi)存申請的代價更為昂貴娘纷,所以棧在程序中應用最廣泛,函數(shù)調用也利用棧來完成拍埠,調用過程中的參數(shù)失驶、返回地址土居、椩婀海基指針和局部變量等都采用棧的方式存放。所以擦耀,建議盡量使用棧棉圈,僅在分配大量或大塊內(nèi)存空間時使用堆 。
使用棧和堆時應避免越界發(fā)生眷蜓,否則可能程序崩潰或破壞程序堆分瘾、棧結構,產(chǎn)生意想不到的后果吁系。
五德召、BSS段(BSS Segment)
通常存放程序中以下符號:
- 未初始化的全局變量和靜態(tài)局部變量
- 初始值為0的全局變量和靜態(tài)局部變量(依賴于編譯器實現(xiàn))
- 未定義且初值不為0的符號(該初值即common block的大小)
C語言中汽纤,未顯式初始化的靜態(tài)分配變量被初始化為0(算術類型)或空指針(指針類型)上岗。由于程序加載時,BSS會被操作系統(tǒng)清零蕴坪,所以未賦初值或初值為0的全局變量都在BSS中肴掷。BSS段僅為未初始化的靜態(tài)分配變量預留位置敬锐,在目標文件中并不占據(jù)空間,這樣可減少目標文件體積呆瞻。但程序運行時需為變量分配內(nèi)存空間台夺,故目標文件必須記錄所有未初始化的靜態(tài)分配變量大小總和(通過start_bss和end_bss地址寫入機器代碼)。當加載器(loader)加載程序時痴脾,將為BSS段分配的內(nèi)存初始化為0颤介。在嵌入式軟件中,進入main()函數(shù)之前BSS段被C運行時系統(tǒng)映射到初始化為全零的內(nèi)存(效率較高)赞赖。
注意买窟,盡管均放置于BSS段,但初值為0的全局變量是強符號薯定,而未初始化的全局變量是弱符號始绍。若其他地方已定義同名的強符號(初值可能非0),則弱符號與之鏈接時不會引起重定義錯誤话侄,但運行時的初值可能并非期望值(會被強符號覆蓋)亏推。因此,定義全局變量時年堆,若只有本文件使用抵栈,則盡量使用static關鍵字修飾颈渊;否則需要為全局變量定義賦初值(哪怕0值),保證該變量為強符號,以便鏈接時發(fā)現(xiàn)變量名沖突手幢,而不是被未知值覆蓋。
某些編譯器將未初始化的全局變量保存在common段拱雏,鏈接時再將其放入BSS段贪惹。在編譯階段可通過-fno-common選項來禁止將未初始化的全局變量放入common段。
此外攻晒,由于目標文件不含BSS段顾复,故程序燒入存儲器(Flash)后BSS段地址空間內(nèi)容未知。U-Boot啟動過程中鲁捏,將U-Boot的Stage2代碼(通常位于lib_xxxx/board.c文件)搬遷(拷貝)到SDRAM空間后必須人為添加清零BSS段的代碼芯砸,而不可依賴于Stage2代碼中變量定義時賦0值。
【擴展閱讀】BSS歷史
BSS(Block Started by Symbol给梅,以符號開始的塊)一詞最初是UA-SAP匯編器(United Aircraft Symbolic Assembly Program)中的偽指令假丧,用于為符號預留一塊內(nèi)存空間。該匯編器由美國聯(lián)合航空公司于20世紀50年代中期為IBM 704大型機所開發(fā)动羽。
后來該詞被作為關鍵字引入到了IBM 709和7090/94機型上的標準匯編器FAP(Fortran Assembly Program)包帚,用于定義符號并且為該符號預留指定字數(shù)的未初始化空間塊。在采用段式內(nèi)存管理的架構中(如Intel 80x86系統(tǒng))曹质,BSS段通常指用來存放程序中未初始化全局變量的一塊內(nèi)存區(qū)域婴噩,該段變量只有名稱和大小卻沒有值擎场。程序開始時由系統(tǒng)初始化清零。
BSS段不包含數(shù)據(jù)几莽,僅維護開始和結束地址迅办,以便內(nèi)存能在運行時被有效地清零。BSS所需的運行時空間由目標文件記錄章蚣,但BSS并不占用目標文件內(nèi)的實際空間站欺,即BSS節(jié)段應用程序的二進制映象文件中并不存在。
六纤垂、數(shù)據(jù)段(Data Segment)
數(shù)據(jù)段通常用于存放程序中已初始化且初值不為0的全局變量和靜態(tài)局部變量矾策。數(shù)據(jù)段屬于靜態(tài)內(nèi)存分配(靜態(tài)存儲區(qū)),可讀可寫峭沦。
數(shù)據(jù)段保存在目標文件中(在嵌入式系統(tǒng)里一般固化在鏡像文件中)贾虽,其內(nèi)容由程序初始化。例如吼鱼,對于全局變量int gVar = 10蓬豁,必須在目標文件數(shù)據(jù)段中保存10這個數(shù)據(jù),然后在程序加載時復制到相應的內(nèi)存菇肃。
數(shù)據(jù)段與BSS段的區(qū)別如下:
- BSS段不占用物理文件尺寸地粪,但占用內(nèi)存空間;數(shù)據(jù)段占用物理文件琐谤,也占用內(nèi)存空間蟆技。
對于大型數(shù)組如int ar0[10000] = {1, 2, 3, ...}和int ar1[10000],ar1放在BSS段斗忌,只記錄共有10000*4個字節(jié)需要初始化為0质礼,而不是像ar0那樣記錄每個數(shù)據(jù)1、2飞蹂、3...几苍,此時BSS為目標文件所節(jié)省的磁盤空間相當可觀。具體的對比細節(jié)可以參考:《深入理解bss段與data段的區(qū)別》 - 當程序讀取數(shù)據(jù)段的數(shù)據(jù)時陈哑,系統(tǒng)會出發(fā)缺頁故障,從而分配相應的物理內(nèi)存伸眶;當程序讀取BSS段的數(shù)據(jù)時惊窖,內(nèi)核會將其轉到一個全零頁面,不會發(fā)生缺頁故障厘贼,也不會為其分配相應的物理內(nèi)存界酒。
七、代碼段(Text Segment)
代碼段也稱正文段或文本段嘴秸,通常用于存放程序執(zhí)行代碼(即CPU執(zhí)行的機器指令)毁欣。一般C語言執(zhí)行語句都編譯成機器代碼保存在代碼段庇谆。通常代碼段是可共享的,因此頻繁執(zhí)行的程序只需要在內(nèi)存中擁有一份拷貝即可凭疮。代碼段通常屬于只讀饭耳,以防止其他程序意外地修改其指令(對該段的寫操作將導致段錯誤)。某些架構也允許代碼段為可寫执解,即允許修改程序寞肖。
代碼段指令根據(jù)程序設計流程依次執(zhí)行,對于順序指令衰腌,只會執(zhí)行一次(每個進程)新蟆;若有反復,則需使用跳轉指令右蕊;若進行遞歸琼稻,則需要借助棧來實現(xiàn)。
代碼段指令中包括操作碼和操作對象(或對象地址引用)饶囚。若操作對象是立即數(shù)(具體數(shù)值)欣簇,將直接包含在代碼中;若是局部數(shù)據(jù)坯约,將在棧區(qū)分配空間熊咽,然后引用該數(shù)據(jù)地址;若位于BSS段和數(shù)據(jù)段闹丐,同樣引用該數(shù)據(jù)地址横殴。
八、保留區(qū)(Reserved)
位于虛擬地址空間的最低部分卿拴,未賦予物理地址衫仑。任何對它的引用都是非法的,用于捕捉使用空指針和小整型值指針引用內(nèi)存的異常情況堕花。
它并不是一個單一的內(nèi)存區(qū)域文狱,而是對地址空間中受到操作系統(tǒng)保護而禁止用戶進程訪問的地址區(qū)域的總稱。大多數(shù)操作系統(tǒng)中缘挽,極小的地址通常都是不允許訪問的瞄崇,如NULL。C語言將無效指針賦值為0也是出于這種考慮壕曼,因為0地址上正常情況下不會存放有效的可訪問數(shù)據(jù)苏研。
在32位X86架構的Linux系統(tǒng)中,用戶進程可執(zhí)行程序一般從虛擬地址空間0x08048000開始加載腮郊。該加載地址由ELF文件頭決定摹蘑,可通過自定義鏈接器腳本覆蓋鏈接器默認配置,進而修改加載地址轧飞。0x08048000以下的地址空間通常由C動態(tài)鏈接庫衅鹿、動態(tài)加載器ld.so和內(nèi)核VDSO(內(nèi)核提供的虛擬共享庫)等占用撒踪。通過使用mmap系統(tǒng)調用,可訪問0x08048000以下的地址空間大渤。
通過cat /proc/self/maps命令查看加載表如下:
【擴展閱讀】分段的好處
進程運行過程中制妄,代碼指令根據(jù)流程依次執(zhí)行,只需訪問一次(當然跳轉和遞歸可能使代碼執(zhí)行多次)兼犯;而數(shù)據(jù)(數(shù)據(jù)段和BSS段)通常需要訪問多次忍捡,因此單獨開辟空間以方便訪問和節(jié)約空間。具體解釋如下:當程序被裝載后切黔,數(shù)據(jù)和指令分別映射到兩個虛存區(qū)域砸脊。數(shù)據(jù)區(qū)對于進程而言可讀寫,而指令區(qū)對于進程只讀纬霞。兩區(qū)的權限可分別設置為可讀寫和只讀凌埂。以防止程序指令被有意或無意地改寫。
現(xiàn)代CPU具有極為強大的緩存(Cache)體系诗芜,程序必須盡量提高緩存命中率瞳抓。指令區(qū)和數(shù)據(jù)區(qū)的分離有利于提高程序的局部性。現(xiàn)代CPU一般數(shù)據(jù)緩存和指令緩存分離伏恐,故程序的指令和數(shù)據(jù)分開存放有利于提高CPU緩存命中率孩哑。
當系統(tǒng)中運行多個該程序的副本時,其指令相同翠桦,故內(nèi)存中只須保存一份該程序的指令部分横蜒。若系統(tǒng)中運行數(shù)百進程,通過共享指令將節(jié)省大量空間(尤其對于有動態(tài)鏈接的系統(tǒng))销凑。其他只讀數(shù)據(jù)如程序里的圖標丛晌、圖片、文本等資源也可共享斗幼。而每個副本進程的數(shù)據(jù)區(qū)域不同澎蛛,它們是進程私有的。
此外蜕窿,臨時數(shù)據(jù)及需要再次使用的代碼在運行時放入棧區(qū)中谋逻,生命周期短。全局數(shù)據(jù)和靜態(tài)數(shù)據(jù)可能在整個程序執(zhí)行過程中都需要訪問渠羞,因此單獨存儲管理斤贰。堆區(qū)由用戶自由分配,以便管理次询。
總結
我不生產(chǎn)知識,我只是大自然的搬運工瓷叫。以上所述的內(nèi)存均可以在《深入理解計算機系統(tǒng)》屯吊、《程序員的自我修養(yǎng)》這兩本書中查找到送巡。
很多知識都是來自兩位男神的博客:clover_toeic 、liutao_1977
作者:_Summer__
鏈接:https://blog.csdn.net/qq_21842557/article/details/50777199
來源:CSDN
著作權歸作者所有盒卸。商業(yè)轉載請聯(lián)系作者獲得授權骗爆,非商業(yè)轉載請注明出處。