1. 程式人生 > >Linux程序與記憶體分配

Linux程序與記憶體分配

程序

    程序是由核心定義的抽象實體,該實體分配用以執行程式的各項系統資源,是擁有資源的基本單位。從核心的角度來看,程序由使用者記憶體空間和一系列核心資料結構組成,其中使用者記憶體空間包含程式程式碼及程式碼所使用的變數(程式段和資料段),而核心資料結構則用於維護程序狀態資訊。

    每個程序都有一個程序號,用以標識系統中的某個程序。Linux核心限制程序號小於等於32767,一旦程序號達到32767,會將程序號計數器重置為300,因為低數值的程序為系統程序和守護程序長期佔用。1號程序init程序為所有程序的始祖程序。

程序記憶體佈局

    每個程序所分配的記憶體由很多“段”組成。

  • 文字段包含了程序執行的程式機器語言指令。
  • 初始化資料段包含顯式初始化的全域性變數和靜態變數。
  • 未初始化資料段包含了未進行顯式初始化的全域性變數和靜態變數。
  • 棧是一個動態增長和收縮的段,由棧幀組成。系統為每個當前呼叫的函式分配一個棧幀,棧幀中儲存函式的實參、區域性變數,還有函式呼叫的連結資訊。
  • 堆是在執行時動態進行記憶體分配的一塊區域。

虛擬記憶體管理

    虛擬記憶體管理技術是基於大多數程式的一個典型特性:訪問區域性性,以求高效使用CPU和RAM資源。正是由於訪問區域性性,使得程式即使有部分地址空間存在於RAM,依舊可以執行。

  • 空間區域性性:是指程式傾向於訪問在最近訪問過的記憶體地址附近的記憶體
  • 時間區域性性:是指程式傾向於在不久的將來再次訪問最近剛剛訪問過的記憶體地址

    虛擬記憶體規劃之一就是將每個程式使用的記憶體切割成小型的、固定大小的“頁”單元。任何一個時刻只有部分頁駐留在實體記憶體(RAM)中,程式未使用的頁拷貝儲存在交換區。若程序欲訪問的頁面目前未駐留在實體記憶體中,將會發生頁面錯誤,核心即可掛起程序的執行,同時從磁碟中將該頁面載入記憶體。

    為支援這一組織方式,核心需要為每個程序維護一張頁表,該頁表描述了每頁在程序虛擬地址空間中的位置。頁表中的每個條目要麼指出一個虛擬頁面在RAM中的位置,要麼表明其當前駐留在磁碟上。

    虛擬記憶體管理的優點:

  1. 程序與程序、程序與核心相互隔離。
  2. 適當情況下,兩個或者更多程序可以共享記憶體。兩個場景:執行同一個程式的多個程序可共享一份程式程式碼副本,程序可以使用shmget()和mmap()系統呼叫顯示請求與其他程序共享記憶體區(共享記憶體,用於程序通訊)
  3. 便於實現記憶體保護機制。
  4. 使得每個程序使用的RAM減少了,RAM中可以同時容納的程序數量增多了,提高CPU利用率。

在堆上分配記憶體

    程序可以通過增加堆的大小來分配記憶體,所謂堆是一段長度可變的連續虛擬記憶體,始於程序的未初始化資料段末尾,隨著記憶體的分配和釋放而增減,通常將堆的當前記憶體邊界稱為“program break”。

    UNIX系統提供兩個操縱program break的系統呼叫:brk()和sbrk()

#include <unistd.h>

int brk(void *end_data_segment);

void *sbrk(intptr_t increment);

    系統呼叫brk()會將program break設定為引數所指定的位置。呼叫sbrk()將program break在原有地址上增加從引數increment傳入的大小。

    除此之外,C語言一般使用malloc和free在堆上分配和釋放記憶體。主要優點:更易於在多執行緒程式中使用,介面簡單,允許分配小塊記憶體,允許隨意釋放記憶體塊,維護一張空閒記憶體列表,在後續記憶體分配呼叫時使用。

#include <stdlib.h>

void *malloc(size_t size);

coid free(void *ptr);

    malloc函式在堆上分配引數size位元組大小的記憶體,並返回指向新分配記憶體起始位置的指標,所分配的記憶體未經初始化。若無法分配記憶體(program break已經達到地址上限),則malloc返回null。

    一般情況下,free並不降低program break位置,而是將這塊記憶體新增到空閒記憶體列表中,供後續malloc函式迴圈使用主要原因:

  1. 被釋放的記憶體塊通常會位於堆中間,所以不能降低program break
  2. 這樣會減少程式必須執行sbrk()呼叫次數
  3. 在大多數情況下,程式通常傾向於持有已分配記憶體或是反覆釋放和重新分配記憶體

malloc和free的實現

    malloc的實現過程:首先他會掃描之前由free釋放的空閒記憶體塊列表,以求找到尺寸大於或者等於要求的一塊空閒記憶體,如果在空閒記憶體列表中根本找不到足夠大的空閒記憶體塊,那麼malloc會呼叫sbrk()以分配更多的記憶體,為了減少sbrk()的呼叫次數, malloc並未按照嚴格的位元組要求來分配記憶體,而是更大幅度的增加program break,將超出部分閒置於空閒記憶體列表(vector原理類似)

    由於malloc在分配記憶體塊時,會額外分配幾個位元組來存放記錄這塊記憶體大小的整數值,該整數值就位於記憶體的起始處。當free()使用記憶體塊本身的空間來存放連結串列指標。(有點像雙向連結串列)

     當記憶體不斷釋放、分配,空閒列表中的空閒記憶體和已分配的記憶體混雜在一起,就如下圖所示。

堆疊上分配記憶體:alloc()

    alloc()也可以動態分配記憶體,不過不是從堆上分配記憶體,而是通過增加棧幀的大小,從堆疊上分配。當前呼叫函式的棧幀位於堆疊頂部,故這種方法是可行的(相當於棧幀從上往下擴充套件)

    alloc()分配記憶體優點:

  1. 速度較快,因為編譯器將alloc作為內聯程式碼,並通過直接呼叫堆疊指標來實現。
  2. 由alloc分配的記憶體隨棧幀的移除而自動釋放,不需要free釋放記憶體,即呼叫alloc的函式返回時。

參考 《TLPI》、《APUE》