mmap核心原始碼分析
對於mmap函式,我之前的理解太單一了。這幾天好好複習了一下以前學過的知識,重新對該函式有了新的認識。
之前我的認識是,mmap是用來對映記憶體的,它對映的記憶體來自磁碟上檔案。所以我以為malloc函式底層也對映檔案記憶體。後來一直想不通。
實際上,mmap函式再malloc底層實現中採用了匿名對映(就是這個匿名對映,我之前一直概念不清)。
先說下malloc呼叫mmap一般的形式:
//原型
//mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
addr = mmap(NULL, 4096, PROT_READ|PORT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
對於malloc對映匿名記憶體來說,必須是以頁為單位的,比如上面的4096。使用者程式向核心空間分配記憶體都是直接向夥伴系統要的。在此基礎上glibc將記憶體細化為可以按照位元組分配的方式。
匿名記憶體的顯著特徵,MAP_ANONYMOUS,以及檔案描述符fd傳遞-1。
下面開始剖析原始碼,看看匿名記憶體與檔案對映有什麼不一樣。
mmap的系統呼叫時sys_mmap2,實際上就是一個簡單轉呼叫:
asmlinkage long sys_mmap2(unsigned long addr, unsigned long len,
unsigned long prot, unsigned long flags,
unsigned long fd, unsigned long pgoff)
{
return do_mmap2(addr, len, prot, flags, fd, pgoff);
}
do_mmap2,程式碼如下:‘
static inline long do_mmap2(
unsigned long addr, unsigned long len,
unsigned long prot, unsigned long flags,
unsigned long fd, unsigned long pgoff)
{
int error = -EBADF;
struct file * file = NULL; //呵呵,注意這裡file指標初始為NULL
flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);
if (!(flags & MAP_ANONYMOUS)) {//MAP_ANONYMOUS設成1,表示沒有檔案,實際上只是用來"圈地"
file = fget(fd);//獲取file結構
if (!file)
goto out;
}
//所以上面的步驟,如果我們設定了MAP_ANONYMOUS,那麼不會用fd獲取實際的檔案,以後file指標仍然為NULL
down(t->mm->mmap_sem);
error = do_mmap_pgoff(file, addr, len, prot, flags, pgoff); //傳入file=NULL
up(¤t->mm->mmap_sem);
if (file)
fput(file);
out:
return error;
}
inline函式do_mmap(),是供核心自己用的,它也是將已開啟檔案對映到當前程式空間。程式碼為:
static inline unsigned long do_mmap(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flag, unsigned long offset)
{
unsigned long ret = -EINVAL;
if ((offset + PAGE_ALIGN(len)) < offset)
goto out;
if (!(offset & ~PAGE_MASK))
ret = do_mmap_pgoff(file, addr, len, prot, flag, offset >> PAGE_SHIFT); //沒得說,file還是NULL
out:
return ret;
}
兩者都呼叫,do_mmap_pgoff,程式碼如下:
unsigned long do_mmap_pgoff(struct file * file, unsigned long addr, unsigned long len,
unsigned long prot, unsigned long flags, unsigned long pgoff)
{
struct mm_struct * mm = current->mm;
struct vm_area_struct * vma;
int correct_wcount = 0;
int error;
.....//各種判斷,先忽略
if (flags & MAP_FIXED) {
if (addr & ~PAGE_MASK)
return -EINVAL;
} else {//MAP_FIXED為0,就表示指定的對映地址只是一個參考值,不能滿足時可以由核心給分配一個
addr = get_unmapped_area(addr, len);//當前程式的使用者空間中分配一個起始地址
if (!addr)
return -ENOMEM;
}
/* Determine the object being mapped and call the appropriate
* specific mapper. the address has already been validated, but
* not unmapped, but the maps are removed from the list.
*/
vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);//對映到一個特定的檔案也是一種屬性,屬性不同的區段不能共存於同一邏輯區間,所以總要為之單獨建立一個邏輯區間
if (!vma)
return -ENOMEM;
vma->vm_mm = mm;
vma->vm_start = addr;//起始地址
vma->vm_end = addr + len;//結束地址
vma->vm_flags = vm_flags(prot,flags) | mm->def_flags;
if (file) {//設定vma->flags
VM_ClearReadHint(vma);
vma->vm_raend = 0;
if (file->f_mode & FMODE_READ)
vma->vm_flags |= VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
if (flags & MAP_SHARED) {
vma->vm_flags |= VM_SHARED | VM_MAYSHARE;
/* This looks strange, but when we don't have the file open
* for writing, we can demote the shared mapping to a simpler
* private mapping. That also takes care of a security hole
* with ptrace() writing to a shared mapping without write
* permissions.
*
* We leave the VM_MAYSHARE bit on, just to get correct output
* from /proc/xxx/maps..
*/
if (!(file->f_mode & FMODE_WRITE))
vma->vm_flags &= ~(VM_MAYWRITE | VM_SHARED);
}
} else {
vma->vm_flags |= VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
if (flags & MAP_SHARED)
vma->vm_flags |= VM_SHARED | VM_MAYSHARE;
}
vma->vm_page_prot = protection_map[vma->vm_flags & 0x0f];
vma->vm_ops = NULL;
vma->vm_pgoff = pgoff;//所對映內容在檔案中的起點,有了這個起點,發生缺頁異常時,就可以根據虛擬地址計算出相應頁面在檔案中的位置
vma->vm_file = NULL;
vma->vm_private_data = NULL;
/* Clear old maps */
error = -ENOMEM;
if (do_munmap(mm, addr, len))//檢查目標地址在當前程式的虛擬空間是否已經在使用,如果已經在使用就要將老的對映撤銷,要是這個操作失敗,則goto free_vma。因為flags的標誌位為MAP_FIXED為1時,並未對此檢查。
goto free_vma;
/* Check against address space limit. */
if ((mm->total_vm << PAGE_SHIFT) + len //虛擬空間的使用是否超出了為其設定的下限
> current->rlim[RLIMIT_AS].rlim_cur)
goto free_vma;
/* Private writable mapping? Check memory availability.. */
if ((vma->vm_flags & (VM_SHARED | VM_WRITE)) == VM_WRITE &&//物理頁面數是否夠
!(flags & MAP_NORESERVE) &&
!vm_enough_memory(len >> PAGE_SHIFT))
goto free_vma;
if (file) {
if (vma->vm_flags & VM_DENYWRITE) {
error = deny_write_access(file);//排斥常規檔案操作,如read write
if (error)
goto free_vma;
correct_wcount = 1;
}
vma->vm_file = file;//重點哦
get_file(file);
error = file->f_op->mmap(file, vma);//指向了generic_file_mmap
if (error)
goto unmap_and_free_vma;
} else if (flags & MAP_SHARED) {
error = shmem_zero_setup(vma);
if (error)
goto free_vma;
}
/* Can addr have changed??
*
* Answer: Yes, several device drivers can do it in their
* f_op->mmap method. -DaveM
*/
flags = vma->vm_flags;
addr = vma->vm_start;
insert_vm_struct(mm, vma);//插入到對應的佇列中
if (correct_wcount)
atomic_inc(&file->f_dentry->d_inode->i_writecount);
mm->total_vm += len >> PAGE_SHIFT;
if (flags & VM_LOCKED) {//僅在加鎖時才呼叫make_pages_present
mm->locked_vm += len >> PAGE_SHIFT;
make_pages_present(addr, addr + len);
}
return addr;//最後返回的起始虛擬地址,一般是後12位為0
unmap_and_free_vma:
if (correct_wcount)
atomic_inc(&file->f_dentry->d_inode->i_writecount);
vma->vm_file = NULL;
fput(file);
/* Undo any partial mapping done by a device driver. */
flush_cache_range(mm, vma->vm_start, vma->vm_end);
zap_page_range(mm, vma->vm_start, vma->vm_end - vma->vm_start);
flush_tlb_range(mm, vma->vm_start, vma->vm_end);
free_vma:
kmem_cache_free(vm_area_cachep, vma);
return error;
}
哈哈,我關注的重點來了,這個函式get_unmapped_area,是用來給程式找到一塊VMA的,來看看它幹了什麼:
unsigned long
get_unmapped_area(struct file *file, unsigned long addr, unsigned long len,
unsigned long pgoff, unsigned long flags)
{
unsigned long (*get_area)(struct file *, unsigned long,
unsigned long, unsigned long, unsigned long);
get_area = current->mm->get_unmapped_area; //預設使用當前程式虛存管理對應的get_unmapped_ared函式,這裡只是從程式地址空間獲得VMA
if (file && file->f_op && file->f_op->get_unmapped_area) //匿名對映檔案指標依舊為空
get_area = file->f_op->get_unmapped_area; //如果檔案指標不為NULL,使用檔案對應的get_unmapped_area函式,這裡就是從檔案獲得VMA
addr = get_area(file, addr, len, pgoff, flags);
if (IS_ERR_VALUE(addr))
return addr;
if (addr > TASK_SIZE - len)
return -ENOMEM;
if (addr & ~PAGE_MASK) //PAGE_MASK低12位都是0,這裡是用來檢測是否是整頁面,如果不是則出錯
return -EINVAL;
return arch_rebalance_pgtables(addr, len);
}
唉,真相大白。匿名對映就是file=NULL。
get_unmmaped_ared函式解開了我的疑惑,該函式實現的核心對於匿名檔案對映和檔案對映的選擇。使用函式指標,要麼賦值mm->get_unmapped_area從程式地址空間獲得VMA,要麼從file->f_op->get_unmapped_ared獲得VMA。
並沒有做實際的記憶體分配,只是簡單的獲取了一片VMA。獲取VMA是呼叫find_vma()函式在vma_ared_struct的雙連結串列中查詢,並且匿名對映還有可能和緊挨著的VMA合併。
當CPU第一個引用mmap區域的頁面時,會引發缺頁中斷,核心會在物理儲存器中找到一個合適的犧牲頁面,如果該頁面被修改過,就將這個頁面換出來,用二進位制零覆蓋犧牲頁面並更新頁表,將這個頁面標記為是駐留在儲存器中的。注意在磁碟和儲存器之間並沒有實際的資料傳送。因為這個原因,對映到匿名檔案的區域的頁面有時也叫做請求二進位制零的頁(demand zero page)。
另外,由於使用mmap分配記憶體,核心需要清零頁面,並且mmap分配的記憶體都是頁對齊的,所以使用mmap有一定的消耗。所以glibc才設定大於128k使用mmap,一般使用sbrk()函式分配記憶體。
參考:
- Linux核心原始碼情景分析-系統呼叫mmap()。
- Linux核心分析之程式地址空間。
- linux mmap函式詳解。
- CSAPP, Randal E.Byant David R.O.Hallaron著,龔奕利,雷迎春譯。
相關文章
- YARN 核心原始碼分析Yarn原始碼
- Spring原始碼分析——spring原始碼核心方法refresh()介紹Spring原始碼
- Laravel核心解讀–Cookie原始碼分析LaravelCookie原始碼
- Spring IOC容器核心流程原始碼分析Spring原始碼
- 從核心世界透視 mmap 記憶體對映的本質(原始碼實現篇)記憶體原始碼
- JVMTI Agent 工作原理及核心原始碼分析JVM原始碼
- JVMTI Attach機制與核心原始碼分析JVM原始碼
- workerman 框架原始碼核心分析和註解框架原始碼
- MyBatis原始碼分析之核心處理層MyBatis原始碼
- 鴻蒙輕核心原始碼分析:Newlib C鴻蒙原始碼
- [轉帖]Linux核心原始碼分析分享專題Linux原始碼
- Linux核心原始碼分析之set_arch (一)Linux原始碼
- iOS 開源庫系列 Aspects核心原始碼分析iOS原始碼
- Scrapy原始碼閱讀分析_3_核心元件原始碼元件
- LiteOS核心原始碼分析:任務LOS_Schedule原始碼
- Linux核心原始碼分析之setup_arch (四)Linux原始碼
- Linux核心原始碼分析之setup_arch (三)Linux原始碼
- Linux核心原始碼分析之setup_arch (二)Linux原始碼
- kube-scheduler原始碼分析(2)-核心處理邏輯分析原始碼
- external-attacher原始碼分析(2)-核心處理邏輯分析原始碼
- 併發程式設計之 SynchronousQueue 核心原始碼分析程式設計原始碼
- Guava Cache:核心引數深度剖析和原始碼分析Guava原始碼
- 鴻蒙輕核心原始碼分析:虛實對映鴻蒙原始碼
- Linux 核心排程器原始碼分析 - 初始化Linux原始碼
- Spring Security系列之核心過濾器原始碼分析(四)Spring過濾器原始碼
- 鴻蒙輕核心原始碼分析:檔案系統LittleFS鴻蒙原始碼
- 鴻蒙輕核心原始碼分析:虛擬記憶體鴻蒙原始碼記憶體
- Retrofit原始碼分析三 原始碼分析原始碼
- 集合原始碼分析[2]-AbstractList 原始碼分析原始碼
- 集合原始碼分析[3]-ArrayList 原始碼分析原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- 【JDK原始碼分析系列】ArrayBlockingQueue原始碼分析JDK原始碼BloC
- 集合原始碼分析[1]-Collection 原始碼分析原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- Linux 4.x MTD原始碼分析-核心資料結構Linux原始碼資料結構
- workerman 網路框架原始碼核心分析和註解 over 篇框架原始碼
- 以太坊原始碼分析(36)ethdb原始碼分析原始碼
- 以太坊原始碼分析(38)event原始碼分析原始碼
- 以太坊原始碼分析(41)hashimoto原始碼分析原始碼