# 文件系統,第 2 部分:文件是 inode(其他一切只是數據...)
> 原文:<https://github.com/angrave/SystemProgramming/wiki/File-System%2C-Part-2%3A-Files-are-inodes>
好主意:忘記文件名:'inode'是文件。
通常將文件名視為“實際”文件。不是!而是將 inode 視為文件。 inode 保存元信息(最后訪問,所有權,大小)并指向用于保存文件內容的磁盤塊。
## 那么......我們如何實現目錄?
目錄只是名稱到 inode 編號的映射。 POSIX 提供了一小組函數來讀取每個條目的文件名和 inode 號(見下文)
讓我們考慮一下它在實際文件系統中的樣子。從理論上講,目錄就像實際文件一樣。磁盤塊將包含 _ 目錄條目 _ 或 _dirents_ 。這意味著我們的磁盤塊看起來像這樣
| inode_num | 名稱 |
| --- | --- |
| 2043567 | hi.txt |
...
每個目錄條目可以是固定大小,也可以是變量 c-string。它取決于特定文件系統在較低級別實現它的方式。
## 如何找到文件的 inode 編號?
從 shell 中,將`ls`與`-i`選項一起使用
```
$ ls -i
12983989 dirlist.c 12984068 sandwich.c
```
從 C,調用 stat 函數之一(下面介紹)。
## 如何找到有關文件(或目錄)的元信息?
使用 stat 調用。例如,要找出上次訪問我的'notes.txt'文件的時間 -
```c
struct stat s;
stat("notes.txt", & s);
printf("Last accessed %s", ctime(s.st_atime));
```
實際上有三個版本的`stat`;
```c
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
```
例如,如果您已經有與該文件關聯的文件描述符,則可以使用`fstat`查找有關文件的元信息
```c
FILE *file = fopen("notes.txt", "r");
int fd = fileno(file); /* Just for fun - extract the file descriptor from a C FILE struct */
struct stat s;
fstat(fd, & s);
printf("Last accessed %s", ctime(s.st_atime));
```
我們將在引入符號鏈接時討論第三個調用'lstat'。
除了訪問,創建和修改時間之外,stat 結構還包括 inode 編號,文件長度和所有者信息。
```c
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for file system I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};
```
## 如何列出目錄的內容?
讓我們編寫自己的'ls'版本來列出目錄的內容。
```c
#include <stdio.h>
#include <dirent.h>
#include <stdlib.h>
int main(int argc, char **argv) {
if(argc == 1) {
printf("Usage: %s [directory]\n", *argv);
exit(0);
}
struct dirent *dp;
DIR *dirp = opendir(argv[1]);
while ((dp = readdir(dirp)) != NULL) {
puts(dp->d_name);
}
closedir(dirp);
return 0;
}
```
## 如何讀取目錄的內容?
Ans:使用 opendir readdir closedir 例如,這是一個非常簡單的'ls'實現來列出目錄的內容。
```c
#include <stdio.h>
#include <dirent.h>
#include <stdlib.h>
int main(int argc, char **argv) {
if(argc ==1) {
printf("Usage: %s [directory]\n", *argv);
exit(0);
}
struct dirent *dp;
DIR *dirp = opendir(argv[1]);
while ((dp = readdir(dirp)) != NULL) {
printf("%s %lu\n", dp-> d_name, (unsigned long)dp-> d_ino );
}
closedir(dirp);
return 0;
}
```
注意:在調用 fork()之后,父級或子級(XOR)可以使用 readdir(),rewinddir()或 seekdir()。如果父項和子項都使用上述內容,則行為未定義。
## 如何檢查文件是否在當前目錄中?
例如,要查看特定目錄是否包含文件(或文件名)'name',我們可能會編寫以下代碼。 (提示:你能發現這個錯誤嗎?)
```c
int exists(char *directory, char *name) {
struct dirent *dp;
DIR *dirp = opendir(directory);
while ((dp = readdir(dirp)) != NULL) {
puts(dp->d_name);
if (!strcmp(dp->d_name, name)) {
return 1; /* Found */
}
}
closedir(dirp);
return 0; /* Not Found */
}
```
上面的代碼有一個微妙的錯誤:它泄漏了資源!如果找到匹配的文件名,那么'closedir'永遠不會被稱為早期返回的一部分。由 opendir 打開的任何文件描述符和任何分配的內存都不會被釋放。這意味著最終進程將耗盡資源,`open`或`opendir`調用將失敗。
解決方法是確保我們在每個可能的代碼路徑中釋放資源。在上面的代碼中,這意味著在`return 1`之前調用`closedir`。忘記釋放資源是一個常見的 C 編程錯誤,因為 C 語言中沒有任何支持來確保始終使用所有代碼路徑釋放資源。
## 使用 readdir 有什么問題?例如,遞歸搜索目錄?
有兩個主要問題和一個考慮因素:`readdir`函數返回“。” (當前目錄)和“..”(父目錄)。如果要查找子目錄,則需要明確排除這些目錄。
對于許多應用程序,在遞歸搜索子目錄之前首先檢查當前目錄是合理的。這可以通過將結果存儲在鏈表中,或重置目錄結構以從頭重新開始來實現。
最后一點需要注意:`readdir`不是線程安全的!對于多線程搜索,使用`readdir_r`,它要求調用者傳入現有 dirent 結構的地址。
有關更多詳細信息,請參見 readdir 的手冊頁。
## 如何確定目錄條目是否是目錄?
Ans:使用`S_ISDIR`檢查存儲在 stat 結構中的模式位
并檢查文件是否是常規文件使用`S_ISREG`,
```c
struct stat s;
if (0 == stat(name, &s)) {
printf("%s ", name);
if (S_ISDIR( s.st_mode)) puts("is a directory");
if (S_ISREG( s.st_mode)) puts("is a regular file");
} else {
perror("stat failed - are you sure I can read this file's meta data?");
}
```
## 目錄是否也有 inode?
是!雖然更好的思考方法是,目錄(如文件)_ 是 _ 的 inode(帶有一些數據 - 目錄名和 inode 內容)。它恰好是一種特殊的 inode。
來自[維基百科](http://en.wikipedia.org/wiki/Inode):
> Unix 目錄是關聯結構的列表,每個關聯結構包含一個文件名和一個 inode 編號。
請記住,inode 不包含文件名 - 只包含其他文件元數據。
## 如何在文件系統中的兩個不同位置顯示相同的文件?
首先要記住文件名!=文件。可以將 inode 視為“文件”,將目錄視為名稱列表,每個名稱都映射到 inode 編號。其中一些 inode 可能是常規文件 inode,其他可能是目錄 inode。
如果我們已經在文件系統上有文件,我們可以使用'ln'命令創建指向同一 inode 的另一個鏈接
```
$ ln file1.txt blip.txt
```
但是 blip.txt _ 是 _ 同一個文件;如果我編輯 blip 我正在編輯與'file1.txt!'相同的文件我們可以通過顯示兩個文件名引用相同的 inode 來證明這一點:
```
$ ls -i file1.txt blip.txt
134235 file1.txt
134235 blip.txt
```
這些類型的鏈接(也稱目錄條目)稱為“硬鏈接”
等效的 C 調用是`link`
```c
link(const char *path1, const char *path2);
link("file1.txt", "blip.txt");
```
為簡單起見,上述示例在同一目錄中創建了硬鏈接,但是可以在同一文件系統內的任何位置創建硬鏈接。
## 當我`rm`(刪除)文件時會發生什么?
刪除文件(使用`rm`或`unlink`)時,將從目錄中刪除 inode 引用。但是,仍可以從其他目錄引用 inode。為了確定是否仍然需要文件的內容,每個 inode 保持一個引用計數,只要創建或銷毀新鏈接就會更新該引用計數。
## 案例研究:備份最小化文件復制的軟件
硬鏈接的示例使用是在不同時間點有效地創建文件系統的多個檔案。歸檔區域具有特定文件的副本后,未來的歸檔可以重新使用這些歸檔文件,而不是創建重復文件。 Apple 的“Time Machine”軟件就是這樣做的。
## 我可以創建目錄和常規文件的硬鏈接嗎?
不,是的。不是真的......其實你真的不想這樣做,是嗎? POSIX 標準說不,你可能不會! `ln`命令僅允許 root 執行此操作,并且僅當您提供`-d`選項時才允許 root。但是,即使 root 也可能無法執行此操作,因為大多數文件系統都會阻止它!
為什么?
## 文件系統的完整性假設目錄結構(我們稍后將討論的軟鏈接除外)是可從根目錄訪問的非循環樹。如果允許目錄鏈接,則強制執行或驗證此約束會變得昂貴。打破這些假設可能導致文件完整性工具無法修復文件系統。遞歸搜索可能永遠不會終止,目錄可以有多個父級,但“..”只能引用單個父級。總而言之,一個壞主意。
- UIUC CS241 系統編程中文講義
- 0. 簡介
- #Informal 詞匯表
- #Piazza:何時以及如何尋求幫助
- 編程技巧,第 1 部分
- 系統編程短篇小說和歌曲
- 1.學習 C
- C 編程,第 1 部分:簡介
- C 編程,第 2 部分:文本輸入和輸出
- C 編程,第 3 部分:常見問題
- C 編程,第 4 部分:字符串和結構
- C 編程,第 5 部分:調試
- C 編程,復習題
- 2.進程
- 進程,第 1 部分:簡介
- 分叉,第 1 部分:簡介
- 分叉,第 2 部分:Fork,Exec,等等
- 進程控制,第 1 部分:使用信號等待宏
- 進程復習題
- 3.內存和分配器
- 內存,第 1 部分:堆內存簡介
- 內存,第 2 部分:實現內存分配器
- 內存,第 3 部分:粉碎堆棧示例
- 內存復習題
- 4.介紹 Pthreads
- Pthreads,第 1 部分:簡介
- Pthreads,第 2 部分:實踐中的用法
- Pthreads,第 3 部分:并行問題(獎金)
- Pthread 復習題
- 5.同步
- 同步,第 1 部分:互斥鎖
- 同步,第 2 部分:計算信號量
- 同步,第 3 部分:使用互斥鎖和信號量
- 同步,第 4 部分:臨界區問題
- 同步,第 5 部分:條件變量
- 同步,第 6 部分:實現障礙
- 同步,第 7 部分:讀者編寫器問題
- 同步,第 8 部分:環形緩沖區示例
- 同步復習題
- 6.死鎖
- 死鎖,第 1 部分:資源分配圖
- 死鎖,第 2 部分:死鎖條件
- 死鎖,第 3 部分:餐飲哲學家
- 死鎖復習題
- 7.進程間通信&amp;調度
- 虛擬內存,第 1 部分:虛擬內存簡介
- 管道,第 1 部分:管道介紹
- 管道,第 2 部分:管道編程秘密
- 文件,第 1 部分:使用文件
- 調度,第 1 部分:調度過程
- 調度,第 2 部分:調度過程:算法
- IPC 復習題
- 8.網絡
- POSIX,第 1 部分:錯誤處理
- 網絡,第 1 部分:簡介
- 網絡,第 2 部分:使用 getaddrinfo
- 網絡,第 3 部分:構建一個簡單的 TCP 客戶端
- 網絡,第 4 部分:構建一個簡單的 TCP 服務器
- 網絡,第 5 部分:關閉端口,重用端口和其他技巧
- 網絡,第 6 部分:創建 UDP 服務器
- 網絡,第 7 部分:非阻塞 I O,select()和 epoll
- RPC,第 1 部分:遠程過程調用簡介
- 網絡復習題
- 9.文件系統
- 文件系統,第 1 部分:簡介
- 文件系統,第 2 部分:文件是 inode(其他一切只是數據...)
- 文件系統,第 3 部分:權限
- 文件系統,第 4 部分:使用目錄
- 文件系統,第 5 部分:虛擬文件系統
- 文件系統,第 6 部分:內存映射文件和共享內存
- 文件系統,第 7 部分:可擴展且可靠的文件系統
- 文件系統,第 8 部分:從 Android 設備中刪除預裝的惡意軟件
- 文件系統,第 9 部分:磁盤塊示例
- 文件系統復習題
- 10.信號
- 過程控制,第 1 部分:使用信號等待宏
- 信號,第 2 部分:待處理的信號和信號掩碼
- 信號,第 3 部分:提高信號
- 信號,第 4 部分:信號
- 信號復習題
- 考試練習題
- 考試主題
- C 編程:復習題
- 多線程編程:復習題
- 同步概念:復習題
- 記憶:復習題
- 管道:復習題
- 文件系統:復習題
- 網絡:復習題
- 信號:復習題
- 系統編程笑話