1. gzyueqian
      13352868059

      剛開始學(xué)習(xí)嵌入式開發(fā)應(yīng)該怎么選擇嵌入式學(xué)習(xí)課程?

      更新時(shí)間: 2018-10-02 12:00:00來源: 嵌入式開發(fā)瀏覽量:3524

          內(nèi)存管理是操作系統(tǒng)的核心任務(wù);它對(duì)程序員和系統(tǒng)管理員來說也是至關(guān)重要的。在接下來的幾篇文章中,我將從實(shí)踐出發(fā)著眼于內(nèi)存管理,并深入到它的內(nèi)部結(jié)構(gòu)。雖然這些概念很通用,但示例大都來自于 32 位 x86 架構(gòu)的 Linux 和 Windows 上。這篇文章描述了在內(nèi)存中程序如何分布。
          在一個(gè)多任務(wù)操作系統(tǒng)中的每個(gè)進(jìn)程都運(yùn)行在它自己的內(nèi)存“沙箱”中。這個(gè)沙箱是一個(gè)虛擬地址空間(virtual address space),在 32 位的模式中它總共有 4GB 的內(nèi)存地址塊。這些虛擬地址是通過內(nèi)核頁表(page table)映射到物理地址的,并且這些虛擬地址是由操作系統(tǒng)內(nèi)核來維護(hù),進(jìn)而被進(jìn)程所消費(fèi)的。每個(gè)進(jìn)程都有它自己的一組頁表,但是這里有點(diǎn)玄機(jī)。一旦虛擬地址被啟用,這些虛擬地址將被應(yīng)用到這臺(tái)電腦上的 所有軟件,包括內(nèi)核本身。因此,一部分虛擬地址空間必須保留給內(nèi)核使用:



          但是,這并不是說內(nèi)核就使用了很多的物理內(nèi)存,恰恰相反,它只使用了很少一部分可用的地址空間映射到其所需要的物理內(nèi)存。內(nèi)核空間在內(nèi)核頁表中被標(biāo)記為獨(dú)占使用于 特權(quán)代碼 (ring 2 或更低),因此,如果一個(gè)用戶模式的程序嘗試去訪問它,將觸發(fā)一個(gè)頁面故障錯(cuò)誤。在 Linux 中,內(nèi)核空間是始終存在的,并且在所有進(jìn)程中都映射相同的物理內(nèi)存。內(nèi)核代碼和數(shù)據(jù)總是可尋址的,準(zhǔn)備隨時(shí)去處理中斷或者系統(tǒng)調(diào)用。相比之下,用戶模式中的地址空間,在每次進(jìn)程切換時(shí)都會(huì)發(fā)生變化:



          藍(lán)色的區(qū)域代表映射到物理地址的虛擬地址空間,白色的區(qū)域是尚未映射的部分。在上面的示例中,眾所周知的內(nèi)存“饕餮” Firefox 使用了大量的虛擬內(nèi)存空間。在地址空間中不同的條帶對(duì)應(yīng)了不同的內(nèi)存段,像堆(heap)、棧(stack)等等。請(qǐng)注意,這些段只是一系列內(nèi)存地址的簡(jiǎn)化表示,它與 Intel 類型的段 并沒有任何關(guān)系 。不過,這是一個(gè)在 Linux 進(jìn)程的標(biāo)準(zhǔn)段布局:


          當(dāng)計(jì)算機(jī)還是快樂、安全的時(shí)代時(shí),在機(jī)器中的幾乎每個(gè)進(jìn)程上,那些段的起始虛擬地址都是完全相同的。這將使遠(yuǎn)程挖掘安全漏洞變得容易。漏洞利用經(jīng)常需要去引用內(nèi)存位置:比如在棧中的一個(gè)地址,一個(gè)庫函數(shù)的地址,等等。遠(yuǎn)程攻擊可以閉著眼睛選擇這個(gè)地址,因?yàn)榈刂房臻g都是相同的。當(dāng)攻擊者們這樣做的時(shí)候,人們就會(huì)受到傷害。因此,地址空間隨機(jī)化開始流行起來。Linux 會(huì)通過在其起始地址上增加偏移量來隨機(jī)化棧、內(nèi)存映射段、以及堆。不幸的是,32 位的地址空間是非常擁擠的,為地址空間隨機(jī)化留下的空間不多,因此 妨礙了地址空間隨機(jī)化的效果。
          在進(jìn)程地址空間中的段是棧,在大多數(shù)編程語言中它存儲(chǔ)本地變量和函數(shù)參數(shù)。調(diào)用一個(gè)方法或者函數(shù)將推送一個(gè)新的棧幀stack frame到這個(gè)棧。當(dāng)函數(shù)返回時(shí)這個(gè)棧幀被刪除。這個(gè)簡(jiǎn)單的設(shè)計(jì),可能是因?yàn)閿?shù)據(jù)嚴(yán)格遵循 后進(jìn)先出(LIFO) 的次序,這意味著跟蹤棧內(nèi)容時(shí)不需要復(fù)雜的數(shù)據(jù)結(jié)構(gòu) —— 一個(gè)指向棧頂?shù)暮?jiǎn)單指針就可以做到。推入和彈出也因此而非常快且準(zhǔn)確。也可能是,持續(xù)的棧區(qū)重用往往會(huì)在 CPU 緩存 中保持活躍的棧內(nèi)存,這樣可以加快訪問速度。進(jìn)程中的每個(gè)線程都有它自己的棧。
          向棧中推送更多的而不是剛合適的數(shù)據(jù)可能會(huì)耗盡棧的映射區(qū)域。這將觸發(fā)一個(gè)頁面故障,在 Linux 中它是通過 expand_stack() 來處理的,它會(huì)去調(diào)用 acct_stack_growth() 來檢查棧的增長是否正常。如果棧的大小低于 RLIMIT_STACK 的值(一般是 8MB 大小),那么這是一個(gè)正常的棧增長和程序的合理使用,否則可能是發(fā)生了未知問題。這是一個(gè)棧大小按需調(diào)節(jié)的常見機(jī)制。但是,棧的大小達(dá)到了上述限制,將會(huì)發(fā)生一個(gè)棧溢出,并且,程序?qū)?huì)收到一個(gè)段故障Segmentation Fault錯(cuò)誤。當(dāng)映射的棧區(qū)為滿足需要而擴(kuò)展后,在棧縮小時(shí),映射區(qū)域并不會(huì)收縮。就像美國聯(lián)邦政府的預(yù)算一樣,它只會(huì)擴(kuò)張。
          動(dòng)態(tài)棧增長是 例外的情況 ,當(dāng)它去訪問一個(gè)未映射的內(nèi)存區(qū)域,如上圖中白色部分,是允許的。除此之外的任何其它訪問未映射的內(nèi)存區(qū)域?qū)⒂|發(fā)一個(gè)頁面故障,導(dǎo)致段故障。一些映射區(qū)域是只讀的,因此,嘗試去寫入到這些區(qū)域也將觸發(fā)一個(gè)段故障。
          在棧的下面,有內(nèi)存映射段。在這里,內(nèi)核將文件內(nèi)容直接映射到內(nèi)存。任何應(yīng)用程序都可以通過 Linux 的 mmap() 系統(tǒng)調(diào)用( 代碼實(shí)現(xiàn))或者 Windows 的 CreateFileMapping() / MapViewOfFile() 來請(qǐng)求一個(gè)映射。內(nèi)存映射是實(shí)現(xiàn)文件 I/O 的方便高效的方式。因此,它經(jīng)常被用于加載動(dòng)態(tài)庫。有時(shí)候,也被用于去創(chuàng)建一個(gè)并不匹配任何文件的匿名內(nèi)存映射,這種映射經(jīng)常被用做程序數(shù)據(jù)的替代。在 Linux 中,如果你通過 malloc() 去請(qǐng)求一個(gè)大的內(nèi)存塊,C 庫將會(huì)創(chuàng)建這樣一個(gè)匿名映射而不是使用堆內(nèi)存。這里所謂的“大”表示是超過了MMAP_THRESHOLD 設(shè)置的字節(jié)數(shù),它的缺省值是 128 kB,可以通過 mallopt() 去調(diào)整這個(gè)設(shè)置值。
          接下來講的是“堆”,就在我們接下來的地址空間中,堆提供運(yùn)行時(shí)內(nèi)存分配,像棧一樣,但又不同于棧的是,它分配的數(shù)據(jù)生存期要長于分配它的函數(shù)。大多數(shù)編程語言都為程序提供了堆管理支持。因此,滿足內(nèi)存需要是編程語言運(yùn)行時(shí)和內(nèi)核共同來做的事情。在 C 中,堆分配的接口是 malloc() 一族,然而在支持垃圾回收的編程語言中,像 C#,這個(gè)接口使用 new 關(guān)鍵字。
          如果在堆中有足夠的空間可以滿足內(nèi)存請(qǐng)求,它可以由編程語言運(yùn)行時(shí)來處理內(nèi)存分配請(qǐng)求,而無需內(nèi)核參與。否則將通過 brk() 系統(tǒng)調(diào)用(代碼實(shí)現(xiàn))來擴(kuò)大堆以滿足內(nèi)存請(qǐng)求所需的大小。堆管理是比較 復(fù)雜的,在面對(duì)我們程序的混亂分配模式時(shí),它通過復(fù)雜的算法,努力在速度和內(nèi)存使用效率之間取得一種平衡。服務(wù)一個(gè)堆請(qǐng)求所需要的時(shí)間可能是非常可觀的。實(shí)時(shí)系統(tǒng)有一個(gè) 特定用途的分配器 去處理這個(gè)問題。堆也會(huì)出現(xiàn)  碎片化 ,如下圖所示:


          ,我們抵達(dá)了內(nèi)存的低位段:BSS、數(shù)據(jù)、以及程序文本。在 C 中,靜態(tài)(全局)變量的內(nèi)容都保存在 BSS 和數(shù)據(jù)中。它們之間的不同之處在于,BSS 保存 未初始化的  靜態(tài)變量的內(nèi)容,它的值在源代碼中并沒有被程序員設(shè)置。BSS 內(nèi)存區(qū)域是 匿名 的:它沒有映射到任何文件上。如果你在程序中寫這樣的語句 static int cntActiveUsers,cntActiveUsers 的內(nèi)容就保存在 BSS 中。
          反過來,數(shù)據(jù)段,用于保存在源代碼中靜態(tài)變量 初始化后 的內(nèi)容。這個(gè)內(nèi)存區(qū)域是 非匿名 的。它映射了程序的二進(jìn)值鏡像上的一部分,包含了在源代碼中給定初始化值的靜態(tài)變量?jī)?nèi)容。因此,如果你在程序中寫這樣的語句 static int cntWorkerBees = 10,那么,cntWorkerBees 的內(nèi)容就保存在數(shù)據(jù)段中,并且初始值為 10。盡管可以通過數(shù)據(jù)段映射到一個(gè)文件,但是這是一個(gè)私有內(nèi)存映射,意味著,如果改變內(nèi)存,它并不會(huì)將這種變化反映到底層的文件上。必須是這樣的,否則,分配的全局變量將會(huì)改變你磁盤上的二進(jìn)制文件鏡像,這種做法就太不可思議了!
          用圖去展示一個(gè)數(shù)據(jù)段是很困難的,因?yàn)樗褂靡粋€(gè)指針。在那種情況下,指針 gonzo 的內(nèi)容(一個(gè) 4 字節(jié)的內(nèi)存地址)保存在數(shù)據(jù)段上。然而,它并沒有指向一個(gè)真實(shí)的字符串。而這個(gè)字符串存在于文本段中,文本段是只讀的,它用于保存你的代碼中的類似于字符串常量這樣的內(nèi)容。文本段也會(huì)在內(nèi)存中映射你的二進(jìn)制文件,但是,如果你的程序?qū)懭氲竭@個(gè)區(qū)域,將會(huì)觸發(fā)一個(gè)段故障錯(cuò)誤。盡管在 C 中,它比不上從一開始就避免這種指針錯(cuò)誤那么有效,但是,這種機(jī)制也有助于避免指針錯(cuò)誤。這里有一個(gè)展示這些段和示例變量的圖:


          你可以通過讀取 /proc/pid_of_process/maps 文件來檢查 Linux 進(jìn)程中的內(nèi)存區(qū)域。請(qǐng)記住,一個(gè)段可以包含很多的區(qū)域。例如,每個(gè)內(nèi)存映射的文件一般都在 mmap 段中的它自己的區(qū)域中,而動(dòng)態(tài)庫有類似于 BSS 和數(shù)據(jù)一樣的額外的區(qū)域。下一篇文章中我們將詳細(xì)說明“區(qū)域(area)”的真正含義是什么。此外,有時(shí)候人們所說的“數(shù)據(jù)段(data segment)”是指“數(shù)據(jù)(data) + BSS + 堆”。
          你可以使用 nm 和 objdump 命令去檢查二進(jìn)制鏡像,去顯示它們的符號(hào)、地址、段等等。終,在 Linux 中上面描述的虛擬地址布局是一個(gè)“彈性的”布局,這就是這幾年來的缺省情況。它假設(shè) RLIMIT_STACK 有一個(gè)值。如果沒有值的話,Linux 將恢復(fù)到如下所示的“經(jīng)典” 布局:


          這就是虛擬地址空間布局。接下來的文章將討論內(nèi)核如何對(duì)這些內(nèi)存區(qū)域保持跟蹤、內(nèi)存映射、文件如何讀取和寫入、以及內(nèi)存使用數(shù)據(jù)的意義。

          想要了解更多的嵌入式內(nèi)核應(yīng)用技術(shù)那就加入我們吧!

      免費(fèi)預(yù)約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 久久久久久综合岛国免费观看 | 欧美久久久久久久一区二区三区 | 日久精品不卡一区在线观看 | 亚洲欧美精品专区精品 | 亚洲高清国产拍精品青青草原 | 亚洲日韩一区二区三区高清 |