自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文看懂什么是頁(yè)緩存(Page Cache)

商務(wù)辦公
我們知道文件一般存放在硬盤(pán)(機(jī)械硬盤(pán)或固態(tài)硬盤(pán))中,CPU 并不能直接訪問(wèn)硬盤(pán)中的數(shù)據(jù),而是需要先將硬盤(pán)中的數(shù)據(jù)讀入到內(nèi)存中,然后才能被 CPU 訪問(wèn)。

[[409491]]

本文轉(zhuǎn)載自微信公眾號(hào)「Linux內(nèi)核那些事」,作者songsong001 。轉(zhuǎn)載本文請(qǐng)聯(lián)系Linux內(nèi)核那些事公眾號(hào)。

我們知道文件一般存放在硬盤(pán)(機(jī)械硬盤(pán)或固態(tài)硬盤(pán))中,CPU 并不能直接訪問(wèn)硬盤(pán)中的數(shù)據(jù),而是需要先將硬盤(pán)中的數(shù)據(jù)讀入到內(nèi)存中,然后才能被 CPU 訪問(wèn)。

由于讀寫(xiě)硬盤(pán)的速度比讀寫(xiě)內(nèi)存要慢很多(DDR4 內(nèi)存讀寫(xiě)速度是機(jī)械硬盤(pán)500倍,是固態(tài)硬盤(pán)的200倍),所以為了避免每次讀寫(xiě)文件時(shí),都需要對(duì)硬盤(pán)進(jìn)行讀寫(xiě)操作,Linux 內(nèi)核使用 頁(yè)緩存(Page Cache) 機(jī)制來(lái)對(duì)文件中的數(shù)據(jù)進(jìn)行緩存。

本文使用的 Linux 內(nèi)核版本為:Linux-2.6.23

什么是頁(yè)緩存

為了提升對(duì)文件的讀寫(xiě)效率,Linux 內(nèi)核會(huì)以頁(yè)大小(4KB)為單位,將文件劃分為多數(shù)據(jù)塊。當(dāng)用戶對(duì)文件中的某個(gè)數(shù)據(jù)塊進(jìn)行讀寫(xiě)操作時(shí),內(nèi)核首先會(huì)申請(qǐng)一個(gè)內(nèi)存頁(yè)(稱為 頁(yè)緩存)與文件中的數(shù)據(jù)塊進(jìn)行綁定。如下圖所示:

如上圖所示,當(dāng)用戶對(duì)文件進(jìn)行讀寫(xiě)時(shí),實(shí)際上是對(duì)文件的 頁(yè)緩存 進(jìn)行讀寫(xiě)。所以對(duì)文件進(jìn)行讀寫(xiě)操作時(shí),會(huì)分以下兩種情況進(jìn)行處理:

  • 當(dāng)從文件中讀取數(shù)據(jù)時(shí),如果要讀取的數(shù)據(jù)所在的頁(yè)緩存已經(jīng)存在,那么就直接把頁(yè)緩存的數(shù)據(jù)拷貝給用戶即可。否則,內(nèi)核首先會(huì)申請(qǐng)一個(gè)空閑的內(nèi)存頁(yè)(頁(yè)緩存),然后從文件中讀取數(shù)據(jù)到頁(yè)緩存,并且把頁(yè)緩存的數(shù)據(jù)拷貝給用戶。
  • 當(dāng)向文件中寫(xiě)入數(shù)據(jù)時(shí),如果要寫(xiě)入的數(shù)據(jù)所在的頁(yè)緩存已經(jīng)存在,那么直接把新數(shù)據(jù)寫(xiě)入到頁(yè)緩存即可。否則,內(nèi)核首先會(huì)申請(qǐng)一個(gè)空閑的內(nèi)存頁(yè)(頁(yè)緩存),然后從文件中讀取數(shù)據(jù)到頁(yè)緩存,并且把新數(shù)據(jù)寫(xiě)入到頁(yè)緩存中。對(duì)于被修改的頁(yè)緩存,內(nèi)核會(huì)定時(shí)把這些頁(yè)緩存刷新到文件中。

頁(yè)緩存的實(shí)現(xiàn)

前面主要介紹了頁(yè)緩存的作用和原理,接下來(lái)我們將會(huì)分析 Linux 內(nèi)核是怎么實(shí)現(xiàn)頁(yè)緩存機(jī)制的。

1. address_space

在 Linux 內(nèi)核中,使用 file 對(duì)象來(lái)描述一個(gè)被打開(kāi)的文件,其中有個(gè)名為 f_mapping 的字段,定義如下:

  1. struct file { 
  2.     ... 
  3.     struct address_space *f_mapping; 
  4. }; 

從上面代碼可以看出,f_mapping 字段的類型為 address_space 結(jié)構(gòu),其定義如下:

  1. struct address_space { 
  2.     struct inode           *host;      /* owner: inode, block_device */ 
  3.     struct radix_tree_root page_tree;  /* radix tree of all pages */ 
  4.     rwlock_t               tree_lock;  /* and rwlock protecting it */ 
  5.     ... 
  6. }; 

address_space 結(jié)構(gòu)其中的一個(gè)作用就是用于存儲(chǔ)文件的 頁(yè)緩存,下面介紹一下各個(gè)字段的作用:

  • host:指向當(dāng)前 address_space 對(duì)象所屬的文件 inode 對(duì)象(每個(gè)文件都使用一個(gè) inode 對(duì)象表示)。
  • page_tree:用于存儲(chǔ)當(dāng)前文件的 頁(yè)緩存。
  • tree_lock:用于防止并發(fā)訪問(wèn) page_tree 導(dǎo)致的資源競(jìng)爭(zhēng)問(wèn)題。

從 address_space 對(duì)象的定義可以看出,文件的 頁(yè)緩存 使用了 radix樹(shù) 來(lái)存儲(chǔ)。

radix樹(shù):又名基數(shù)樹(shù),它使用鍵值(key-value)對(duì)的形式來(lái)保存數(shù)據(jù),并且可以通過(guò)鍵快速查找到其對(duì)應(yīng)的值。內(nèi)核以文件讀寫(xiě)操作中的數(shù)據(jù) 偏移量 作為鍵,以數(shù)據(jù)偏移量所在的 頁(yè)緩存 作為值,存儲(chǔ)在 address_space 結(jié)構(gòu)的 page_tree 字段中。

下圖展示了上述各個(gè)結(jié)構(gòu)之間的關(guān)系:

如果對(duì) radix樹(shù) 不太了解,可以簡(jiǎn)單將其看成可以通過(guò)文件偏移量快速找到其所在 頁(yè)緩存 的結(jié)構(gòu),有機(jī)會(huì)我會(huì)另外寫(xiě)一篇關(guān)于 radix樹(shù) 的文章。

2. 讀文件操作

現(xiàn)在我們來(lái)分析一下讀取文件數(shù)據(jù)的過(guò)程,用戶可以通過(guò)調(diào)用 read 系統(tǒng)調(diào)用來(lái)讀取文件中的數(shù)據(jù),其調(diào)用鏈如下:

  1. read() 
  2. └→ sys_read() 
  3.    └→ vfs_read() 
  4.       └→ do_sync_read() 
  5.          └→ generic_file_aio_read() 
  6.             └→ do_generic_file_read() 
  7.                └→ do_generic_mapping_read() 

從上面的調(diào)用鏈可以看出,read 系統(tǒng)調(diào)用最終會(huì)調(diào)用 do_generic_mapping_read 函數(shù)來(lái)讀取文件中的數(shù)據(jù),其實(shí)現(xiàn)如下:

  1. void 
  2. do_generic_mapping_read(struct address_space *mapping, 
  3.                         struct file_ra_state *_ra, 
  4.                         struct file *filp, 
  5.                         loff_t *ppos, 
  6.                         read_descriptor_t *desc
  7.                         read_actor_t actor) 
  8.     struct inode *inode = mapping->host; 
  9.     unsigned long index
  10.     struct page *cached_page; 
  11.     ... 
  12.  
  13.     cached_page = NULL
  14.     index = *ppos >> PAGE_CACHE_SHIFT; 
  15.     ... 
  16.  
  17.     for (;;) { 
  18.         struct page *page; 
  19.         ... 
  20.  
  21. find_page: 
  22.         // 1. 查找文件偏移量所在的頁(yè)緩存是否存在 
  23.         page = find_get_page(mapping, index); 
  24.         if (!page) { 
  25.             ... 
  26.             // 2. 如果頁(yè)緩存不存在, 那么跳到 no_cached_page 進(jìn)行處理 
  27.             goto no_cached_page;  
  28.         } 
  29.         ... 
  30.  
  31. page_ok: 
  32.         ... 
  33.         // 3. 如果頁(yè)緩存存在, 那么把頁(yè)緩存的數(shù)據(jù)拷貝到用戶應(yīng)用程序的內(nèi)存中 
  34.         ret = actor(desc, page, offset, nr); 
  35.         ... 
  36.         if (ret == nr && desc->count
  37.             continue
  38.         goto out
  39.         ... 
  40.  
  41. readpage: 
  42.         // 4. 從文件讀取數(shù)據(jù)到頁(yè)緩存中 
  43.         error = mapping->a_ops->readpage(filp, page); 
  44.         ... 
  45.         goto page_ok; 
  46.         ... 
  47.  
  48. no_cached_page: 
  49.         if (!cached_page) { 
  50.             // 5. 申請(qǐng)一個(gè)內(nèi)存頁(yè)作為頁(yè)緩存 
  51.             cached_page = page_cache_alloc_cold(mapping); 
  52.             ... 
  53.         } 
  54.  
  55.         // 6. 把新申請(qǐng)的頁(yè)緩存添加到文件頁(yè)緩存中 
  56.         error = add_to_page_cache_lru(cached_page, mapping, index, GFP_KERNEL); 
  57.         ... 
  58.         page = cached_page; 
  59.         cached_page = NULL
  60.         goto readpage; 
  61.     } 
  62.  
  63. out
  64.     ... 

do_generic_mapping_read 函數(shù)的實(shí)現(xiàn)比較復(fù)雜,經(jīng)過(guò)精簡(jiǎn)后,上面代碼只留下最重要的邏輯,可以歸納為以下幾個(gè)步驟:

  • 通過(guò)調(diào)用 find_get_page 函數(shù)查找要讀取的文件偏移量所對(duì)應(yīng)的頁(yè)緩存是否存在,如果存在就把頁(yè)緩存中的數(shù)據(jù)拷貝到應(yīng)用程序的內(nèi)存中。
  • 否則調(diào)用 page_cache_alloc_cold 函數(shù)申請(qǐng)一個(gè)空閑的內(nèi)存頁(yè)作為新的頁(yè)緩存,并且通過(guò)調(diào)用 add_to_page_cache_lru 函數(shù)把新申請(qǐng)的頁(yè)緩存添加到文件頁(yè)緩存和 LRU 隊(duì)列中(后面會(huì)介紹)。
  • 通過(guò)調(diào)用 readpage 接口從文件中讀取數(shù)據(jù)到頁(yè)緩存中,并且把頁(yè)緩存的數(shù)據(jù)拷貝到應(yīng)用程序的內(nèi)存中。

從上面代碼可以看出,當(dāng)頁(yè)緩存不存在時(shí)會(huì)申請(qǐng)一塊空閑的內(nèi)存頁(yè)作為頁(yè)緩存,并且通過(guò)調(diào)用 add_to_page_cache_lru 函數(shù)把其添加到文件的頁(yè)緩存和 LRU 隊(duì)列中。我們來(lái)看看 add_to_page_cache_lru 函數(shù)的實(shí)現(xiàn):

  1.  int add_to_page_cache_lru(struct page *page, struct address_space *mapping, 
  2.                            pgoff_t offset, gfp_t gfp_mask) 
  3.     // 1. 把頁(yè)緩存添加到文件頁(yè)緩存中 
  4.     int ret = add_to_page_cache(page, mapping, offset, gfp_mask); 
  5.     if (ret == 0) 
  6.         lru_cache_add(page); // 2. 把頁(yè)緩存添加到 LRU 隊(duì)列中 
  7.     return ret; 

add_to_page_cache_lru 函數(shù)主要完成兩個(gè)工作:

  • 通過(guò)調(diào)用 add_to_page_cache 函數(shù)把頁(yè)緩存添加到文件頁(yè)緩存中,也就是添加到 address_space 結(jié)構(gòu)的 page_tree 字段中。
  • 通過(guò)調(diào)用 lru_cache_add 函數(shù)把頁(yè)緩存添加到 LRU 隊(duì)列中。LRU 隊(duì)列用于當(dāng)系統(tǒng)內(nèi)存不足時(shí),對(duì)頁(yè)緩存進(jìn)行清理時(shí)使用。

總結(jié) 

本文主要介紹了 頁(yè)緩存 的作用和原理,并且介紹了在讀取文件數(shù)據(jù)時(shí)對(duì)頁(yè)緩存的處理過(guò)程。本文并沒(méi)有介紹寫(xiě)文件操作對(duì)應(yīng)的頁(yè)緩存處理和當(dāng)系統(tǒng)內(nèi)存不足時(shí)怎么釋放頁(yè)緩存,有興趣的話可以自行閱讀相關(guān)的代碼實(shí)現(xiàn)。

 

責(zé)任編輯:武曉燕 來(lái)源: Linux內(nèi)核那些事
相關(guān)推薦

2021-02-21 11:25:17

云計(jì)算IaaSPaaS

2022-03-29 08:02:01

數(shù)字孿生能源程序

2021-02-08 22:23:16

云計(jì)算辦公硬件

2021-10-17 19:48:10

擴(kuò)展頁(yè)表虛擬機(jī)

2020-03-31 14:40:24

HashMap源碼Java

2016-08-18 00:21:12

網(wǎng)絡(luò)爬蟲(chóng)抓取網(wǎng)絡(luò)

2024-08-12 12:30:27

2025-01-20 09:15:00

iOS 18.3蘋(píng)果iOS 18

2021-08-02 06:56:19

TypeScript編程語(yǔ)言編譯器

2019-07-01 09:22:15

Linux操作系統(tǒng)硬件

2019-05-22 09:50:42

Python沙箱逃逸網(wǎng)絡(luò)攻擊

2022-09-23 15:07:32

東數(shù)西算數(shù)據(jù)中心IT

2018-09-28 14:06:25

前端緩存后端

2021-10-18 14:30:55

物聯(lián)網(wǎng)IOT

2021-05-11 10:40:29

JUCAQSJava

2021-05-12 15:16:17

JUCAQSJava

2023-12-18 10:45:31

2019-02-13 15:38:09

存儲(chǔ)虛擬化云計(jì)算

2022-04-26 13:41:16

區(qū)塊鏈比特幣數(shù)據(jù)庫(kù)

2025-03-25 09:06:11

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)