Linux 的檔案與目錄
現代作業系統為解決資訊能獨立于程序之外被長期存儲引入了檔案,檔案作為程序建立資訊的邏輯單元可被多個程序并發使用。在 UNIX 系統中,作業系統為磁盤上的文本與圖像、滑鼠與鍵盤等輸入裝置及網絡互動等 I/O 操作設計了一組通用 API,使他們被處理時均可統一使用位元組流方式。換言之,UNIX 系統中除程序之外的一切皆是檔案,而 Linux 保持了這一特性。為了便于檔案的管理,Linux 還引入了目錄(有時亦被稱為檔案夾)這一概念。目錄使檔案可被分類管理,且目錄的引入使 Linux 的檔案系統形成一個層級結構的目錄樹。清單 1.所示的是普通 Linux 系統的頂層目錄結構,其中 /dev 是存放了裝置相關檔案的目錄。
清單 1. Linux 系統的頂層目錄結構
/ 根目錄
├── bin 存放使用者二進制檔案
├── boot 存放核心引導配置檔案
├── dev 存放裝置檔案
├── etc 存放系統配置檔案
├── home 使用者主目錄
├── lib 動态共享庫
├── lost+found 檔案系統恢複時的恢複檔案
├── media 可解除安裝存儲媒體挂載點
├── mnt 檔案系統臨時挂載點
├── opt 附加的應用程式包
├── proc 系統記憶體的映射目錄,提供核心與程序資訊
├── root root 使用者主目錄
├── sbin 存放系統二進制檔案
├── srv 存放服務相關資料
├── sys sys 虛拟檔案系統挂載點
├── tmp 存放臨時檔案
├── usr 存放使用者應用程式
└── var 存放郵件、系統日志等變化檔案
Linux 與其他類 UNIX 系統一樣并不區分檔案與目錄:目錄是記錄了其他檔案名的檔案。使用指令 mkdir 建立目錄時,若期望建立的目錄的名稱與現有的檔案名(或目錄名)重複,則會建立失敗。
# ls -F /usr/bin/zi*
/usr/bin/zip* /usr/bin/zipgrep* /usr/bin/zipnote*
/usr/bin/zipcloak* /usr/bin/zipinfo* /usr/bin/zipsplit*
# mkdir -p /usr/bin/zip
mkdir: cannot create directory `/usr/bin/zip': File exists
Linux 将裝置當做檔案進行處理,清單 2.展示了如何打開裝置檔案 /dev/input/event5 并讀取檔案内容。檔案 event5 表示一種輸入裝置,其可能是滑鼠或鍵盤等。檢視檔案 /proc/bus/input/devices 可知 event5 對應裝置的類型。裝置檔案 /dev/input/event5 使用 read() 以字元流的方式被讀取。結構體 input_event 被定義在核心頭檔案 linux/input.h 中。
清單 2. 打開并讀取裝置檔案
int fd;
struct input_event ie;
fd = open("/dev/input/event5", O_RDONLY);
read(fd, &ie, sizeof(struct input_event));
printf("type = %d code = %d value = %d\n",
ie.type, ie.code, ie.value);
close(fd);
回頁首
硬連結與軟連結的聯系與差別
我們知道檔案都有檔案名與資料,這在 Linux 上被分成兩個部分:使用者資料 (user data) 與中繼資料 (metadata)。使用者資料,即檔案資料塊 (data block),資料塊是記錄檔案真實内容的地方;而中繼資料則是檔案的附加屬性,如檔案大小、建立時間、所有者等資訊。在 Linux 中,中繼資料中的 inode 号(inode 是檔案中繼資料的一部分但其并不包含檔案名,inode 号即索引節點号)才是檔案的唯一辨別而非檔案名。檔案名僅是為了友善人們的記憶和使用,系統或程式通過 inode 号尋找正确的檔案資料塊。圖 1.展示了程式通過檔案名擷取檔案内容的過程。
圖 1. 通過檔案名打開檔案

清單 3. 移動或重命名檔案
# stat /home/harris/source/glibc-2.16.0.tar.xz
File: `/home/harris/source/glibc-2.16.0.tar.xz'
Size: 9990512 Blocks: 19520 IO Block: 4096 regular file
Device: 807h/2055d Inode: 2485677 Links: 1
Access: (0600/-rw-------) Uid: ( 1000/ harris) Gid: ( 1000/ harris)
...
...
# mv /home/harris/source/glibc-2.16.0.tar.xz /home/harris/Desktop/glibc.tar.xz
# ls -i -F /home/harris/Desktop/glibc.tar.xz
2485677 /home/harris/Desktop/glibc.tar.xz
在 Linux 系統中檢視 inode 号可使用指令 stat 或 ls -i(若是 AIX 系統,則使用指令 istat)。清單 3.中使用指令 mv 移動并重命名檔案 glibc-2.16.0.tar.xz,其結果不影響檔案的使用者資料及 inode 号,檔案移動前後 inode 号均為:2485677。
為解決檔案的共享使用,Linux 系統引入了兩種連結:硬連結 (hard link) 與軟連結(又稱符号連結,即 soft link 或 symbolic link)。連結為 Linux 系統解決了檔案的共享使用,還帶來了隐藏檔案路徑、增權重限安全及節省存儲等好處。若一個 inode 号對應多個檔案名,則稱這些檔案為硬連結。換言之,硬連結就是同一個檔案使用了多個别名(見 圖 2.hard link 就是 file 的一個别名,他們有共同的 inode)。硬連結可由指令 link 或 ln 建立。如下是對檔案 oldfile 建立硬連結。
link oldfile newfile
ln oldfile newfile
由于硬連結是有着相同 inode 号僅檔案名不同的檔案,是以硬連結存在以下幾點特性:
- 檔案有相同的 inode 及 data block;
- 隻能對已存在的檔案進行建立;
- 不能交叉檔案系統進行硬連結的建立;
- 不能對目錄進行建立,隻可對檔案建立;
- 删除一個硬連結檔案并不影響其他有相同 inode 号的檔案。
清單 4. 硬連結特性展示
# ls -li
total 0
// 隻能對已存在的檔案建立硬連接配接
# link old.file hard.link
link: cannot create link `hard.link' to `old.file': No such file or directory
# echo "This is an original file" > old.file
# cat old.file
This is an original file
# stat old.file
File: `old.file'
Size: 25 Blocks: 8 IO Block: 4096 regular file
Device: 807h/2055d Inode: 660650 Links: 2
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
...
// 檔案有相同的 inode 号以及 data block
# link old.file hard.link | ls -li
total 8
660650 -rw-r--r-- 2 root root 25 Sep 1 17:44 hard.link
660650 -rw-r--r-- 2 root root 25 Sep 1 17:44 old.file
// 不能交叉檔案系統
# ln /dev/input/event5 /root/bfile.txt
ln: failed to create hard link `/root/bfile.txt' => `/dev/input/event5':
Invalid cross-device link
// 不能對目錄進行建立硬連接配接
# mkdir -p old.dir/test
# ln old.dir/ hardlink.dir
ln: `old.dir/': hard link not allowed for directory
# ls -iF
660650 hard.link 657948 old.dir/ 660650 old.file
檔案 old.file 與 hard.link 有着相同的 inode 号:660650 及檔案權限,inode 是随着檔案的存在而存在,是以隻有當檔案存在時才可建立硬連結,即當 inode 存在且連結計數器(link count)不為 0 時。inode 号僅在各檔案系統下是唯一的,當 Linux 挂載多個檔案系統後将出現 inode 号重複的現象(如 清單 5.所示,檔案 t3.jpg、sync 及 123.txt 并無關聯,卻有着相同的 inode 号),是以硬連結建立時不可跨檔案系統。裝置檔案目錄 /dev 使用的檔案系統是 devtmpfs,而 /root(與根目錄 / 一緻)使用的是磁盤檔案系統 ext4。清單 5.展示了使用指令 df 檢視目前系統中挂載的檔案系統類型、各檔案系統 inode 使用情況及檔案系統挂載點。
清單 5. 查找有相同 inode 号的檔案
# df -i --print-type
Filesystem Type Inodes IUsed IFree IUse% Mounted on
/dev/sda7 ext4 3147760 283483 2864277 10% /
udev devtmpfs 496088 553 495535 1% /dev
tmpfs tmpfs 499006 491 498515 1% /run
none tmpfs 499006 3 499003 1% /run/lock
none tmpfs 499006 15 498991 1% /run/shm
/dev/sda6 fuseblk 74383900 4786 74379114 1% /media/DiskE
/dev/sda8 fuseblk 29524592 19939 29504653 1% /media/DiskF
# find / -inum 1114
/media/DiskE/Pictures/t3.jpg
/media/DiskF/123.txt
/bin/sync
值得一提的是,Linux 系統存在 inode 号被用完但磁盤空間還有剩餘的情況。我們建立一個 5M 大小的 ext4 類型的 mo.img 檔案,并将其挂載至目錄 /mnt。然後我們使用一個 shell 腳本将挂載在 /mnt 下 ext4 檔案系統的 indoe 耗盡(見清單 6.)。
清單 6. 測試檔案系統 inode 耗盡但仍有磁盤空間的情景
# dd if=/dev/zero of=mo.img bs=5120k count=1
# ls -lh mo.img
-rw-r--r-- 1 root root 5.0M Sep 1 17:54 mo.img
# mkfs -t ext4 -F ./mo.img
...
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
Stride=0 blocks, Stripe width=0 blocks
1280 inodes, 5120 blocks
256 blocks (5.00%) reserved for the super user
...
...
Writing superblocks and filesystem accounting information: done
# mount -o loop ./mo.img /mnt
# cat /mnt/inode_test.sh
#!/bin/bash
for ((i = 1; ; i++))
do
if [ $? -eq 0 ]; then
echo "This is file_$i" > file_$i
else
exit 0
fi
done
# ./inode_test.sh
./inode_test.sh: line 6: file_1269: No space left on device
# df -iT /mnt/; du -sh /mnt/
Filesystem Type Inodes IUsed IFree IUse% Mounted on
/dev/loop0 ext4 1280 1280 0 100% /mnt
1.3M /mnt/
硬連結不能對目錄建立是受限于檔案系統的設計(見 清單 4.對目錄建立硬連結将失敗)。現 Linux 檔案系統中的目錄均隐藏了兩個個特殊的目錄:目前目錄(.)與父目錄(..)。檢視這兩個特殊目錄的 inode 号可知其實這兩目錄就是兩個硬連結(注意目錄 /mnt/lost+found/ 的 inode 号)。若系統允許對目錄建立硬連結,則會産生目錄環。
# ls -aliF /mnt/lost+found
total 44
11 drwx------ 2 root root 12288 Sep 1 17:54 ./
2 drwxr-xr-x 3 root root 31744 Sep 1 17:57 ../
# stat /mnt/lost+found/
File: `/mnt/lost+found/'
Size: 12288 Blocks: 24 IO Block: 1024 directory
Device: 700h/1792d Inode: 11 Links: 2
Access: (0700/drwx------) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2012-09-01 17:57:17.000000000 +0800
Modify: 2012-09-01 17:54:49.000000000 +0800
Change: 2012-09-01 17:54:49.000000000 +0800
Birth: -
軟連結與硬連結不同,若檔案使用者資料塊中存放的内容是另一檔案的路徑名的指向,則該檔案就是軟連接配接。軟連結就是一個普通檔案,隻是資料塊内容有點特殊。軟連結有着自己的 inode 号以及使用者資料塊(見 圖 2.)。是以軟連結的建立與使用沒有類似硬連結的諸多限制:
- 軟連結有自己的檔案屬性及權限等;
- 可對不存在的檔案或目錄建立軟連結;
- 軟連結可交叉檔案系統;
- 軟連結可對檔案或目錄建立;
- 建立軟連結時,連結計數 i_nlink 不會增加;
- 删除軟連結并不影響被指向的檔案,但若被指向的原檔案被删除,則相關軟連接配接被稱為死連結(即 dangling link,若被指向路徑檔案被重新建立,死連結可恢複為正常的軟連結)。
圖 2. 軟連結的通路
清單 7. 軟連結特性展示
# ls -li
total 0
// 可對不存在的檔案建立軟連結
# ln -s old.file soft.link
# ls -liF
total 0
789467 lrwxrwxrwx 1 root root 8 Sep 1 18:00 soft.link -> old.file
// 由于被指向的檔案不存在,此時的軟連結 soft.link 就是死連結
# cat soft.link
cat: soft.link: No such file or directory
// 建立被指向的檔案 old.file,soft.link 恢複成正常的軟連結
# echo "This is an original file_A" >> old.file
# cat soft.link
This is an original file_A
// 對不存在的目錄建立軟連結
# ln -s old.dir soft.link.dir
# mkdir -p old.dir/test
# tree . -F --inodes
.
├── [ 789497] old.dir/
│ └── [ 789498] test/
├── [ 789495] old.file
├── [ 789495] soft.link -> old.file
└── [ 789497] soft.link.dir -> old.dir/
當然軟連結的使用者資料也可以是另一個軟連結的路徑,其解析過程是遞歸的。但需注意:軟連結建立時原檔案的路徑指向使用絕對路徑較好。使用相對路徑建立的軟連結被移動後該軟連結檔案将成為一個死連結(如下所示的軟連結 a 使用了相對路徑,是以不宜被移動),因為連結資料塊中記錄的亦是相對路徑指向。
$ ls -li
total 2136
656627 lrwxrwxrwx 1 harris harris 8 Sep 1 14:37 a -> data.txt
656662 lrwxrwxrwx 1 harris harris 1 Sep 1 14:37 b -> a
656228 -rw------- 1 harris harris 2186738 Sep 1 14:37 data.txt 6
回頁首
連結相關指令
在 Linux 中檢視目前系統已挂着的檔案系統類型,除上述使用的指令 df,還可使用指令 mount 或檢視檔案 /proc/mounts。
# mount
/dev/sda7 on / type ext4 (rw,errors=remount-ro)
proc on /proc type proc (rw,noexec,nosuid,nodev)
sysfs on /sys type sysfs (rw,noexec,nosuid,nodev)
...
...
none on /run/shm type tmpfs (rw,nosuid,nodev)
指令 ls 或 stat 可幫助我們區分軟連結與其他檔案并檢視檔案 inode 号,但較好的方式還是使用 find 指令,其不僅可查找某檔案的軟連結,還可以用于查找相同 inode 的所有硬連結。(見清單 8.)
清單 8. 使用指令 find 查找軟連結與硬連結
// 查找在路徑 /home 下的檔案 data.txt 的軟連結
# find /home -lname data.txt
/home/harris/debug/test2/a
// 檢視路徑 /home 有相同 inode 的所有硬連結
# find /home -samefile /home/harris/debug/test3/old.file
/home/harris/debug/test3/hard.link
/home/harris/debug/test3/old.file
# find /home -inum 660650
/home/harris/debug/test3/hard.link
/home/harris/debug/test3/old.file
// 列出路徑 /home/harris/debug/ 下的所有軟連結檔案
# find /home/harris/debug/ -type l -ls
656662 0 lrwxrwxrwx 1 harris harris 1 Sep 1 14:37 /home/harris/debug/test2/b -> a
656627 0 lrwxrwxrwx 1 harris harris 8 Sep 1 14:37 /home/harris/debug/test2/a ->
data.txt
789467 0 lrwxrwxrwx 1 root root 8 Sep 1 18:00 /home/harris/debug/test/soft.link ->
old.file
789496 0 lrwxrwxrwx 1 root root 7 Sep 1 18:01
/home/harris/debug/test/soft.link.dir -> old.dir
系統根據磁盤的大小預設設定了 inode 的值(見清單 9.),如若必要,可在格式檔案系統前對該值進行修改。如鍵入指令
mkfs -t ext4 -I 512
/
dev/sda4,
将使磁盤裝置 /dev/sda4 格式成 inode 大小是 512 位元組的 ext4 檔案系統。
清單 9. 檢視系統的 inode 值
清單 9. 檢視系統的 inode 值
// 檢視磁盤分區 /dev/sda7 上的 inode 值
# dumpe2fs -h /dev/sda7 | grep "Inode size"
dumpe2fs 1.42 (29-Nov-2011)
Inode size: 256
# tune2fs -l /dev/sda7 | grep "Inode size"
Inode size: 256
回頁首
Linux VFS
Linux VFS
Linux 有着極其豐富的檔案系統,大體上可分如下幾類:
-
網絡檔案系統,如 nfs、cifs 等;
-
磁盤檔案系統,如 ext4、ext3 等;
-
特殊檔案系統,如 proc、sysfs、ramfs、tmpfs 等。
實作以上這些檔案系統并在 Linux 下共存的基礎就是 Linux VFS(Virtual File System 又稱 Virtual Filesystem Switch),即虛拟檔案系統。VFS 作為一個通用的檔案系統,抽象了檔案系統的四個基本概念:檔案、目錄項 (dentry)、索引節點 (inode) 及挂載點,其在核心中為使用者空間層的檔案系統提供了相關的接口(見 圖 3.所示 VFS 在 Linux 系統的架構)。VFS 實作了 open()、read() 等系統調并使得 cp 等使用者空間程式可跨檔案系統。VFS 真正實作了上述内容中:在 Linux 中除程序之外一切皆是檔案。
圖 3. VFS 在系統中的架構
圖 3. VFS 在系統中的架構
Linux硬連結和符号連結
Linux VFS 存在四個基本對象:超級塊對象 (superblock object)、索引節點對象 (inode object)、目錄項對象 (dentry object) 及檔案對象 (file object)。超級塊對象代表一個已安裝的檔案系統;索引節點對象代表一個檔案;目錄項對象代表一個目錄項,如裝置檔案 event5 在路徑 /dev/input/event5 中,其存在四個目錄項對象:/ 、dev/ 、input/ 及 event5。檔案對象代表由程序打開的檔案。這四個對象與程序及磁盤檔案間的關系如圖 4. 所示,其中 d_inode 即為硬連結。為檔案路徑的快速解析,Linux VFS 設計了目錄項緩存(Directory Entry Cache,即 dcache)。
圖 4. VFS 的對象之間的處理
回頁首
Linux 檔案系統中的 inode
在 Linux 中,索引節點結構存在于系統記憶體及磁盤,其可區分成 VFS inode 與實際檔案系統的 inode。VFS inode 作為實際檔案系統中 inode 的抽象,定義了結構體 inode 與其相關的操作 inode_operations(見核心源碼 include/linux/fs.h)。
清單 10. VFS 中的 inode 與 inode_operations 結構體
struct inode {
...
const struct inode_operations *i_op; // 索引節點操作
unsigned long i_ino; // 索引節點号
atomic_t i_count; // 引用計數器
unsigned int i_nlink; // 硬連結數目
...
}
struct inode_operations {
...
int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
int (*link) (struct dentry *,struct inode *,struct dentry *);
int (*unlink) (struct inode *,struct dentry *);
int (*symlink) (struct inode *,struct dentry *,const char *);
int (*mkdir) (struct inode *,struct dentry *,int);
int (*rmdir) (struct inode *,struct dentry *);
...
}
如清單 10. 所見,每個檔案存在兩個計數器:i_count 與 i_nlink,即引用計數與硬連結計數。結構體 inode 中的 i_count 用于跟蹤檔案被通路的數量,而 i_nlink 則是上述使用 ls -l 等指令檢視到的檔案硬連結數。或者說 i_count 跟蹤檔案在記憶體中的情況,而 i_nlink 則是磁盤計數器。當檔案被删除時,則 i_nlink 先被設定成 0。檔案的這兩個計數器使得 Linux 系統更新或程式更新變的容易。系統或程式可在不關閉的情況下(即檔案 i_count 不為 0),将新檔案以同樣的檔案名進行替換,新檔案有自己的 inode 及 data block,舊檔案會在相關程序關閉後被完整的删除。
清單 11. 檔案系統 ext4 中的 inode
struct ext4_inode {
...
__le32 i_atime; // 檔案内容最後一次通路時間
__le32 i_ctime; // inode 修改時間
__le32 i_mtime; // 檔案内容最後一次修改時間
__le16 i_links_count; // 硬連結計數
__le32 i_blocks_lo; // Block 計數
__le32 i_block[EXT4_N_BLOCKS]; // 指向具體的 block
...
};
清單 11. 展示的是檔案系統 ext4 中對 inode 的定義(見核心源碼 fs/ext4/ext4.h)。其中三個時間的定義可對應與指令 stat 中檢視到三個時間。i_links_count 不僅用于檔案的硬連結計數,也用于目錄的子目錄數跟蹤(目錄并不顯示硬連結數,指令 ls -ld 檢視到的是子目錄數)。由于檔案系統 ext3 對 i_links_count 有限制,其最大數為:32000(該限制在 ext4 中被取消)。嘗試在 ext3 檔案系統上驗證目錄子目錄及普通檔案硬連結最大數可見 清單 12. 的錯誤資訊。是以實際檔案系統的 inode 之間及與 VFS inode 相較是有差異的。
清單 12. 檔案系統 ext3 中 i_links_count 的限制
# ./dirtest.sh
mkdir: cannot create directory `dir_31999': Too many links
# ./linkcount.sh
ln: failed to create hard link to `old.file': Too many links