page cache 与free
我們經常用free查看服務器的內存使用情況,而free中的輸出卻有些讓人困惑,如下:
?
先看看各個數字的意義以及如何計算得到:
free命令輸出的第二行(Mem):這行分別顯示了物理內存的總量(total)、已使用的 (used)、空閑的(free)、共享的(shared)、buffer(buffer大小)、 cache(cache的大小)的內存。我們知道Total、free、buffers、cached這幾個字段是從/proc/meminfo中獲取的,而used = total – free。Share列已經過時,忽略(見參考)。
free命令輸出的第三行(-/+ buffers/cache):
它顯示的第一個值(used):210236,這個值表示系統本身使用的內存總量,即除去buffer/cache,等于Mem行used列 - Mem行buffers列 - Mem行cached列。
它顯示的第二個值(free):814956,這個值表示系統當前可用內存,它等于Mem行total列— buffers/cache used,也等于Mem行free列 + Mem行buffers列 + Mem行cached列。
free命令輸出的第四行(Swap) 這行顯示交換內存的總量、已使用量、 空閑量。
?
我們都知道free是從/proc/meminfo中讀取相關的數據的。
?
下面是/proc/meminfo的實現:
復制代碼
static int meminfo_read_proc(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
struct sysinfo i;
int len;
unsigned long committed;
unsigned long allowed;
struct vmalloc_info vmi;
long cached;
/*
* display in kilobytes.
*/
#define K(x) ((x) << (PAGE_SHIFT - 10))
si_meminfo(&i);
si_swapinfo(&i);
committed = atomic_read(&vm_committed_space);
allowed = ((totalram_pages - hugetlb_total_pages())
* sysctl_overcommit_ratio / 100) + total_swap_pages;
cached = global_page_state(NR_FILE_PAGES) -
total_swapcache_pages - i.bufferram;
if (cached < 0)
cached = 0;
get_vmalloc_info(&vmi);
/*
* Tagged format, for easy grepping and expansion.
*/
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(cached),
K(total_swapcache_pages),
......
#undef K
}
?
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;//total ram pages
val->sharedram = 0;
val->freeram = global_page_state(NR_FREE_PAGES);//free mem pages
val->bufferram = nr_blockdev_pages();//block devices used pages
val->totalhigh = totalhigh_pages;
val->freehigh = nr_free_highpages();
val->mem_unit = PAGE_SIZE;
}
long nr_blockdev_pages(void)
{
struct block_device *bdev;
long ret = 0;
spin_lock(&bdev_lock);
list_for_each_entry(bdev, &all_bdevs, bd_list) {
ret += bdev->bd_inode->i_mapping->nrpages;
}
spin_unlock(&bdev_lock);
return ret;
}
復制代碼
nr_blockdev_pages計算塊設備使用的頁框數,遍歷所有塊設備,將使用的頁框數相加。而不包含普通文件使用的頁框數。
cached = global_page_state(NR_FILE_PAGES) - total_swapcache_pages - i.bufferram;
復制代碼
static inline unsigned long global_page_state(enum zone_stat_item item)
{
long x = atomic_long_read(&vm_stat[item]);
#ifdef CONFIG_SMP
if (x < 0)
x = 0;
#endif
return x;
}
復制代碼
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調度。
復制代碼
/**
* 塊緩沖頭描述符
*/
struct buffer_head {
/* 塊緩沖狀態位圖,如BH_Uptodate */
unsigned long b_state; /* buffer state bitmap (see above) */
/* 指向下一個塊緩沖,二者屬于同一個頁緩存 */
struct buffer_head *b_this_page;/* circular list of page's buffers */
/* 如果緩沖區屬于頁緩存,則指向緩存頁。如果獨立于頁緩存,則為NULL */
struct page *b_page; /* the page this bh is mapped to */
/* 對應的塊號 */
sector_t b_blocknr; /* start block number */
/* 塊長 */
size_t b_size; /* size of mapping */
/* 內存中的數據指針 */
char *b_data; /* pointer to data within the page */
/* 后備設備 */
struct block_device *b_bdev;
/* 當IO操作完成時,由內核調用的回調函數 */
bh_end_io_t *b_end_io; /* I/O completion */
/* 預留指針,用于b_end_io。一般用于日志文件系統。 */
void *b_private; /* reserved for b_end_io */
struct list_head b_assoc_buffers; /* associated with another mapping */
/* 所屬地址空間 */
struct address_space *b_assoc_map; /* mapping this buffer is
associated with */
/* 訪問計數器 */
atomic_t b_count; /* users using this buffer_head */
};
復制代碼
在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)
{
struct inode *inode = page->mapping->host;
sector_t iblock, lblock;
struct buffer_head *bh, *head, *arr[MAX_BUF_PER_PAGE];
unsigned int blocksize;
int nr, i;
int fully_mapped = 1;
BUG_ON(!PageLocked(page));
blocksize = 1 << inode->i_blkbits;
if (!page_has_buffers(page))/* 如果還沒有建立緩沖區,則建立幾個空緩沖區 */
create_empty_buffers(page, blocksize, 0);
/* 取頁面關聯的第一個緩沖區 */
head = page_buffers(page);
/* 計算要讀取的塊號 */
iblock = (sector_t)page->index << (PAGE_CACHE_SHIFT - inode->i_blkbits);
lblock = (i_size_read(inode)+blocksize-1) >> inode->i_blkbits;
bh = head;
nr = 0;
i = 0;
/* 遍歷所有緩沖區 */
do {
if (buffer_uptodate(bh))/* 緩沖區已經與設備匹配了,不需要處理 */
continue;
if (!buffer_mapped(bh)) {/* 沒有映射 */
int err = 0;
fully_mapped = 0;
if (iblock < lblock) {/* 在設備上還不存在塊 */
WARN_ON(bh->b_size != blocksize);
/* 獲得邏輯塊在磁盤上的位置 */
err = get_block(inode, iblock, bh, 0);
if (err)
SetPageError(page);
}
if (!buffer_mapped(bh)) {/* 對應的塊是稀疏塊,寫入0即可 */
zero_user_page(page, i * blocksize, blocksize,
KM_USER0);
if (!err)
set_buffer_uptodate(bh);
continue;
}
/*
* get_block() might have updated the buffer
* synchronously
*/
if (buffer_uptodate(bh))/* get_block將緩沖區更新了,繼續處理下一塊 */
continue;
}
/* 緩沖區已經映射,但內容不是最新的,將它放到臨時數組中 */
arr[nr++] = bh;
} while (i++, iblock++, (bh = bh->b_this_page) != head);
if (fully_mapped)
SetPageMappedToDisk(page);
if (!nr) {/* 所有緩沖區都是最新的 */
/*
* All buffers are uptodate - we can set the page uptodate
* as well. But not if get_block() returned an error.
*/
if (!PageError(page))/* 設置頁的uptodate標志,然后退出 */
SetPageUptodate(page);
unlock_page(page);
return 0;
}
/* Stage two: lock the buffers */
for (i = 0; i < nr; i++) {/* 鎖定緩沖區 */
bh = arr[i];
lock_buffer(bh);
mark_buffer_async_read(bh);
}
/*
* Stage 3: start the IO. Check for uptodateness
* inside the buffer lock in case another process reading
* the underlying blockdev brought it uptodate (the sct fix).
*/
for (i = 0; i < nr; i++) {/* 遍歷頁內所有需要更新的緩沖區 */
bh = arr[i];
if (buffer_uptodate(bh))/* 在沒有獲得鎖的期間,如果有其他進程讀取的內容 */
end_buffer_async_read(bh, 1);
else
submit_bh(READ, bh);/* 提交IO請求 */
}
return 0;
}
復制代碼
這里使用buffer head主要是通過buffer head建立頁框與數據塊的映射關系。因為頁面中的數據不是連接的,而頁框描述符struct page的字段又不足以表達這種信息。
該函數會調用create_empty_buffers來創建一組全新的緩沖區,并與page關聯起來
復制代碼
/**
* 創建一組全新的緩沖區,以便與頁關聯
*/
void create_empty_buffers(struct page *page,
unsigned long blocksize, unsigned long b_state)
{
struct buffer_head *bh, *head, *tail;
/* 創建所需要數目的緩沖頭,并將其形成一個鏈表,返回第一個緩沖頭 */
head = alloc_page_buffers(page, blocksize, 1);
/* 設置所有緩沖頭的狀態,并將緩沖頭形成一個環形鏈表 */
bh = head;
do {
bh->b_state |= b_state;
tail = bh;
bh = bh->b_this_page;
} while (bh);
tail->b_this_page = head;
/* 根據頁面狀態設置塊緩沖區的狀態 */
spin_lock(&page->mapping->private_lock);
if (PageUptodate(page) || PageDirty(page)) {
bh = head;
do {/* 更新每一個緩沖頭的狀態 */
if (PageDirty(page))
set_buffer_dirty(bh);
if (PageUptodate(page))
set_buffer_uptodate(bh);
bh = bh->b_this_page;
} while (bh != head);
}
/* 將緩沖區關聯到頁面 */
attach_page_buffers(page, head);
spin_unlock(&page->mapping->private_lock);
}
復制代碼
create_empty_buffers調用alloc_page_buffers來創建一組buffer head鏈表,但還不是循環鏈表:
復制代碼
struct buffer_head *alloc_page_buffers(struct page *page, unsigned long size,
int retry)
{
struct buffer_head *bh, *head;
long offset;
try_again:
head = NULL;
offset = PAGE_SIZE;
while ((offset -= size) >= 0) {
bh = alloc_buffer_head(GFP_NOFS);
if (!bh)
goto no_grow;
bh->b_bdev = NULL;
bh->b_this_page = head;
bh->b_blocknr = -1;
head = bh;
bh->b_state = 0;
atomic_set(&bh->b_count, 0);
bh->b_private = NULL;
bh->b_size = size;
/* Link the buffer to its page */
set_bh_page(bh, page, offset);
init_buffer(bh, NULL, NULL);
}
return head;
......
}
復制代碼
alloc_page_buffers調用set_bh_page來設置b_data.
復制代碼
void set_bh_page(struct buffer_head *bh,
struct page *page, unsigned long offset)
{
bh->b_page = page;
BUG_ON(offset >= PAGE_SIZE);
if (PageHighMem(page))
/*
* This catches illegal uses and preserves the offset:
*/
bh->b_data = (char *)(0 + offset);
else
bh->b_data = page_address(page) + offset;
}
復制代碼
(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函數。
轉載于:https://www.cnblogs.com/chenliyang/p/6543165.html
總結
以上是生活随笔為你收集整理的page cache 与free的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaScript中this的指向问题
- 下一篇: 通过监测DLL调用探测Mimikatz