Linux的使用和使用者空間程式的程式設計與檔案系統密切相關。對於檔案系統的概念,大家可能已經比較熟悉了,所以我不會過度講解。畢竟,只要能了解這些概念就可以了,對於想深入了解的人,可以隨時透過百度等搜尋引擎獲取更多資訊。現在我將重點放在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,下圖講訴了進程描述符是如何與檔案相關的結構相關聯的。
先來介紹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中,記憶體被分成區,每個擁有活躍頁的鍊錶和不活躍的鍊錶,當頁不活躍的時候,就會被寫回磁碟,下圖說明了上述關係:
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中,其結構如下圖:
最後來說說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中文網其他相關文章!