首頁 系統教程 Linux 24小時學通Linux核心之有關Linux檔案系統實作的問題

24小時學通Linux核心之有關Linux檔案系統實作的問題

Feb 05, 2024 pm 04:00 PM
linux linux教程 linux系統 linux指令 shell腳本 嵌入式linux 良許 linux入門 linux學習

Linux的使用和使用者空間程式的程式設計與檔案系統密切相關。對於檔案系統的概念,大家可能已經比較熟悉了,所以我不會過度講解。畢竟,只要能了解這些概念就可以了,對於想深入了解的人,可以隨時透過百度等搜尋引擎獲取更多資訊。現在我將重點放在Linux的虛擬檔案系統。

虛擬檔案系統是Linux的一個重要特性之一,它支援多種不同的檔案系統。文件系統的架構如下圖所示:[圖片見原文] 24小時學通Linux核心之有關Linux檔案系統實作的問題

上圖中VFS(虛擬檔案系統)依賴資料結構來保存其對一個檔案系統的一般表示,其中資料結構羅列如下:

  • 超級區塊結構:存放已經安裝的檔案系統的相關資訊;

  • 索引結點結構:存放有關文件的 資訊;

  • 檔案結構:存放已行程開啟的檔案的相關資訊;

  • 目錄項目結構:存放有關路徑名和路徑名所指向的檔案的資訊。

    Linux核心使用全域變數來保存先前提到的指向結構體的指針,所有的結構都用雙向鍊錶保存,核心保存指向鍊錶頭的指針,並且把它當作鍊錶的存取點,這些結構都用list_head類型的域,用它來指向鍊錶中的前一個元素,下表是內核保存的全域變數以及這些變數指向的鍊錶類型(與VFS相關的全域變數)

全域變數 結構類型
super_blocks #super_block
file_systems #file_systems_type
dentry_unused dentry
vfsmntlist vfsmount
inode_in_use inode
inode_unused inode
#

super_block、file_system_type、dentry、vfsmoubt結構都保存在它們自己的鍊錶中,索引結點能夠在全域的inode_in_use上或inode_unused上找到自己,或者它們對應的超快的局部鍊錶上都可以找到自己。

除了主要的VFS結構之外,還有幾個其他的結構與VFS相互作用,fs_struct和files_struct,namespace,fd_set,下圖講訴了進程描述符是如何與檔案相關的結構相關聯的。

24小時學通Linux核心之有關Linux檔案系統實作的問題

先來介紹fs_struct結構,fs_struct結構可以被多個行程描述子引用,下述程式碼在include/Linux/fs_struct.h中可以查到哦,程式碼解釋不好的請大神指教

struct fs_struct{
    atomic_t count;  //保存引用特定fs_struct的进程描述符数目
    rwlock_t lock;
    int umask;  //保存一个掩码,表示将要在打开文件上设置的许可权
    struct dentry * root, *pwd ,*altroot;  //都是指针,,,,
    struct vfsmount * rootmnt, *pwdmnt,  *altrootmnt;  //指针,
};
登入後複製

files_struct包含開啟檔案和其描述符的相關訊息,它使用這些集合來對它的描述符進行分組。下面程式碼可以在include/linux/file.h上查看到

struct files_struct{
    atomic_t count;  //与fs_struct类似
    spinlock_t file_lock;
    int max_fds;  //表示进程能够打开的文件的最大数
    int max_fdset;  //表示描述符的最大数
    int next_fd;  //保存下一个将要分配的文件描述符的值
    struct file ** fd;  //fd数组指向打开的文件对象的数组
    fd_set *close_on_exec; //是指向文件描述符集的一个指针,这些文件描述符在exec()时候就被标志位将要关闭,如果在exec()时候被标志位“打开”的文件描述符数超过close_on_exec_init域的大小,则改变close_on_exec域的值;
    fd_set *open_fds; //是一个指针,指向被标记为“打开”的文件描述符集合,
    fd_set close_on_exec_init;  //保存一个位域,表示打开文件对应的文件描述符
    fd_set open_fds_init;    //这些都是fd_set类型的域,其实都不懂,,,
    struct file *fd_array[NR_OPEN_DEFAULT];//fd_array数组指针指向前32个打开的文件描述法
};
登入後複製

透過INIT_FILES巨集初始化fs_struct結構:

#define INIT_FILES \
{
    .count = ATOMIC_INIT(1),
    .file_lock = SPIN_LOCK_UNLOCKED,
    .max_fds = NR_OPEN_DEFAULT,
    .max_fdset = __FD_SETSIZE,
    .next_fd = 0,
    .fd = &init_files.fd_array[0];
    .close_on_exec = &init_files.close_on_exec_init,
    .open_fds = &init_files.open_fds_init,
    .close_on_exec_init = {{0, }},
    .open_fda_init = {{0, }},
    .fd_array = {NULL, }
}
登入後複製

NR_OPEN_DEFAULT的全域定義被設定為BITS_PER_LONG,BITS_PER_LONG在32位元系統中是32,在64位元系統中是64.

下面來介紹一下頁緩衝,我們現在看看它是如何運作和實現的。在Linux中,記憶體被分成區,每個擁有活躍頁的鍊錶和不活躍的鍊錶,當頁不活躍的時候,就會被寫回磁碟,下圖說明了上述關係:

24小時學通Linux核心之有關Linux檔案系統實作的問題

image-20240202221039708

頁緩衝的核心是address_space對象,其程式碼在include/linux/fs.h中可以查看(這段程式碼不是很懂,求大神指教):

struct address_space{    
    struct inode *host;
    struct radix_tree_root page_tree;
    spinlock_t tree_lock;
    unsigned long nrpages;
    pgoff_t writeback;
    struct address_space_operations *a_ops;
    struct prio_tree_root i_map;
    unsigned inr i_map_lock;
    struct list_head i_mmap_nonlinear;
    spinlock_t i_mmap_lock;
    atomic_t truncate_count;
    unsigned long flags;
    struct backing_dev_info *backing_dev_info;
    spinlock_t private_lock;
    struct list_head private_list;
    struct address_space *assoc_mapping;
};

登入後複製

Linux核心也把區塊裝置上的每個磁區表示buffer_head結構,buffer_head結構應用的物理區是裝置b_dev的邏輯區塊b_blocknr,引用的實體記憶體是起始在區塊大小為b_size個位元組的b_data記憶體資料區塊,這個記憶體區塊在物理頁b_page中,其結構如下圖:

24小時學通Linux核心之有關Linux檔案系統實作的問題

最後來說說VFS系統呼叫和檔案系統層,並且追蹤它們的執行直到核心級別,我們得先了解四個函數:open()、close()、read()、write() 。

open()函數:

open 函數用於開啟和建立檔案。以下是 open 函數的簡單描述

#include 
int open(const char *pathname, int oflag, ... );
登入後複製

傳回值:成功則傳回檔案描述符,否則傳回 -1

對於 open 函數來說,第三個參數(…)僅在建立新檔案時才使用,用於指定檔案的存取權位元(access permission bits)。 pathname 是待開啟/建立檔案的路徑名稱(如 C:/cpp/a.cpp);oflag 用來指定檔案的開啟/建立模式,這個參數可由下列常數(定義於 fcntl.h)透過邏輯或構成。

  • O_RDONLY 唯讀模式

  • O_WRONLY 只寫模式

  • O_RDWR 讀寫模式

  • 開啟/建立檔案時,至少得使用上述三個常數中的一個。以下常數是選用的:

  • O_APPEND 每次寫入作業都會寫入檔案的結尾

  • O_CREAT 如果指定檔案不存在,則建立這個檔案

  • O_EXCL 如果要建立的檔案已存在,則傳回 -1,並且修改 errno 的值

  • O_TRUNC 如果檔案存在,並且以唯寫/讀寫方式打開,則清空檔案全部內容

  • O_NOCTTY 如果路徑名稱指向終端設備,不要把這個裝置當作控制終端。

  • O_NONBLOCK 如果路徑名稱指向 FIFO/區塊檔案/字元文件,則把檔案的開啟和後繼 I/O設定為非阻塞模式(nonblocking mode)

  • 以下三個常數同樣是選用的,它們用來同步輸入輸出

  • O_DSYNC 等待物理 I/O 結束後再 write。在不影響讀取新寫入的資料的前提下,不等待檔案屬性更新。

  • O_RSYNC read 等待所有寫入相同區域的寫入操作完成後再進行

  • O_SYNC 等待物理 I/O 結束後再 write,包括更新檔案屬性的 I/O

    open 傳回的檔案描述符一定是最小的未被使用的描述符。

    如果NAME_MAX(檔案名稱最大長度,不包含'\0')是14,而我們想在目前目錄下建立檔案名稱長度超過14 個位元組的文件,早期的System V 系統(如SVR2)會截斷超出部分,只保留前14 個位元組;而由BSD 衍生的(BSD-derived)系統會傳回錯誤訊息,並且把errno 置為ENAMETOOLONG。

    POSIX.1 引入常量 _POSIX_NO_TRUNC 用于决定是否截断长文件名/长路径名。如果_POSIX_NO_TRUNC 设定为禁止截断,并且路径名长度超过 PATH_MAX(包括 ‘\0’),或者组成路径名的任意文件名长度超过 NAME_MAX,则返回错误信息,并且把 errno 置为 ENAMETOOLONG。

close()函数

进程使用完文件后,发出close()系统调用:

sysopsis

#include 
int close(int fd);
登入後複製

参数:fd文件描述符

函数返回值:0成功,-1出错

参数fd是要关闭的文件描述符。需要说明的是:当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。但是对于一个长年累月运行的程序(比如网络服务器),打开的文件描述符一定要记得关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。

read()函数

当用户级别程序调用read()函数时,Linux把它转换成系统调sys_read():

功能描述:从文件读取数据。
所需头文件: #include

函数原型:ssize_t read(int fd, void *buf, size_t count);

参数

  • fd: 将要读取数据的文件描述词。

  • buf:指缓冲区,即读取的数据会被放到这个缓冲区中去。

  • count:表示调用一次read操作,应该读多少数量的字符。

  • 返回值:返回所读取的字节数;0(读到EOF);-1(出错)。

  • 以下几种情况会导致读取到的字节数小于 count :

  • 读取普通文件时,读到文件末尾还不够 count 字节。例:如果文件只有 30 字节,而我们想读取 100,字节,那么实际读到的只有 30 字节, 函数返回 30 。此时再使用 read 函数作用于这个文件会导致 read 返回 0

  • 从终端设备(terminal device)读取时,一般情况下每次只能读取一行。

  • 从网络读取时,网络缓存可能导致读取的字节数小于 count字节。

  • 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 count 。

  • 从面向记录(record-oriented)的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。

  • 在读取了部分数据时被信号中断,读操作始于 cfo 。在成功返回之前,cfo 增加,增量为实际读取到的字节数。

    例程如下(程序是网上找的例子,贴下来以以供大家理解一下)::

#include 
#include 
#include 
#include 
#include 
#include 
int main(void)
{
    void* buf ;
    int handle;
    int bytes ;
    buf=malloc(10);
    /*
    LooksforafileinthecurrentdirectorynamedTEST.$$$andattempts
    toread10bytesfromit.Tousethisexampleyoushouldcreatethe
    fileTEST.$$$
    */
    handle=open("TEST.$$$",O_RDONLY|O_BINARY,S_IWRITE|S_IREAD);
    if(handle==-1)
    {
        printf("ErrorOpeningFile\n");
        exit(1);
    }
    bytes=read(handle,buf,10);
    if(bytes==-1)
    {
        printf("ReadFailed.\n");
        exit(1);
    }
    else 
    {
        printf("Read:%dbytesread.\n",bytes);
    }
    return0 ;
}
登入後複製

write()函数

功能描述:向文件写入数据。
所需头文件: #include

函数原型:ssize_t write(int fd, void *buf, size_t count);

返回值:写入文件的字节数(成功);-1(出错)

功能:write 函数向 filedes 中写入 count 字节数据,数据来源为 buf 。返回值一般总是等于 count,否则就是出错了。常见的出错原因是磁盘空间满了或者超过了文件大小限制。对于普通文件,写操作始于 cfo 。如果打开文件时使用了 O_APPEND,则每次写操作都将数据写入文件末尾。成功写入后,cfo 增加,增量为实际写入的字节数。

例程如下(程序是网上找的例子,贴下来以以供大家理解一下):

#include 
#include 
#include 
#include 
#include 
#include 
int main(void)
{
int *handle; char string[40];
int length, res;/* Create a file named "TEST.$$$" in the current directory and write a string to it. If "TEST.$$$" already exists, it will be overwritten. */
if ((handle = open("TEST.$$$", O_WRONLY | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE)) == -1)
{
printf("Error opening file.\n");
exit(1);
}
strcpy(string, "Hello, world!\n");
length = strlen(string);
if ((res = write(handle, string, length)) != length)
{
printf("Error writing to the file.\n");
exit(1);
}
printf("Wrote %d bytes to the file.\n", res);
close(handle); return 0; }
登入後複製

小结

今天看的代码不多,差不多都是网上找的代码,有些解释也是查阅资料写上去的,有些还是不懂,希望各路大神指教,这里我总结了有关Linux文件系统实现的问题,但是具体的细节方面并没有提及到,大家看了之后应该只能有一个大致的最Linux文件系统的了解,有读者问我看的是哪些书,这里我说明一下,看了Linux内核编程,还有深入理解Linux内核以及网上各种资料或者其他大牛写的好的博客。这里我是总结了一下,并且把自己不懂的还有觉得重要的说了一下,希望各位大神给些建议,thanks~

以上是24小時學通Linux核心之有關Linux檔案系統實作的問題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1666
14
CakePHP 教程
1425
52
Laravel 教程
1325
25
PHP教程
1272
29
C# 教程
1252
24
Linux體系結構:揭示5個基本組件 Linux體系結構:揭示5個基本組件 Apr 20, 2025 am 12:04 AM

Linux系統的五個基本組件是:1.內核,2.系統庫,3.系統實用程序,4.圖形用戶界面,5.應用程序。內核管理硬件資源,系統庫提供預編譯函數,系統實用程序用於系統管理,GUI提供可視化交互,應用程序利用這些組件實現功能。

git怎麼查看倉庫地址 git怎麼查看倉庫地址 Apr 17, 2025 pm 01:54 PM

要查看 Git 倉庫地址,請執行以下步驟:1. 打開命令行並導航到倉庫目錄;2. 運行 "git remote -v" 命令;3. 查看輸出中的倉庫名稱及其相應的地址。

notepad怎麼運行java代碼 notepad怎麼運行java代碼 Apr 16, 2025 pm 07:39 PM

雖然 Notepad 無法直接運行 Java 代碼,但可以通過借助其他工具實現:使用命令行編譯器 (javac) 編譯代碼,生成字節碼文件 (filename.class)。使用 Java 解釋器 (java) 解釋字節碼,執行代碼並輸出結果。

sublime寫好代碼後如何運行 sublime寫好代碼後如何運行 Apr 16, 2025 am 08:51 AM

在 Sublime 中運行代碼的方法有六種:通過熱鍵、菜單、構建系統、命令行、設置默認構建系統和自定義構建命令,並可通過右鍵單擊項目/文件運行單個文件/項目,構建系統可用性取決於 Sublime Text 的安裝情況。

Linux的主要目的是什麼? Linux的主要目的是什麼? Apr 16, 2025 am 12:19 AM

Linux的主要用途包括:1.服務器操作系統,2.嵌入式系統,3.桌面操作系統,4.開發和測試環境。 Linux在這些領域表現出色,提供了穩定性、安全性和高效的開發工具。

laravel安裝代碼 laravel安裝代碼 Apr 18, 2025 pm 12:30 PM

要安裝 Laravel,需依序進行以下步驟:安裝 Composer(適用於 macOS/Linux 和 Windows)安裝 Laravel 安裝器創建新項目啟動服務訪問應用程序(網址:http://127.0.0.1:8000)設置數據庫連接(如果需要)

git軟件安裝 git軟件安裝 Apr 17, 2025 am 11:57 AM

安裝 Git 軟件包括以下步驟:下載安裝包運行安裝包驗證安裝配置 Git安裝 Git Bash(僅限 Windows)

VSCode怎麼用 VSCode怎麼用 Apr 15, 2025 pm 11:21 PM

Visual Studio Code (VSCode) 是一款跨平台、開源且免費的代碼編輯器,由微軟開發。它以輕量、可擴展性和對眾多編程語言的支持而著稱。要安裝 VSCode,請訪問官方網站下載並運行安裝程序。使用 VSCode 時,可以創建新項目、編輯代碼、調試代碼、導航項目、擴展 VSCode 和管理設置。 VSCode 適用於 Windows、macOS 和 Linux,支持多種編程語言,並通過 Marketplace 提供各種擴展。它的優勢包括輕量、可擴展性、廣泛的語言支持、豐富的功能和版

See all articles