1. 程式人生 > >普通檔案和塊裝置檔案的page cache問題

普通檔案和塊裝置檔案的page cache問題

注:本文程式碼基於linux-3.18.31,此版本中塊快取已經合入頁快取。

寫在前面

普通檔案和塊裝置檔案都有一個address_space物件,inode中分別維護兩個成員指向這兩個不同的address_space,分別是i_mapping和i_data。需要注意的是,這兩個page cache中可能擁有磁碟上相同的資料(即擁有相同的頁資料)。

struct inode {
	…………
	const struct inode_operations	*i_op;
	struct super_block	*i_sb;
	struct address_space	*i_mapping;  //對應於普通檔案的頁快取
………… struct address_space i_data; //對應於塊裝置檔案的頁快取 struct list_head i_devices; union { struct pipe_inode_info *i_pipe; struct block_device *i_bdev; //塊裝置inode對應的塊裝置描述符 struct cdev *i_cdev; char *i_link; }; };

普通檔案的address space

檔案系統讀取檔案一般會使用do_generic_file_read(),mapping指向普通檔案的address space。如果一個檔案的某一塊不在page cache中,在find_get_page函式中會建立一個page,並將這個page根據index插入到這個普通檔案的address space中。這也是我們熟知的過程。

static ssize_t do_generic_file_read(struct file *filp, loff_t *ppos,
        struct iov_iter *iter, ssize_t written)
{
    struct address_space *mapping = filp->f_mapping;
    struct inode *inode = mapping->host;
    struct file_ra_state *ra = &filp->f_ra;
    pgoff_t index;
    pgoff_t last_index;
pgoff_t prev_index; unsigned long offset; /* offset into pagecache page */ unsigned int prev_offset; int error = 0; index = *ppos >> PAGE_CACHE_SHIFT; prev_index = ra->prev_pos >> PAGE_CACHE_SHIFT; prev_offset = ra->prev_pos & (PAGE_CACHE_SIZE-1); last_index = (*ppos + iter->count + PAGE_CACHE_SIZE-1) >> PAGE_CACHE_SHIFT; offset = *ppos & ~PAGE_CACHE_MASK; for (;;) { struct page *page; pgoff_t end_index; loff_t isize; unsigned long nr, ret; cond_resched(); find_page: page = find_get_page(mapping, index); if (!page) { page_cache_sync_readahead(mapping, ra, filp, index, last_index - index); page = find_get_page(mapping, index); if (unlikely(page == NULL)) goto no_cached_page; } ......//此處省略約200行 }

塊裝置的address space

但是在讀取檔案系統元資料的時候,元資料對應的page會被加入到底層裸塊裝置的address space中。下面程式碼的bdev_mapping指向塊裝置的address space,呼叫find_get_page_flags()後,一個新的page(如果page不在這個塊裝置的address space)就被建立並且插入到這個塊裝置的address space。

static struct buffer_head *
__find_get_block_slow(struct block_device *bdev, sector_t block)
{
    struct inode *bd_inode = bdev->bd_inode;
    struct address_space *bd_mapping = bd_inode->i_mapping;
    struct buffer_head *ret = NULL;
    pgoff_t index;
    struct buffer_head *bh;
    struct buffer_head *head;
    struct page *page;
    int all_mapped = 1;
 
    index = block >> (PAGE_CACHE_SHIFT - bd_inode->i_blkbits);
    page = find_get_page_flags(bd_mapping, index, FGP_ACCESSED);
    if (!page)
        goto out;
    ......//此處省略幾十行
}

兩份快取?

前面提到的情況是正常的操作流程,屬於普通檔案的page放在檔案的address space,元資料對應的page放在塊裝置的address space中,大家井水不犯河水,和平共處。但是世事難料,總有一些不按套路出牌的傢伙。檔案系統在塊裝置上歡快的跑著,如果有人繞過檔案系統,直接去操作塊裝置上屬於檔案的資料塊,這會出現什麼情況?如果這個資料塊已經在普通檔案的address space中,這次直接的資料塊修改能夠立馬體現到普通檔案的快取中嗎?

答案是直接修改塊裝置上塊會新建一個對應這個塊的page,並且這個page會被加到塊裝置的address space中。也就是同一個資料塊,在其所屬的普通檔案的address space中有一個對應的page。同時,在這個塊裝置的address space中也會有一個與其對應的page,所有的修改都更新到這個塊裝置address space中的page上。除非重新從磁碟上讀取這一塊的資料,否則普通檔案的檔案快取並不會感知這一修改。

實驗

口說無憑,實踐是檢驗真理的唯一標準。我在這裡準備了一個實驗,先將一個檔案的資料全部載入到page cache中,然後直接操作塊裝置修改這個檔案的資料塊,再讀取檔案的內容,看看有沒有被修改。

為了確認一個檔案的資料是否在page cache中,我先介紹一個有趣的工具—vmtouch,這個工具可以顯示出一個檔案有多少內容已經被載入到page cache。大家可以在github上獲取到它的原始碼,並自行編譯安裝

現在開始我們的表演:

首先,我們找一個測試檔案,就拿我家目錄下的read.c來測試,這個檔案的內容就是一些凌亂的c程式碼。

➜ ~ cat read.c

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
 
char buf[4096] = {0};
 
int main(int argc, char *argv[])
{
	int fd;
	if (argc != 2) {
		printf("argument error.\n");
		return -1;
	}
 
	fd = open(argv[1], O_RDONLY);
	if (fd < 0) {
		perror("open failed:");
		return -1;
	}
 
	read(fd, buf, 4096);
	//read(fd, buf, 4096);
	close(fd);
}
➜  ~ 

接著執行vmtouch,看看這個檔案是否在page cache中了,由於這個檔案剛才被讀取過,所以檔案已經全部儲存在page cache中了。

➜  ~ vmtouch read.c                   
           Files: 1
     Directories: 0
  Resident Pages: 1/1  4K/4K  100%
         Elapsed: 0.000133 seconds
➜  ~ 

然後我通過debugfs找到read.c的資料塊,並且通過dd命令直接修改資料塊。

Inode: 3945394   Type: regular    Mode:  0644   Flags: 0x80000
Generation: 659328746    Version: 0x00000000:00000001
User:     0   Group:     0   Project:     0   Size: 386
File ACL: 0
Links: 1   Blockcount: 8
Fragment:  Address: 0    Number: 0    Size: 0
 ctime: 0x5ad2f108:60154d80 -- Sun Apr 15 14:28:24 2018
 atime: 0x5ad2f108:5db2f37c -- Sun Apr 15 14:28:24 2018
 mtime: 0x5ad2f108:5db2f37c -- Sun Apr 15 14:28:24 2018
crtime: 0x5ad2f108:5db2f37c -- Sun Apr 15 14:28:24 2018
Size of extra inode fields: 32
EXTENTS:
(0):2681460
 
➜  ~ dd if=/dev/zero of=/dev/sda2 seek=2681460 bs=4096 count=1 
1+0 records in
1+0 records out
4096 bytes (4.1 kB, 4.0 KiB) copied, 0.000323738 s, 12.7 MB/s

修改已經完成,我們看看直接讀取這個檔案會怎麼樣。

➜  ~ cat read.c 
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
 
char buf[4096] = {0};
 
int main(int argc, char *argv[])
{
	int fd;
	if (argc != 2) {
		printf("argument error.\n");
		return -1;
	}
 
	fd = open(argv[1], O_RDONLY);
	if (fd < 0) {
		perror("open failed:");
		return -1;
	}
 
	read(fd, buf, 4096);
	//read(fd, buf, 4096);
	close(fd);
}
 
➜  ~ vmtouch read.c 
           Files: 1
     Directories: 0
  Resident Pages: 1/1  4K/4K  100%
         Elapsed: 0.00013 seconds

檔案依然在page cache中,所以我們還是能夠讀取到檔案的內容。然而當我們drop cache以後,再讀取這個檔案,會發現檔案內容被清空。

➜  ~ vmtouch read.c 
           Files: 1
     Directories: 0
  Resident Pages: 1/1  4K/4K  100%
         Elapsed: 0.00013 seconds
➜  ~ echo 3 > /proc/sys/vm/drop_caches                        
➜  ~ vmtouch read.c                   
           Files: 1
     Directories: 0
  Resident Pages: 0/1  0/4K  0%
         Elapsed: 0.000679 seconds
➜  ~ cat read.c
➜  ~ 

總結

普通檔案的資料可以儲存在它的地址空間中,同時直接訪問塊裝置中此檔案的塊,也會將這個檔案的資料儲存在塊裝置的地址空間中。這兩份快取相互獨立,kernel並不會為這種非正常訪問同步兩份快取,從而避免了同步的開銷。

相關推薦

普通檔案裝置檔案page cache問題

注:本文程式碼基於linux-3.18.31,此版本中塊快取已經合入頁快取。 寫在前面 普通檔案和塊裝置檔案都有一個address_space物件,inode中分別維護兩個成員指向這兩個不同的address_space,分別是i_mapping和i_data。需

關於字元裝置檔案裝置檔案的區別

這 兩種型別的裝置的根本區別在於它們是否可以被隨機訪問——換句話說就是,能否在訪問裝置時隨意地從一個位置跳轉到另一個位置。舉個例子,鍵盤這種裝置提供 的就是一個數據流,當你敲入“fox”這個字串時,鍵盤驅動程式會按照和輸入完全相同的順序返回這個由三個字元組成的資料流。如果讓鍵盤驅動程式打亂順 序來讀字串,或

C檔案包含.h檔案包含.c檔案總結

原文連結:http://blog.csdn.net/yangtalent1206/article/details/6830051        很多人對C語言中的 “檔案包含”都不陌生了,檔案包含處理在程式開發中會給我們的模組化程式設計帶來很大的好處

mysql資料庫表結構定義檔案儲存引擎檔案

目錄 1.表結構定義檔案:      2.儲存引擎檔案 2.1表空間檔案 2.2重做日誌檔案 3. 如何用frm檔案恢復資料庫結構 1.表結構定義檔案:      *.frm 檔案是所有m

VC 6 0中新增庫檔案標頭檔案

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

一分鐘學會讀csv檔案寫csv檔案(python實現)

  import csv with open('Python-Predict/Data/train.csv') as tra: rdr = csv.reader(tra) items = list(rdr) print("rdr:",rdr) print(items)

application properties 檔案 application yml 檔案有什麼區別呢

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

用Makefile編譯靜態庫檔案動態庫檔案

    最近要用到的簽名演算法只給了一堆原始碼沒有給庫檔案,api都不好呼叫,於是嘗試著用Makefile給一堆c原始碼編譯靜態連結庫檔案和動態連結庫檔案。 Makefile檔案編輯的相關資料連結: https://www.cnblogs.com/yya

C#不用ArcEngine,生成Shp檔案(五)---------讀取.shx檔案生成.shx檔案

這一篇來寫一下.shx檔案的讀取跟生成。測試資料下載地址為:http://download.csdn.net/detail/gis0911178/9650967 在第一篇時候有介紹. 索引檔案(.shx)主要包含座標檔案的索引資訊,檔案中每個記錄包含對應的座標檔案記錄距離座標檔案的檔案頭的偏

Struts2單個檔案多個檔案上傳

<一>簡述: Struts2的檔案上傳其實也是通過攔截器來實現的,只是該攔截器定義為預設攔截器了,所以不用自己去手工配置,<interceptor name=”fileUpload” class=”org.apache.struts2.interceptor.

Android 讀取本地txt檔案寫入txt檔案到本地

import android.util.Log; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java

中科之旅------matlab生成wav檔案解析wav檔案

生成wav: % 生成時間序列 fs = 5000;      % [Hz] 訊號取樣頻率 T = 1;          % [s] 訊號長度  x = 0:1/fs:T;   % [s] 時間序列   % 生成訊號序列 f = 440;        % [Hz] 訊號

java 通過SFTP連線,獲取指定目錄檔案上傳檔案

import com.jcraft.jsch.Channel; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; import co

Qt新增庫檔案標頭檔案目錄(QCreator)(轉載學習)

在使用QtCreator開發影象處理程式的時候想加入Opencv庫來處理圖形,新增標頭檔案,需要編輯工程資料夾下的.pro檔案在檔案中新增以下內容,即可包含標頭檔案的資料夾:   INCLUDEPATH +=D:\OpenCV2.0\vc2008\include

Java讀取txt檔案寫入txt檔案-多種方法

記得關閉流,記得關閉流,記得關閉流, 讀取: 第一種: import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import

Java讀取txt檔案寫入txt檔案

import java.io.File;   import java.io.InputStreamReader;   import java.io.BufferedReader;   import java.io.BufferedWriter;   import java.io.FileInputSt

WPF專案中.xaml檔案.xaml.cs檔案無法收縮顯示的解決辦法

在VisualStudio的WPF專案中,通常同名的.xaml和.xaml.cs檔案是能夠收縮顯示的,但是在某些情況下,會出現.xaml和.xaml.cs並列顯示,無法進行收縮. 如下圖,綠色部分為正常的顯示情況;紅色部分為不正常顯示情況. 解決辦法是修改.csproj檔案

Ubantu下如何安裝mysql資料庫,以及如何備份sql檔案執行sql檔案

  1. sudo apt-get install mysql-server   2. apt-get isntall mysql-client   3.  sudo apt-get install libmysqlclient-dev 安裝好mysql之後,檢查是否安裝成功:sudo netstat

Linux下隱藏檔案顯示隱藏檔案命令

例子:將a目錄隱藏 命令:mv a .a 還可以在建立檔案時直接以.開頭起名,得到的檔案就是隱藏檔案 linux下顯示隱藏檔案有兩種可能:顯示所有檔案,包括隱藏檔案;僅顯示隱藏檔案。 顯示所有檔案(包含隱藏檔案)  ls -a 只顯示隱藏檔案  l.  或者  ls -d .*

linux驅動開發-檔案系統與裝置檔案

目錄 1.Linux檔案系統操作 Linux檔案建立,開啟,關閉函式 #檔案許可權最終由mode&umask決定 int creat (const char *filename,mode_t mod