色偷偷91综合久久噜噜-色偷偷成人-色偷偷尼玛图亚洲综合-色偷偷人人澡久久天天-国内精品视频一区-国内精品视频一区二区三区

Hello! 歡迎來到小浪云!


Linux黑科技|mmap實現詳解


avatar
小浪云 2025-01-03 159

故事的開始是這樣的,某天在脈脈上看到有人發了下面的帖子:

Linux黑科技|mmap實現詳解

mmap 原理

在之前的文章中,我們也介紹過 mmap 的原理,比如這篇:《原來 mmap 這么簡單》。當然這篇文章只是簡單介紹了 mmap 的原理,但是 mmap 的實現遠不止那么簡單,這是因為 mmap 涉及多個子系統,如:內存管理、文件系統、中斷處理等。

好消息是,這幾個子系統我們都有對應的文章介紹過:

  • 內存管理:《Linux虛擬內存空間管理》
  • 文件系統:《 什么是頁緩存》
  • 中斷處理:《Linux中斷處理》

在閱讀本文前,最好復習一下上面的文章。

雖然在《原來 mmap 這么簡單》一文中,我們簡單介紹過 mmap 的原理。但為了方便分析源碼,下面還是簡單回顧一下 mmap 的原理吧。

mmap 的全稱是 memory map,中文意思是 內存映射。其用途是將文件映射到內存中,然后可以通過對映射區的內存進行讀寫操作,其效果等同于對文件進行讀寫操作。

下面我們通過一幅圖來對 mmap 的原理進行闡述:

Linux黑科技|mmap實現詳解

從上圖可以看出,mmap 的原理就是將虛擬內存空間映射到文件的頁緩存,在《什么是頁緩存》一文中可知,對文件進行讀寫時需要經過頁緩存進行中轉的。所以當虛擬內存地址映射到文件的頁緩存后,就可以直接通過讀寫映射區內存來對文件進行讀寫操作。

mmap 實現

在分析 mmap 的實現前,最好先了解其使用方式,mmap 的使用可以參考《原來 mmap 這么簡單》這篇文章。

1. 文件映射

當我們使用 mmap() 系統調用對文件進行映射時,將會觸發調用 do_mmap_pgoff() 內核函數來完成工作,我們來看看 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) { ????... ????//?1.?獲取一個未被使用的虛擬內存區 ????addr?=?get_unmapped_area(file,?addr,?len,?pgoff,?flags); ????if?(addr?&?~PAGE_MASK) ????????return?addr;  ????... ????//?2.?調用?mmap_region()?函數繼續進行映射操作 ????return?mmap_region(file,?addr,?len,?flags,?vm_flags,?pgoff,?accountable); } 

經過精簡后的 do_mmap_pgoff() 函數主要完成 2 個工作:

  • 首先,調用 get_unmapped_area() 函數來獲取進程沒被使用的虛擬內存區,并且返回此內存區的首地址。
  • 然后,調用 mmap_region() 函數繼續進行映射操作。

?

在 32 位的操作系統中,每個進程都有 4GB 的虛擬內存空間,應用程序在使用內存前,需要先向操作系統發起申請內存的操作。操作系統會從進程的虛擬內存空間中查找未被使用的內存地址,并且返回給應用程序。

操作系統會記錄進程正在使用中的虛擬內存地址,如果內存地址沒被登記,說明此內存地址是空閑的(未被使用)。

我們繼續來看看 mmap_region() 函數的實現,代碼如下(經過精簡后):

unsigned?long mmap_region(struct?file?*file,?unsigned?long?addr, ????????????unsigned?long?len,?unsigned?long?flags, ????????????unsigned?int?vm_flags,?unsigned?long?pgoff, ????????????int?accountable) { ????struct?mm_struct?*mm?=?current->mm; ????struct?vm_area_struct?*vma,?*prev; ????int?correct_wcount?=?0; ????int?error; ????...  ????//?1.?申請一個虛擬內存區管理結構(vma) ????vma?=?kmem_cache_zalloc(vm_area_cachep,?GFP_KERNEL); ????...  ????//?2.?設置vma結構各個字段的值 ????vma->vm_mm?=?mm; ????vma->vm_start?=?addr; ????vma->vm_end?=?addr?+?len; ????vma->vm_flags?=?vm_flags; ????vma->vm_page_prot?=?protection_map[vm_flags?&?(VM_READ|VM_WRITE|VM_EXEC|VM_SHARED)]; ????vma->vm_pgoff?=?pgoff;  ????if?(file)?{ ????????... ????????vma->vm_file?=?file;  ????????/*?3.?此處是內存映射的關鍵點,調用文件對象的?mmap()?回調函數來設置vma結構的?fault()?回調函數。 ?????????*????vma對象的?fault()?回調函數的作用是: ?????????*????????-?當訪問的虛擬內存沒有映射到物理內存時, ?????????*????????-?將會調用?fault()?回調函數對虛擬內存地址映射到物理內存地址。 ?????????*/ ????????error?=?file->f_op->mmap(file,?vma); ????????... ????} ????...  ????//?4.?把?vma?結構連接到進程虛擬內存區的鏈表和紅黑樹中。 ????vma_link(mm,?vma,?prev,?rb_link,?rb_parent); ????...  ????return?addr; } 

mmap_region() 函數主要完成以下 4 件事情:

  • 申請一個 vm_area_struct 結構(vma),內核使用 vma 來管理進程的虛擬內存地址,關于 vma 的詳細介紹可以參考:《Linux虛擬內存空間管理》。
  • 設置 vma 結構各個字段的值。
  • 通過調用文件對象的 mmap() 回調函數來設置vma結構的 fault() 回調函數,一般文件對象的 mmap() 回調函數為:generic_file_mmap()。
  • 把新創建的 vma 結構連接到進程的虛擬內存區鏈表和紅黑樹中。

內核使用 vm_area_struct 結構來管理進程的虛擬內存地址。當進程需要使用內存時,首先要向操作系統進行申請,操作系統會使用 vm_area_struct 結構來記錄被分配出去的內存區的大小、起始地址和權限等。

我們來看看 vm_area_struct 結構的定義:

struct?vm_area_struct?{ ????struct?mm_struct?*vm_mm; ????unsigned?long?vm_start;??????????????//?內存區的開始地址 ????unsigned?long?vm_end;????????????????//?內存區的結束地址 ????struct?vm_area_struct?*vm_next;??????//?把進程所有已分配的內存區鏈接起來 ????pgprot_t?vm_page_prot;???????????????//?內存區的權限 ????... ????struct?rb_node?vm_rb;????????????????//?為了加快查找內存區而建立的紅黑樹 ????... ????struct?vm_operations_struct?*vm_ops;?//?內存區的操作回調函數集  ????unsigned?long?vm_pgoff; ????struct?file?*vm_file;????????????????//?如果映射到文件,將指向映射的文件對象 ????... };  struct?vm_operations_struct?{ ????//?當虛擬內存區沒有映射到物理內存地址時,將會觸發缺頁異常, ????//?而在缺頁異常處理函數中,將會調用此回調函數來對虛擬內存映射到物理內存。 ????int?(*fault)(struct?vm_area_struct?*vma,?struct?vm_fault?*vmf); ????... }; 

當把文件映射到虛擬內存空間時,需要把 vma 結構的 vm_file 字段設置為要映射的文件對象,然后調用文件對象的 mmap() 回調函數來設置 vma 結構的 fault() 回調函數。

?

vma 結構的 fault() 回調函數的作用是:當虛擬內存區沒有映射到物理內存地址時,將會觸發缺頁異常。而在缺頁異常處理中,將會調用此回調函數來對虛擬內存映射到物理內存。

我們來看看 generic_file_mmap() 函數是怎么設置 vma 結構的 fault() 回調函數的:

struct?vm_operations_struct?generic_file_vm_ops?=?{ ????.fault?=?filemap_fault,?//?將?fault()?回調函數設置為:filemap_fault() };  int?generic_file_mmap(struct?file?*file,?struct?vm_area_struct?*vma) { ????... ????vma->vm_ops?=?&generic_file_vm_ops; ????... ????return?0; } 

至此,文件映射的過程已經分析完畢。我們來看看其調用鏈:

sys_mmap() └→?do_mmap_pgoff() ???└→?mmap_region() ??????└→?generic_file_mmap() 

2. 缺頁異常

前面介紹了 mmap() 系統調用的處理過程,可以發現 mmap() 只是將 vma 的 vm_file 字段設置為被映射的文件對象,并且將 vma 的 fault() 回調函數設置為 filemap_fault()。也就是說,mmap() 系統調用并沒有對虛擬內存進行任何的映射操作。

我們在《漫畫解說 “內存映射”》一文中介紹過,虛擬內存必須映射到物理內存才能使用。如果訪問沒有映射到物理內存的虛擬內存地址,CPU 將會觸發缺頁異常。也就是說,虛擬內存并不能直接映射到磁盤中的文件。

那么 mmap() 是怎么將文件映射到虛擬內存中呢?我們在《 什么是頁緩存》一文中介紹過,讀寫文件時并不是直接對磁盤上的文件進行操作的,而是通過 頁緩存 作為中轉的,而頁緩存就是物理內存中的內存頁。所以,mmap() 可以通過將文件的頁緩存映射到虛擬內存空間來實現對文件的映射。

但我們在 mmap() 系統調用的實現中,也沒看到將文件頁緩存映射到虛擬內存空間。那么映射過程是在什么時候發生的呢?

?

答案就是:缺頁異常。

由于 mmap() 系統調用并沒有直接將文件的頁緩存映射到虛擬內存中,所以當訪問到沒有映射的虛擬內存地址時,將會觸發 缺頁異常。當 CPU 觸發缺頁異常時,將會調用 do_page_fault() 函數來修復觸發異常的虛擬內存地址。

我們主要來看看 do_page_fault() 函數對文件映射的實現部分,其調用鏈如下:

do_page_fault() └→?handle_mm_fault() ???└→?handle_pte_fault() ??????└→?do_linear_fault() ?????????└→?__do_fault() 

所以我們直接來看看 __do_fault() 函數的實現:

static?int __do_fault(struct?mm_struct?*mm,?struct?vm_area_struct?*vma, ???????????unsigned?long?address,?pmd_t?*pmd,?pgoff_t?pgoff, ???????????unsigned?int?flags,?pte_t?orig_pte) { ????... ????vmf.virtual_address?=?address?&?PAGE_MASK;?//?要映射的虛擬內存地址 ????vmf.pgoff?=?pgoff;?????????????????????????//?映射到文件的偏移量 ????vmf.flags?=?flags;?????????????????????????//?標志位 ????vmf.page?=?NULL;???????????????????????????//?映射到虛擬內存中的物理內存頁  ????//?1.?如果虛擬內存管理區提供了?falut()?回調函數,那么將調用此函數來獲取要映射的物理內存頁, ????//????我們在?mmap()?系統調用的實現中看到,已經將其設置為?filemap_fault()?函數了。 ????if?(likely(vma->vm_ops->fault))?{ ????????ret?=?vma->vm_ops->fault(vma,?&vmf); ????????... ????} ????...  ????if?(likely(pte_same(*page_table,?orig_pte)))?{ ????????... ????????//?2.?通過物理內存頁生成一個頁表項值(可以參考內存映射一文) ????????entry?=?mk_pte(page,?vma->vm_page_prot); ????????if?(flags?&?FAULT_FLAG_WRITE) ????????????entry?=?maybe_mkwrite(pte_mkdirty(entry),?vma);  ????????//?3.?將虛擬內存地址映射到物理內存(也就是將進程的頁表項設置為剛生成的頁表項的值) ????????set_pte_at(mm,?address,?page_table,?entry); ????????... ????} ????...  ????return?ret; } 

__do_fault() 函數對處理文件映射部分主要分為 3 個步驟:

  • 調用虛擬內存管理區結構(vma)的 fault() 回調函數(也就是 filemap_fault() 函數)來獲取到文件的頁緩存。
  • 通過頁緩存的物理內存頁來生成一個頁表項值,可以參考《漫畫解說 “內存映射”》一文。
  • 將虛擬內存地址映射到頁緩存的物理內存頁(也就是將進程的頁表項設置為上面生成的頁表項的值)。

對于 filemap_fault() 函數是怎樣讀取文件頁緩存的,本文不作解釋,有興趣的可以自行閱讀源碼。

最后,我們以一幅圖來描述一下虛擬內存是如何與文件進行映射的:

從上圖可以看出,mmap() 是通過將虛擬內存地址映射到文件的頁緩存來實現的。當對映射后的虛擬內存進行讀寫操作時,其效果等價于直接對文件的頁緩存進行讀寫操作。對文件的頁緩存進行讀寫操作,也等價于對文件進行讀寫操作。

相關閱讀

主站蜘蛛池模板: 一本久道综合久久精品 | 日韩美毛片 | 国产欧美在线视频免费 | 久久久精品国产 | 国产xxxx做受欧美88xx00tube | 一级片国产| 色婷婷在线影院 | 日韩视频免费在线观看 | 四虎永久在线精品免费影视 | 久久99这里只有精品国产 | 国产成人一区二区视频在线观看 | 色播五月综合 | 免费四虎永久在线精品 | 国产毛片网 | 免费一级毛片女人图片 | 色1看片网| 精品三级久久 | 天天操天天舔天天射 | 亚洲男人天堂a | 久久er99| 婷婷综合五月 | 色偷偷亚洲女性天堂 | 玖玖国产精品视频 | 一级毛片在线播放免费 | 人人澡人人澡人人看 | 久久精品国产99久久香蕉 | 成年视频在线播放 | 狠狠干夜夜 | 性满足久久久久久久久 | 国产欧美在线观看不卡 | 国产一区二区在线播放 | 天天摸天天干 | 九色国产 | 五月激情丁香婷婷综合第九 | 欧洲真实呦女网站 | 成人全黄三级视频在线观看 | 国产清纯白嫩大学生正在播放 | 日韩伦理片免费观看 | 亚洲视频在线网站 | 一区二区日韩 | 久久久久久久国产精品 |