shell 编程 开源 Ubuntu centos apache 微软 mysql Windows wordpress php 程序员 Python linux java Android google nginx Firefox 云计算

堆和栈的区别

   malloc是libc的庫函數,用戶程序一般通過它(或類似函數)來分配內存空間。
   libc對內存的分配有兩種途徑,一是調整堆的大小,二是mmap一個新的虛擬內存區域(堆也是一個vma)。
   在內核中,堆是一個一端固定、一端可伸縮的vma(圖:左中)。可伸縮的一端通過系統調用brk來調整。libc管理著堆的空間,用戶調用malloc分配內存時,libc盡量從現有的堆中去分配。如果堆空間不夠,則通過brk增大堆空間。
   當用戶將已分配的空間free時,libc可能會通過brk減小堆空間。但是堆空間增大容易減小卻難,考慮這樣一種情況,用戶空間連續分配了10塊內存,前9塊已經free。這時,未free的第10塊哪怕只有1字節大,libc也不能夠去減小堆的大小。因為堆只有一端可伸縮,並且中間不能掏空。而第10塊內存就死死地占據著堆可伸縮的那一端,堆的大小沒法減小,相關資源也沒法歸還內核。
   當用戶malloc一塊很大的內存時,libc會通過mmap系統調用映射一個新的vma。因為對於堆的大小調整和空間管理還是比較麻煩的,重新建一個vma會更方便(上面提到的free的問題也是原因之一)。
   那麽為什麽不總是在malloc的時候去mmap一個新的vma呢?第一,對於小空間的分配與回收,被libc管理的堆空間已經能夠滿足需要,不必每次都去進行系統調用。並且vma是以page為單位的,最小就是分配一個頁;第二,太多的vma會降低系統性能。缺頁異常、vma的新建與銷毀、堆空間的大小調整、等等情況下,都需要對vma進行操作,需要在當前進程的所有vma中找到需要被操作的那個(或那些)vma。vma數目太多,必然導致性能下降。(在進程的vma較少時,內核采用鏈表來管理vma;vma較多時,改用紅黑樹來管理。)
   與堆一樣,棧也是一個vma(圖:左中),這個vma是一端固定、一端可伸(註意,不能縮)的。這個vma比較特殊,沒有類似brk的系統調用讓這個vma伸展,它是自動伸展的。
   當用戶訪問的虛擬地址越過這個vma時,內核會在處理缺頁異常的時候將自動將這個vma增大。內核會檢查當時的棧寄存器(如:ESP),訪問的虛擬地址不能超過ESP加n(n為CPU壓棧指令一次性壓棧的最大字節數)。也就是說,內核是以ESP為基準來檢查訪問是否越界。
   但是,ESP的值是可以由用戶態程序自由讀寫的,用戶程序如果調整ESP,將棧劃得很大很大怎麽辦呢?內核中有一套關於進程限制的配置,其中就有棧大小的配置,棧只能這麽大,再大就出錯。
   對於一個進程來說,棧一般是可以被伸展得比較大(如:8MB)。然而對於線程呢?
   首先線程的棧是怎麽回事?前面說過,線程的mm是共享其父進程的。雖然棧是mm中的一個vma,但是線程不能與其父進程共用這個vma(兩個運行實體顯然不用共用一個棧)。於是,在線程創建時,線程庫通過mmap新建了一個vma,以此作為線程的棧(大於一般為:2M)。
   可見,線程的棧在某種意義上並不是真正棧,它是一個固定的區域,並且容量很有限。

延伸阅读

  • 抱歉,暂无相关内容!

评论