《電子技術(shù)應(yīng)用》
您所在的位置:首頁 > 可編程邏輯 > 其他 > Linux教學(xué)——mmap實現(xiàn)詳解

Linux教學(xué)——mmap實現(xiàn)詳解

2022-09-29
作者:songsong001
來源:一口Linux
關(guān)鍵詞: Linux mmap 中斷處理

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

微信截圖_20220929173630.png

  想不到 mmap 都成了黑科技了,為了讓大家都能了解這個黑科技,所以還是寫篇文章來詳細介紹一下 mmap 的實現(xiàn)吧。

  其實,源碼分析是比較難寫的,主要有兩個原因:

  一方面是源碼實現(xiàn)一般會涉及多個知識點,所以在分析源碼時需要穿插多個知識點,從而增加分析的難度。另一方面是源碼實現(xiàn)會處理很多細節(jié)問題,這些細節(jié)問題雖然不是設(shè)計的主要框架,但忽略了有時會讓人摸不著頭腦。

  所以,為了降低分析的難度和讓讀者能夠更容易看懂,在分析源碼時更注重知識點的實現(xiàn),而在不影響理解的情況下,我會忽略一些細節(jié)問題。而對于穿插其他知識點的時候,會先跳過其實現(xiàn),并且在后續(xù)的文章對其進行分析。

  mmap 原理

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

  好消息是,這幾個子系統(tǒng)我們都有對應(yīng)的文章介紹過:

  內(nèi)存管理:Linux虛擬內(nèi)存空間管理》
       文件系統(tǒng):《 什么是頁緩存》
       中斷處理:《Linux中斷處理》

  在閱讀本文前,最好復(fù)習(xí)一下上面的文章。

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

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

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

微信截圖_20220929173816.png

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

  mmap 實現(xiàn)

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

  1. 文件映射

  當(dāng)我們使用 mmap() 系統(tǒng)調(diào)用對文件進行映射時,將會觸發(fā)調(diào)用 do_mmap_pgoff() 內(nèi)核函數(shù)來完成工作,我們來看看 do_mmap_pgoff() 函數(shù)的實現(xiàn)(經(jīng)過精簡后):

  unsigned long

  do_mmap_pgoff(struct file *file, unsigned long addr,

  unsigned long len, unsigned long prot,

  unsigned long flags, unsigned long pgoff)

  {

  ...

  // 1. 獲取一個未被使用的虛擬內(nèi)存區(qū)

  addr = get_unmapped_area(file, addr, len, pgoff, flags);

  if (addr & ~PAGE_MASK)

  return addr;

  ...

  // 2. 調(diào)用 mmap_region() 函數(shù)繼續(xù)進行映射操作

  return mmap_region(file, addr, len, flags, vm_flags, pgoff, accountable);

  }

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

  首先,調(diào)用 get_unmapped_area() 函數(shù)來獲取進程沒被使用的虛擬內(nèi)存區(qū),并且返回此內(nèi)存區(qū)的首地址。然后,調(diào)用 mmap_region() 函數(shù)繼續(xù)進行映射操作。

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

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

  我們繼續(xù)來看看 mmap_region() 函數(shù)的實現(xiàn),代碼如下(經(jīng)過精簡后):

  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. 申請一個虛擬內(nèi)存區(qū)管理結(jié)構(gòu)(vma)

  vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);

  ...

  // 2. 設(shè)置vma結(jié)構(gòu)各個字段的值

  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. 此處是內(nèi)存映射的關(guān)鍵點,調(diào)用文件對象的 mmap() 回調(diào)函數(shù)來設(shè)置vma結(jié)構(gòu)的 fault() 回調(diào)函數(shù)。

  *    vma對象的 fault() 回調(diào)函數(shù)的作用是:

  *        - 當(dāng)訪問的虛擬內(nèi)存沒有映射到物理內(nèi)存時,

  *        - 將會調(diào)用 fault() 回調(diào)函數(shù)對虛擬內(nèi)存地址映射到物理內(nèi)存地址。

  */

  error = file->f_op->mmap(file, vma);

  ...

  }

  ...

  // 4. 把 vma 結(jié)構(gòu)連接到進程虛擬內(nèi)存區(qū)的鏈表和紅黑樹中。

  vma_link(mm, vma, prev, rb_link, rb_parent);

  ...

  return addr;

  }

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

  申請一個 vm_area_struct 結(jié)構(gòu)(vma),內(nèi)核使用 vma 來管理進程的虛擬內(nèi)存地址,關(guān)于 vma 的詳細介紹可以參考:《Linux虛擬內(nèi)存空間管理》。設(shè)置 vma 結(jié)構(gòu)各個字段的值。通過調(diào)用文件對象的 mmap() 回調(diào)函數(shù)來設(shè)置vma結(jié)構(gòu)的 fault() 回調(diào)函數(shù),一般文件對象的 mmap() 回調(diào)函數(shù)為:generic_file_mmap()。把新創(chuàng)建的 vma 結(jié)構(gòu)連接到進程的虛擬內(nèi)存區(qū)鏈表和紅黑樹中。

  內(nèi)核使用 vm_area_struct 結(jié)構(gòu)來管理進程的虛擬內(nèi)存地址。當(dāng)進程需要使用內(nèi)存時,首先要向操作系統(tǒng)進行申請,操作系統(tǒng)會使用 vm_area_struct 結(jié)構(gòu)來記錄被分配出去的內(nèi)存區(qū)的大小、起始地址和權(quán)限等。

  我們來看看 vm_area_struct 結(jié)構(gòu)的定義:

  struct vm_area_struct {

  struct mm_struct *vm_mm;

  unsigned long vm_start;              // 內(nèi)存區(qū)的開始地址

  unsigned long vm_end;                // 內(nèi)存區(qū)的結(jié)束地址

  struct vm_area_struct *vm_next;      // 把進程所有已分配的內(nèi)存區(qū)鏈接起來

  pgprot_t vm_page_prot;               // 內(nèi)存區(qū)的權(quán)限

  ...

  struct rb_node vm_rb;                // 為了加快查找內(nèi)存區(qū)而建立的紅黑樹

  ...

  struct vm_operations_struct *vm_ops; // 內(nèi)存區(qū)的操作回調(diào)函數(shù)集

  unsigned long vm_pgoff;

  struct file *vm_file;                // 如果映射到文件,將指向映射的文件對象

  ...

  };

  struct vm_operations_struct {

  // 當(dāng)虛擬內(nèi)存區(qū)沒有映射到物理內(nèi)存地址時,將會觸發(fā)缺頁異常,

  // 而在缺頁異常處理函數(shù)中,將會調(diào)用此回調(diào)函數(shù)來對虛擬內(nèi)存映射到物理內(nèi)存。

  int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);

  ...

  };

  當(dāng)把文件映射到虛擬內(nèi)存空間時,需要把 vma 結(jié)構(gòu)的 vm_file 字段設(shè)置為要映射的文件對象,然后調(diào)用文件對象的 mmap() 回調(diào)函數(shù)來設(shè)置 vma 結(jié)構(gòu)的 fault() 回調(diào)函數(shù)。

  vma 結(jié)構(gòu)的 fault() 回調(diào)函數(shù)的作用是:當(dāng)虛擬內(nèi)存區(qū)沒有映射到物理內(nèi)存地址時,將會觸發(fā)缺頁異常。而在缺頁異常處理中,將會調(diào)用此回調(diào)函數(shù)來對虛擬內(nèi)存映射到物理內(nèi)存。

  我們來看看 generic_file_mmap() 函數(shù)是怎么設(shè)置 vma 結(jié)構(gòu)的 fault() 回調(diào)函數(shù)的:

  struct vm_operations_struct generic_file_vm_ops = {

  .fault = filemap_fault, // 將 fault() 回調(diào)函數(shù)設(shè)置為:filemap_fault()

  };

  int generic_file_mmap(struct file *file, struct vm_area_struct *vma)

  {

  ...

  vma->vm_ops = &generic_file_vm_ops;

  ...

  return 0;

  }

  至此,文件映射的過程已經(jīng)分析完畢。我們來看看其調(diào)用鏈:

  sys_mmap()

  └→ do_mmap_pgoff()

  └→ mmap_region()

  └→ generic_file_mmap()

  2. 缺頁異常

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

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

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

  但我們在 mmap() 系統(tǒng)調(diào)用的實現(xiàn)中,也沒看到將文件頁緩存映射到虛擬內(nèi)存空間。那么映射過程是在什么時候發(fā)生的呢?

  答案就是:缺頁異常。

  由于 mmap() 系統(tǒng)調(diào)用并沒有直接將文件的頁緩存映射到虛擬內(nèi)存中,所以當(dāng)訪問到?jīng)]有映射的虛擬內(nèi)存地址時,將會觸發(fā) 缺頁異常。當(dāng) CPU 觸發(fā)缺頁異常時,將會調(diào)用 do_page_fault() 函數(shù)來修復(fù)觸發(fā)異常的虛擬內(nèi)存地址。

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

  do_page_fault()

  └→ handle_mm_fault()

  └→ handle_pte_fault()

  └→ do_linear_fault()

  └→ __do_fault()

  所以我們直接來看看 __do_fault() 函數(shù)的實現(xiàn):

  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; // 要映射的虛擬內(nèi)存地址

  vmf.pgoff = pgoff;                         // 映射到文件的偏移量

  vmf.flags = flags;                         // 標志位

  vmf.page = NULL;                           // 映射到虛擬內(nèi)存中的物理內(nèi)存頁

  // 1. 如果虛擬內(nèi)存管理區(qū)提供了 falut() 回調(diào)函數(shù),那么將調(diào)用此函數(shù)來獲取要映射的物理內(nèi)存頁,

  //    我們在 mmap() 系統(tǒng)調(diào)用的實現(xiàn)中看到,已經(jīng)將其設(shè)置為 filemap_fault() 函數(shù)了。

  if (likely(vma->vm_ops->fault)) {

  ret = vma->vm_ops->fault(vma, &vmf);

  ...

  }

  ...

  if (likely(pte_same(*page_table, orig_pte))) {

  ...

  // 2. 通過物理內(nèi)存頁生成一個頁表項值(可以參考內(nèi)存映射一文)

  entry = mk_pte(page, vma->vm_page_prot);

  if (flags & FAULT_FLAG_WRITE)

  entry = maybe_mkwrite(pte_mkdirty(entry), vma);

  // 3. 將虛擬內(nèi)存地址映射到物理內(nèi)存(也就是將進程的頁表項設(shè)置為剛生成的頁表項的值)

  set_pte_at(mm, address, page_table, entry);

  ...

  }

  ...

  return ret;

  }

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

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

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

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

微信截圖_20220929174107.png

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

  

更多信息可以來這里獲取==>>電子技術(shù)應(yīng)用-AET<<

微信圖片_20210517164139.jpg

本站內(nèi)容除特別聲明的原創(chuàng)文章之外,轉(zhuǎn)載內(nèi)容只為傳遞更多信息,并不代表本網(wǎng)站贊同其觀點。轉(zhuǎn)載的所有的文章、圖片、音/視頻文件等資料的版權(quán)歸版權(quán)所有權(quán)人所有。本站采用的非本站原創(chuàng)文章及圖片等內(nèi)容無法一一聯(lián)系確認版權(quán)者。如涉及作品內(nèi)容、版權(quán)和其它問題,請及時通過電子郵件或電話通知我們,以便迅速采取適當(dāng)措施,避免給雙方造成不必要的經(jīng)濟損失。聯(lián)系電話:010-82306118;郵箱:aet@chinaaet.com。
亚洲一区二区欧美_亚洲丝袜一区_99re亚洲国产精品_日韩亚洲一区二区
欧美电影在线观看完整版| 国产精品你懂的在线| 欧美在线观看网站| 国产精品视频yy9099| 欧美日韩久久不卡| 欧美电影免费观看| 欧美gay视频激情| 美日韩精品免费| 美女诱惑一区| 欧美99久久| 欧美岛国在线观看| 欧美精品少妇一区二区三区| 欧美成人免费在线视频| 欧美成人r级一区二区三区| 免费不卡在线观看| 久色婷婷小香蕉久久| 久久综合一区二区| 蜜臀av国产精品久久久久| 美日韩精品免费观看视频| 欧美va亚洲va日韩∨a综合色| 免费看亚洲片| 欧美美女bb生活片| 欧美日韩国产一区| 欧美日韩在线视频一区| 国产精品国产三级国产a| 国产精品久久久久久久久久尿| 国产精品美女久久久久久久| 国产欧美精品xxxx另类| 国产午夜久久久久| 一区二区在线视频播放| 亚洲国产精品激情在线观看| 亚洲精品男同| 亚洲一区二区视频在线| 欧美亚洲专区| 亚洲经典在线看| 999在线观看精品免费不卡网站| 这里只有精品丝袜| 性色av一区二区三区| 久久精品国产精品亚洲精品| 男同欧美伦乱| 国产精品初高中精品久久| 国产精品人人做人人爽人人添| 国产亚洲欧美一区在线观看| 在线看无码的免费网站| 亚洲美女在线视频| 香蕉av福利精品导航| 亚洲国产欧美久久| 正在播放亚洲一区| 欧美在线精品一区| 嫩草国产精品入口| 欧美亚洲成人网| 精品福利电影| 中文欧美字幕免费| 亚洲第一区色| 亚洲综合三区| 蜜桃精品久久久久久久免费影院| 欧美日韩亚洲一区三区| 国产视频一区二区三区在线观看| 亚洲国产精品999| 亚洲曰本av电影| 日韩视频一区二区| 久久大逼视频| 欧美日韩国产欧美日美国产精品| 国产日韩欧美91| 亚洲国产专区| 欧美一区在线视频| 一区二区三区四区国产| 久久精品视频免费播放| 欧美日韩免费在线视频| 国产揄拍国内精品对白| 日韩视频精品| 亚洲高清视频一区| 亚洲欧洲av一区二区| 欧美激情精品久久久久久大尺度 | 国产伦精品一区二区三区高清| 亚洲国产成人精品久久久国产成人一区| 亚洲视频在线看| 日韩午夜三级在线| 久久人人97超碰国产公开结果| 欧美日韩综合不卡| 一区视频在线播放| 亚洲欧美日韩国产综合在线 | 91久久精品一区| 久久都是精品| 国产精品国产三级欧美二区| 亚洲国产老妈| 久久精品夜色噜噜亚洲a∨| 亚洲综合色噜噜狠狠| 欧美美女视频| 亚洲第一在线视频| 欧美在线一级va免费观看| 亚洲宅男天堂在线观看无病毒| 欧美成人视屏| 在线观看久久av| 久久国产一二区| 久久av老司机精品网站导航| 国产精品国产三级国产aⅴ无密码 国产精品国产三级国产aⅴ入口 | 狠狠色丁香久久综合频道| 亚洲欧美经典视频| 亚洲一区二区免费看| 男人的天堂成人在线| 好吊一区二区三区| 欧美在线综合视频| 久久精品一二三区| 国产欧美视频一区二区| 亚洲午夜激情| 亚洲一区免费观看| 欧美揉bbbbb揉bbbbb| 最新亚洲视频| 亚洲免费电影在线| 欧美高清视频一区二区| 亚洲第一在线综合在线| 亚洲人成网站精品片在线观看 | 国产精品五区| 亚洲天堂黄色| 羞羞视频在线观看欧美| 国产精品久久久久av| 亚洲一二三四区| 欧美在线短视频| 国产视频在线观看一区二区| 欧美亚洲免费在线| 久久精品国产亚洲高清剧情介绍| 国产视频久久网| 久久精品视频免费观看| 久久久www免费人成黑人精品 | 欧美日韩日本视频| 一本不卡影院| 午夜精品福利视频| 国产视频久久久久久久| 欧美综合二区| 欧美91福利在线观看| 亚洲精品一区二| 亚洲一级黄色| 国产精品网曝门| 久久成人资源| 欧美国产日韩一区二区| 亚洲精品在线观看视频| 亚洲一区二区三区免费在线观看| 国产精品久久久久国产a级| 亚洲免费伊人电影在线观看av| 欧美在线短视频| 1769国内精品视频在线播放| 99精品国产福利在线观看免费 | 国内精品嫩模av私拍在线观看| 亚洲二区在线| 欧美日韩精品免费观看视频完整| 99re亚洲国产精品| 欧美亚洲免费电影| 国产原创一区二区| 亚洲精品免费在线观看| 欧美日韩亚洲另类| 亚洲欧美中文字幕| 欧美成人乱码一区二区三区| 99这里只有久久精品视频| 性欧美超级视频| 亚洲丁香婷深爱综合| 亚洲午夜激情网站| 国产一区香蕉久久| 日韩天堂在线观看| 国产欧美一区二区精品性| 亚洲黄色免费网站| 国产精品久久9| 亚洲电影在线看| 欧美色区777第一页| 欧美一区二区日韩一区二区| 欧美顶级少妇做爰| 亚洲欧美春色| 欧美剧在线观看| 午夜精品偷拍| 欧美伦理在线观看| 久久av免费一区| 欧美日韩免费观看一区| 欧美一区二区网站| 欧美精品一区视频| 亚洲欧美在线一区二区| 欧美激情在线| 欧美在线三级| 欧美日韩亚洲一区在线观看| 久久精品夜色噜噜亚洲a∨| 欧美偷拍一区二区| 亚洲国产精品va在线看黑人动漫 | 欧美人与禽性xxxxx杂性| 欧美亚洲在线视频| 欧美日韩一区二区视频在线 | 一区二区三区精品视频| 麻豆精品在线播放| 亚洲一区视频在线| 欧美高清视频一区| 欧美在线资源| 国产精品美女久久久浪潮软件| 亚洲激情在线激情| 国产欧美一区二区三区国产幕精品| 亚洲精品久久久久久下一站| 国产女优一区| 亚洲制服丝袜在线| 91久久嫩草影院一区二区| 久久免费观看视频| 亚洲无限av看| 欧美人成免费网站| 亚洲人午夜精品|