我們經常用free檢視伺服器的記憶體使用情況,而free中的輸出卻有些讓人困惑,如下:
圖1-1
先看看各個數字的意義以及如何計算得到:
free指令輸出的第二行(Mem):這行分别顯示了實體記憶體的總量(total)、已使用的 (used)、空閑的(free)、共享的(shared)、buffer(buffer大小)、 cache(cache的大小)的記憶體。我們知道Total、free、buffers、cached這幾個字段是從/proc/meminfo中擷取的,而used = total – free。Share列已經過時,忽略(見參考)。
free指令輸出的第三行(-/+ buffers/cache):
它顯示的第一個值(used):548840,這個值表示系統本身使用的記憶體總量,即除去buffer/cache,等于Mem行used列 - Mem行buffers列 - Mem行cached列。
它顯示的第二個值(free):1417380,這個值表示系統目前可用記憶體,它等于Mem行total列—used,也等于Mem行free列 + Mem行buffers列 + Mem行cached列。
free指令輸出的第四行(Swap) 這行顯示交換記憶體的總量、已使用量、 空閑量。
我們都知道free是從/proc/meminfo中讀取相關的資料的。
圖1-2
來看看/proc/meminfo的相關實作:
// proc_misc.c
static int meminfo_read_proc(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
…
struct sysinfo i;
si_meminfo(&i); //統計memory的使用情況
si_swapinfo(&i); //統計swap的使用情況
len = sprintf(page,
"MemTotal: %8lu kB\n"
"MemFree: %8lu kB\n"
"Buffers: %8lu kB\n"
"Cached: %8lu kB\n"
"SwapCached: %8lu kB\n"
K(i.totalram),
K(i.freeram),
K(i.bufferram),
K(get_page_cache_size()-total_swapcache_pages-i.bufferram),
K(total_swapcache_pages),
}
struct sysinfo {
long uptime; /* Seconds since boot */
unsigned long loads[3]; /* 1, 5, and 15 minute load averages */
unsigned long totalram; /*總的頁框數,Total usable main memory size */
unsigned long freeram; /* Available memory size */
unsigned long sharedram; /* Amount of shared memory */
unsigned long bufferram; /* Memory used by buffers */
unsigned long totalswap; /* Total swap space size */
unsigned long freeswap; /* swap space still available */
unsigned short procs; /* Number of current processes */
unsigned short pad; /* explicit padding for m68k */
unsigned long totalhigh; /* Total high memory size */
unsigned long freehigh; /* Available high memory size */
unsigned int mem_unit; /* Memory unit size in bytes */
char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding: libc5 uses this.. */
};
圖中,Buffers對應sysinfo.bufferram,核心中以頁框為機關,通過宏K轉化成以KB為機關輸出。
void si_meminfo(struct sysinfo *val)
val->totalram = totalram_pages;//總的頁框數
val->sharedram = 0;
val->freeram = nr_free_pages();//計算空閑頁框數
val->bufferram = nr_blockdev_pages();//block device使用的面框數
#ifdef CONFIG_HIGHMEM
val->totalhigh = totalhigh_pages;
val->freehigh = nr_free_highpages();
#else
val->totalhigh = 0;
val->freehigh = 0;
#endif
val->mem_unit = PAGE_SIZE;
//周遊所有的塊裝置,累加相應的頁面數量
long nr_blockdev_pages(void)
struct list_head *p;
long ret = 0;
spin_lock(&bdev_lock);
list_for_each(p, &all_bdevs) {
struct block_device *bdev;
bdev = list_entry(p, struct block_device, bd_list);
ret += bdev->bd_inode->i_mapping->nrpages;
}
spin_unlock(&bdev_lock);
return ret;
nr_blockdev_pages計算塊裝置使用的頁框數,周遊所有塊裝置,将使用的頁框數相加。而不包含普通檔案使用的頁框數。
Cached = get_page_cache_size()-total_swapcache_pages-i.bufferram。
static inline unsigned long get_page_cache_size(void)
int ret = atomic_read(&nr_pagecache);
if (unlikely(ret < 0))
ret = 0;
Cache的大小為核心總的page cache減去swap cache和塊裝置占用的頁框數量,實際上cache即為普通檔案的占用的page cache。實際上,在函數add_to_page_cache和__add_to_swap_cache 中,都會通過調用pagecache_acct實作對核心變量nr_pagecache進行累加。前者對應page cache,核心讀塊裝置和普通檔案使用;後者對應swap cache,核心讀交換分區使用。
Page cache(頁面緩存)
在linux系統中,為了加快檔案的讀寫,核心中提供了page cache作為緩存,稱為頁面緩存(page cache)。為了加快對塊裝置的讀寫,核心中還提供了buffer cache作為緩存。在2.4核心中,這兩者是分開的。這樣就造成了雙緩沖,因為檔案讀寫最後還是轉化為對塊裝置的讀寫。在2.6中,buffer cache合并到page cache中,對應的頁面叫作buffer page。當進行檔案讀寫時,如果檔案在磁盤上的存儲塊是連續的,那麼檔案在page cache中對應的頁是普通的page,如果檔案在磁盤上的資料塊是不連續的,或者是裝置檔案,那麼檔案在page cache中對應的頁是buffer page。buffer page與普通的page相比,每個頁多了幾個buffer_head結構體(個數視塊的大小而定)。此外,如果對單獨的塊(如超級塊)直接進行讀寫,對應的page cache中的頁也是buffer page。這兩種頁面雖然形式略有不同,但是最終他們的資料都會被封裝成bio結構體,送出到通用塊裝置驅動層,統一進行I/O排程。
// include/buffer_head.h
struct buffer_head {
/* First cache line: */
unsigned long b_state; /* buffer state bitmap (see above) */
struct buffer_head *b_this_page;/* circular list of page's buffers */
struct page *b_page; /* the page this bh is mapped to,頁框 */
atomic_t b_count; /* users using this block */
u32 b_size; /* block size */
sector_t b_blocknr; /* block number,塊裝置中的邏輯塊号 */
char *b_data; /* pointer to data block */
struct block_device *b_bdev;//塊裝置
bh_end_io_t *b_end_io; /* I/O completion */
void *b_private; /* reserved for b_end_io */
struct list_head b_assoc_buffers; /* associated with another mapping */
在kernel2.6之後,buffer_head沒有别的作用,主要用來保持頁框與塊裝置中資料塊的映射關系。
Buffer page(緩沖頁)
如果核心需要單獨通路一個塊,就會涉及到buffer page,并會檢查對應的buffer head。
核心建立buffer page的兩種常見情況:
(1)當讀或者寫一個檔案頁的資料塊不相鄰時。發生這種情況是因為檔案系統為檔案配置設定了非連續的塊,或者檔案有洞。具體請參見block_read_full_page(fs/buffer.c)函數:
///對于塊裝置檔案和資料塊不相鄰的普通檔案,都會調用該方法
int block_read_full_page(struct page *page, get_block_t *get_block)
if (!page_has_buffers(page))///page沒有配置設定buffer head,見buffer_head.h
create_empty_buffers(page, blocksize, 0);//為page建立buffer_head
head = page_buffers(page);
這裡使用buffer head主要是通過buffer head建立頁框與資料塊的映射關系。因為頁面中的資料不是連接配接的,而頁框描述符struct page的字段又不足以表達這種資訊。
(2)通路一個單獨的磁盤塊(比如,讀超級塊或者索引節點塊時)。參見ext2_fill_super(fs/ext2/super.c),該函數在安裝ext2檔案系統時調用。
Buffer page和buffer head的關系:
小結:對于普通檔案,如果頁面中的塊是連續的,則頁面沒有對應buffer head;如果不連續,則頁面有對應的buffer head,參見do_mpage_readpage函數。對于塊裝置,無論是讀取單獨的資料塊,還是作為裝置檔案來進行讀取,頁面始終有對應的buffer head,參見block_read_full_page/__bread函數。
Swap cache(交換緩存)
在圖1-2中,有一行swapcached,它表示交換緩存的大小。Page cache是磁盤資料在記憶體中的緩存,而swap cache則是交換分區在記憶體中的臨時緩存。Swap cache對交換分區具有十分重要的意義,這裡不再贅述,詳細讨論請見參考文獻。
Swap用來為非映射頁在磁盤上提供備份。有三類頁必須由交換子系統進行處理:(1)程序匿名線性區(anonymous memory region)的頁,比如使用者态的堆棧和堆,匿名記憶體映射也是。(2) 程序私有記憶體映射的髒頁。(3)IPC共享記憶體區的頁。1和3都比較好了解,因為它們都沒有對應的磁盤檔案,是以必須通過swap來臨時存儲資料。下面讨論一下第2點。
Memory mapping(記憶體映射)
核心有兩種類型的記憶體映射:共享型(shared)和私有型(private)。私有型是當程序為了隻讀檔案,而不寫檔案時使用,這時,私有映射更加高效。但是,任何對私有映射頁的寫操作都會導緻核心停止映射該檔案中的頁。是以,寫操作既不會改變磁盤上的檔案,對通路該檔案的其它程序也是不可見的。
共享記憶體中的頁通常都位于page cache,私有記憶體映射隻要沒有修改,也位于page cache。當程序試圖修改一個私有映射記憶體頁時,核心就把該頁進行複制,并在頁表中用複制的頁替換原來的頁。由于修改了頁表,盡管原來的頁仍然在page cache,但是已經不再屬于該記憶體映射。而新複制的頁也不會插入page cache,而是添加到匿名頁反向映射資料結構。參見do_no_page(mm/memory.c)。
static int
do_no_page(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, int write_access, pte_t *page_table, pmd_t *pmd)
//寫私有記憶體映射,則複制頁面
if (write_access && !(vma->vm_flags & VM_SHARED)) {
struct page *page;
if (unlikely(anon_vma_prepare(vma)))
goto oom;
page = alloc_page_vma(GFP_HIGHUSER, vma, address);
if (!page)
copy_user_highpage(page, new_page, address);///copy page
page_cache_release(new_page);
new_page = page;
anon = 1;
if (anon) {
lru_cache_add_active(new_page);//将頁插入LRU的活動連結清單
page_add_anon_rmap(new_page, vma, address);//将匿名頁插入到反向映射資料結構
}
釋放cache
Free指令輸出的第一行是對應的實實在在的記憶體,不管是buffer,還是cache。Swap對應磁盤上的交換分區。Kernel會盡量使用RAM做cache,是以一般cache都比較大:
8G的記憶體,而空閑(free)的記憶體隻有約50M,比較驚人。其實,cached占了3G+。另外,我們看到第三行used列約為4G,是比較大的,确實,該機器上跑了好幾個接入服務。
Kernel2.6.16之後的版本提供了一種釋放cache的機制,通過修改核心參數/proc/sys/vm/drop_caches讓核心釋放幹淨的cache。
To free pagecache:
echo 1 > /proc/sys/vm/drop_caches
To free dentries and inodes:
echo 2 > /proc/sys/vm/drop_caches
To free pagecache, dentries and inodes:
echo 3 > /proc/sys/vm/drop_caches
# free
total used free shared buffers cached
Mem: 1966220 1676428 289792 0 418900 705216
-/+ buffers/cache: 552312 1413908
Swap: 2104504 131084 1973420
# echo 1 > /proc/sys/vm/drop_caches
Mem: 1966220 597840 1368380 0 324 65852
-/+ buffers/cache: 531664 1434556
設定核心參數drop_caches後,cached的值迅速下降。通常來說,Linux會盡量使用可用的RAM,cache過高,是正常的。而手動釋放cache會增加I/O開銷,導緻系統性能下降。
最後,水準有限,歡迎指正和探讨。
主要參考:
《ULK》
http://linux.die.net/man/1/free
http://www.kernel.org/doc/Documentation/sysctl/vm.txt
<a href="http://linux-mm.org/Drop_Caches">http://linux-mm.org/Drop_Caches</a>
作者:MrDB
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。