Linux System Programming 學習筆記(九) 內存管理

1. 進程地址空間

Linux中,進程並非直接操做物理內存地址,而是 每一個進程關聯一個虛擬地址空間
內存頁是memory management unit (MMU) 能夠管理的最小地址單元
機器的體系結構決定了內存頁大小,32位系統一般是 4KB, 64位系統一般是 8KB
內存頁分爲 valid or invalid:
A valid page is associated with an actual page of data,例如RAM或者磁盤上的文件
An invalid page is not associated with anything,表現爲 未使用未分配的地址空間,存取無效內存頁將會致使 段錯誤
 
若是有效內存頁與外存數據相關聯, 進程就不能存取內存頁直到數據交換至物理內存
當進程直接存取關聯到磁盤文件的內存頁時,會產生 缺頁中斷,而後內核透明地將數據從外存磁盤置換到 物理內存
there is considerably more virtual memory than physical memory
Paging out is the process of moving data from physical memory to secondary storage
 

2. 動態內存

/* obtaining dynamic memory */
#include <stdlib.h>
void * malloc (size_t size);
The contents of the memory are undefined
切記: 每次動態分配內存都必須嚴格嚴查返回值是否爲NULL,表示分配失敗
 
/* returns a pointer to a block of memory suitable for holding an array of nr elements, each of size bytes */
#include <stdlib.h>
void * calloc (size_t nr, size_t size);
注意:calloc() zeros all bytes in the returned chunk of memory,默認0值初始化
程序員必須 優先使用 calloc() 確保分配的內存已0值初始化
calloc() 進行0值初始化 比 memset 速度要快

 

/* resizing (making larger or smaller) existing allocations */
#include <stdlib.h>
void * realloc (void *ptr, size_t size);
It returns a pointer to the newly sized memory. 
參數size爲0時,此時realloc至關於 free
參數ptr爲NULL時,此時realloc至關於 malloc
 

3. 數據對齊

編寫跨平臺代碼時,必須當心 數據對齊要求
大多數狀況下,編譯器和C標準庫透明的處理數據對齊狀況,Linux系統中,malloc系列函數分配的內存 在32位系統是 8-byte 對齊,在64位系統是16-byte 對齊
posix_memalign() 是改變系統默認數據對齊規則的函數
由於數據對齊要求,因此動態內存分配獲得的實際大小 必定是 大於或等於 請求大小
/* actual allocation size of the chunk of memory pointed to by ptr */
#include <malloc.h>
size_t malloc_usable_size (void *ptr);
dereferencing a cast of a pointer from one type of variable to a different type is usually a violation of the strict aliasing rule
 

4. 管理數據段

#include <unistd.h>
int brk (void *end);
void * sbrk (intptr_t increment);
brk() sets the break point (the end of the data segment) to the address specified by end.
sbrk() increments the end of the data segment by increment bytes, which may be a positive or negative delta.
 

5. 匿名內存映射

glibc的內存分配綜合使用了 brk、sbrk數據段管理 和 mmap 內存映射
夥伴內存分配(buddy memory allocation scheme):
將數據段劃分爲一系列大小爲2的指數冪的內存塊,返回一個與請求大小最適應的內存塊
釋放內存時,若是相鄰的劃分塊也被標記位free,那麼將合併內存塊
優勢:速度快,簡單
缺點:致使大量 內部碎片
內部碎片:實際分配到的內存塊比請求大小要多,致使 分配的內存塊使用率低下
外部碎片:系統中空閒內存塊總和要比請求大小多,可是沒有 單一內存塊知足請求大小,致使 系統內存塊使用率低下
 
the heap is not shrunk after each free. Instead, the malloc() implementation keeps freed memory around for a subsequent allocation.
可是當申請較大內存時,若是釋放後, 仍然將此內存保留以便後續分配,這將影響到系統內存使用
 
For large allocations, glibc does not use the heap. Instead, glibc creates an anonymous memory mapping to satisfy the allocation request
普通內存映射將內存映射到磁盤文件, 匿名內存映射將內存映射到 a large, zero-filled block of memory
匿名內存映射發生在 堆內存以外,因此不會發生數據段內存碎片
 
匿名內存映射的好處:
a. 不用擔憂碎片問題,the mapping is unmapped,則內存當即歸還系統
b. 能夠調整塊大小,且有能夠調整的權限
c. 每次分配都存在單一內存映射中,不用擔憂全局的堆內存管理
d. 分配到的內存已經 0值初始化,由於 kernel maps the application's anonymous pages to a zero-filled page via copy-on-write
 
匿名內存映射的壞處:
a. 每一個內存映射大小必須是系統內存頁面大小的整數倍,這將致使內存空間浪費,利用率低
b. 建立內存映射的開銷 大於 堆內存分配的開銷 
 
glibc's malloc() uses the data segment to satisfy small allocations and anonymous memory mappings to satisfy large allocations
默認臨界值是128KB, 小於或等於128KB則使用堆內存分配,大於128KB則使用匿名內存映射
 
建立匿名內存映射 只須要在mmap函數中將 fd參數設置爲-1 (由於並非映射到文件)
 
void *p;
p = mmap (NULL,                        /* do not care where */
          512 * 1024,                  /* 512 KB */
          PROT_READ | PROT_WRITE,      /* read/write */
          MAP_ANONYMOUS | MAP_PRIVATE, /* anonymous, private */1,                          /* fd (ignored) */
          0);                          /* offset (ignored) */
if (p == MAP_FAILED)
        perror ("mmap");
else
        /* 'p' points at 512 KB of anonymous memory... */

 

6. 基於棧的動態內存分配

/* make a dynamic memory allocation from the stack */
#include <alloca.h>
void * alloca (size_t size);
Usage is identical to malloc(), but you do not need to (indeed, must not) free the allocated memory
注意 alloca是在棧上進行動態內存分配,而且 不須要使用free釋放內存
This means you cannot use this memory once the function that calls alloca() returns! However, because you don't have to do any  cleanup by calling free()
 
POSIX未定義alloca函數,因此不適合編寫跨平臺程序
 

7. 變長數組 Variable-Length Arrays

C99引進 變長數組, 數組的長度在運行時動態肯定,而不是在編譯時靜態肯定
for (i = 0; i < n; ++i) {
        char foo[i + 1];
        /* use 'foo'... */
}
On each iteration of the loop, foo is dynamically created and automatically cleaned up when it falls out of scope
 

8. 選擇內存分配機制

 
 

9. 內存操做

 

/* memset() sets the n bytes starting at s to the byte c and returns s */
#include <string.h>
void * memset (void *s, int c, size_t n);
/* compares two chunks of memory for equivalence */
#include <string.h>
int memcmp (const void *s1, const void *s2, size_t n);

由於結構體一般涉及到數據對齊,因此使用memcmp來比較兩個結構體是不安全的程序員

/* are two dinghies identical? (BROKEN) */
int compare_dinghies (struct dinghy *a, struct dinghy *b)
{
        return memcmp (a, b, sizeof (struct dinghy));
}

上述代碼不安全,應該分別比較每一個結構體成員:數組

/* are two dinghies identical? */
int compare_dinghies (struct dinghy *a, struct dinghy *b)
{
        int ret;
        if (a->nr_oars < b->nr_oars)
                return1;
        if (a->nr_oars > b->nr_oars)
                return 1;
        ret = strcmp (a->boat_name, b->boat_name);
        if (ret)
                return ret;
        /* and so on, for each member... */
}

 

/* copies the first n bytes of src to dst, returning dst */
#include <string.h>
void * memmove (void *dst, const void *src, size_t n);

memmove能夠正確處理內存區重疊的狀況(部分dst位於src以內)安全

 

#include <string.h>
void * memcpy (void *dst, const void *src, size_t n)

memcpy在內存區出現重疊時 屬於未定義行爲app

 

/* scans the n bytes of memory pointed at by s for the character c */
#include <string.h>
void * memchr (const void *s, int c, size_t n);

 

10. 鎖定內存

Linux實現的 內存頁置換, which means that pages are paged in from disk as needed and paged out to disk when no longer needed

 

/* 「locking」one or more pages into physical memory, ensuring that they are never paged out to disk */
#include <sys/mman.h>
int mlock (const void *addr, size_t len);

mlock() locks the virtual memory starting at addr and extending for len bytes into physical memoryide

 

/*  mlockall() locks all of the pages in the current process's address space into physical memory. */
#include <sys/mman.h>
int mlockall (int flags);
相關文章
相關標籤/搜索