在Linux內核的struct page中,_count(或_refcount)和_mapcount是兩個關鍵的引用計數成員,它們各自承擔不同的職責。以下是深度解析和代碼案例:
?
1. _count vs _mapcount 區別詳解
?
_count(或_refcount)
?
特性 說明
?
全稱 atomic_t _refcount(新內核)或atomic_t _count(舊內核)
核心作用 頁面生命周期計數 - 表示內核持有該物理頁的"所有者"數量
觸發釋放條件 當計數值降為0時,頁面返回伙伴系統
操作接口 get_page(), put_page(), page_ref_count()
計數值含義 每增加1表示新增一個所有者(如頁緩存、匿名頁、設備映射等)
數值范圍 ≥0
?
作用:記錄物理頁的引用總數,用于判斷頁是否可以被釋放。
?
遞增場景:
?
頁被分配給進程(如malloc、fork)。
?
頁被內核臨時使用(如I/O緩沖)。
?
遞減場景:
?
進程釋放內存(如free、exit)。
?
內核不再需要該頁。
?
臨界值:
?
_count == 0:頁可以被回收。
?
_count > 0:頁正在被使用。
?
?
?
?
_mapcount
?
特性 說明
?
全稱 atomic_t _mapcount 或 int _mapcount
核心作用 頁表映射計數 - 表示頁面被映射到用戶空間頁表的次數
特殊值 -1: 未被用戶進程映射
0: 被1個進程映射
N: 被N+1個進程映射
操作接口 page_mapcount(), page_add_file_rmap(), page_remove_rmap()
計數范圍 -1 ~ 未限定上限
?
作用:記錄頁表映射的數量(即有多少個進程的頁表指向該物理頁)。
?
遞增場景:
?
頁被映射到進程的地址空間(如mmap、寫時復制)。
?
遞減場景:
?
進程取消映射(如munmap、exit)。
?
特殊值:
?
_mapcount == -1:頁未被任何進程映射(如僅內核使用)。
_mapcount == 0:頁被1個進程映射。
_mapcount > 0:頁被多個進程共享(如共享內存)。
?
?
2. 核心差異總結
?
維度 _refcount _mapcount
?
保護對象 物理頁面的生命周期 頁面在虛擬地址空間的映射關系
計數值=0 頁面可被回收 無實際意義(正常值為-1,0或正數)
增加場景 加入頁緩存、設備映射等 創建新頁表映射(mmap/mprotect)
減少場景 put_page()調用、文件刪除等 解除頁表映射(munmap)
用戶態影響 間接(決定物理頁存在) 直接(決定虛擬映射是否有效)
?
3.代碼案例:模擬寫時復制(COW)
?
以下是一個簡化版的內核模塊代碼,演示_count和_mapcount在寫時復制中的變化:
?
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/sched.h>
?
static void print_page_counts(struct page *page) {
? ? pr_info("_count = %d, _mapcount = %d\n",
? ? ? ? ? ? page_ref_count(page),
? ? ? ? ? ? page_mapcount(page));
}
?
static int __init cow_demo_init(void) {
? ? struct page *page;
? ? struct vm_area_struct *vma;
? ? unsigned long addr = current->mm->mmap->vm_start;
?
? ? // 1. 獲取用戶態地址對應的頁
? ? page = follow_page(current->mm->mmap, addr, FOLL_GET);
? ? if (!page) {
? ? ? ? pr_err("Failed to get page\n");
? ? ? ? return -EFAULT;
? ? }
?
? ? pr_info("Original page:\n");
? ? print_page_counts(page); // 初始狀態:_count=1, _mapcount=0
?
? ? // 2. 模擬fork():寫時復制前(共享映射)
? ? get_page(page); // _count++
? ? atomic_inc(&page->_mapcount); // _mapcount++
?
? ? pr_info("After fork (shared):\n");
? ? print_page_counts(page); // _count=2, _mapcount=1
?
? ? // 3. 模擬寫入觸發COW
? ? put_page(page); // 原進程釋放引用 _count--
? ? atomic_dec(&page->_mapcount); // 原進程取消映射 _mapcount--
?
? ? // 新分配COW頁
? ? struct page *new_page = alloc_page(GFP_KERNEL);
? ? copy_user_highpage(new_page, page, addr, current->mm->mmap);
?
? ? pr_info("After COW (new page):\n");
? ? print_page_counts(new_page); // _count=1, _mapcount=0
?
? ? // 清理
? ? __free_page(page);
? ? __free_page(new_page);
? ? return 0;
}
?
static void __exit cow_demo_exit(void) {
? ? pr_info("Module exited\n");
}
?
module_init(cow_demo_init);
module_exit(cow_demo_exit);
MODULE_LICENSE("GPL");
?
5. 代碼流程解析
?
初始狀態:
?
進程A映射一個頁:_count=1(A持有),_mapcount=0(未共享)。
fork()后:
?
進程B共享該頁:_count=2(A+B持有),_mapcount=1(1次映射)。
寫入觸發COW:
?
進程B復制新頁:原頁_count=1(A持有),_mapcount=0(B取消映射)。
新頁_count=1(B持有),_mapcount=0(新映射)。
?
6. 關鍵函數說明
?
page_ref_count(page):獲取_count值。
page_mapcount(page):獲取_mapcount值。
follow_page():通過虛擬地址獲取struct page。
copy_user_highpage():復制頁內容(COW核心操作)。
?
7. 實際運行輸出示例
?
Original page:
_count = 1, _mapcount = 0
After fork (shared):
_count = 2, _mapcount = 1
After COW (new page):
_count = 1, _mapcount = 0
?
8. 總結
?
_count是物理頁的全局引用計數,決定頁是否可回收。
_mapcount是映射計數,反映共享狀態(如COW、共享內存)。
兩者協同工作:即使_mapcount=0(無映射),若_count>0(內核仍在使用),頁也不能釋放。
?
9.性能優化與注意事項
?
原子操作開銷
?
_refcount使用atomic_t確保原子性
高并發場景下可能成為瓶頸,需盡量減少頻繁操作
引用循環問題
?
?
// 錯誤示例:導致永久引用
void set_page_private(struct page *page, void *data)
{
? ? get_page(page); // 增加額外引用
? ? page->private = data;
}
多類型頁面處理
?
// 處理復合頁(Compound Pages)
if (PageCompound(page)) {
? ? // 整個復合頁共享相同_refcount
? ? // 但每個子頁有獨立_mapcount
}
頁面遷移保護
?
// 遷移前檢查
if (page_mapcount(page) > 0 || page_ref_count(page) > 1) {
? ? return -EBUSY; // 頁面被使用中
}
?
10.調試工具
?
CONFIG_DEBUG_VM:檢測無效的計數操作
page_owner:追蹤頁面生命周期所有者
/proc/pagetypeinfo:查看頁面計數統計
通過正確理解_refcount和_mapcount的差異及其協同工作方式,開發者可以有效優化內存管理邏輯,避免常見錯誤如頁面泄露或過早釋放。