Ba khái niệm này thường được giải thích bằng sơ đồ. Bài này đo giới hạn thật của chúng.
Hết ổ đĩa với 71% dung lượng còn trống
Tạo tệp rỗng trên ext4 tới khi không tạo được nữa:
dừng ở tệp thứ 131.058
No space left on device
dung lượng: 516 MB / 2,0 GB (29%) <- CÒN TRỐNG
inode : 131.072 / 131.072 (100%) <- ĐÃ HẾT
Ổ đĩa còn 71%. Thông báo lỗi nói "hết chỗ", và nó không nhắc gì tới inode.
Mỗi tệp cần một inode — cấu trúc chứa quyền, chủ sở hữu, thời gian, và danh sách khối dữ liệu. Tên tệp không nằm trong inode; nó nằm trong thư mục, cùng với số hiệu inode.
ext4 chốt số inode lúc mkfs và không đổi được sau đó. Cùng phép thử trên xfs:
xfs tạo hết 200.000 tệp, không báo lỗi
inode: 200.006 / 1.048.576 (20%)
xfs cấp inode động. btrfs cũng vậy — df -i trên btrfs báo 0 vì không có con số cố định nào để báo.
Đây là một trong những sự cố khó chẩn đoán nhất trên máy chủ: thư mục hàng đợi thư, bộ đệm phiên, thư mục tệp tạm ngập tệp nhỏ, và ứng dụng bắt đầu lỗi ENOSPC trong khi df -h hiện màu xanh.
df -i # LUON kiem tra cai nay khi thay ENOSPC
Đặt trước lúc tạo hệ tệp nếu biết sẽ có nhiều tệp nhỏ:
mkfs.ext4 -i 4096 /dev/sdb1 # mot inode moi 4 KB thay vi moi 16 KB
mkfs.ext4 -N 5000000 /dev/sdb1 # hoac dat thang so luong
Không sửa được sau khi đã tạo — phải định dạng lại.
Trần số liên kết cứng
Tạo liên kết cứng tới khi hỏng:
| Hệ tệp | Dừng ở | Lỗi |
|---|---|---|
| ext4 | 65.000 | EMLINK — Too many links |
| btrfs | 65.535 | EMLINK |
| xfs | vượt 70.001 | chưa chạm trần |
ext4 lưu bộ đếm liên kết trong 16 bit và giữ lại một khoảng; xfs dùng 32 bit nên trần cao tới mức thực tế không gặp.
Con số này ảnh hưởng tới các công cụ sao lưu kiểu liên kết cứng để chia sẻ dữ liệu chung — rsnapshot, rsync --link-dest, Time Machine. Một tệp không đổi qua 65.000 bản sao lưu là kịch bản có thật với sao lưu theo giờ trong bảy năm.
Liên kết cứng không tốn gì, liên kết mềm tốn theo tầng
200.000 lần stat(), đo bằng chương trình C:
| Đường dẫn | ns mỗi lần | So với tệp gốc |
|---|---|---|
| Tệp gốc | 235 | 1,00× |
| Liên kết cứng | 244 | 1,04× |
| Liên kết mềm, 1 mức | 321 | 1,37× |
| Liên kết mềm, 2 mức | 399 | 1,70× |
| Liên kết mềm, 4 mức | 558 | 2,37× |
| Liên kết mềm, 8 mức | 879 | 3,74× |
| Liên kết mềm, 16 mức | 1.506 | 6,41× |
Liên kết cứng bằng đúng tệp gốc — vì nó là tệp gốc. Một inode có nhiều tên; không tên nào là "bản gốc", và stat không phải làm gì thêm.
Liên kết mềm là một tệp riêng chứa một chuỗi đường dẫn. Mỗi mức cộng thêm khoảng 80 ns: đọc nội dung liên kết, rồi bắt đầu lại việc tra tên từ đầu.
Trong đường dẫn nóng của một dịch vụ mở hàng chục nghìn tệp mỗi giây, một chuỗi liên kết mềm bốn tầng — chuyện rất thường gặp với /etc/alternatives hoặc thư mục do trình quản lý gói dựng — nhân đôi chi phí mở tệp.
Trần chuỗi liên kết mềm là đúng 40
chuỗi 39 mức: regular file
chuỗi 40 mức: regular file
chuỗi 41 mức: Too many levels of symbolic links
ELOOP không chỉ dành cho vòng lặp. Một chuỗi thẳng dài quá 40 cũng bị từ chối, và thông báo lỗi vẫn nói "vòng lặp" — đủ để dẫn người gỡ lỗi đi sai hướng.
Sai lầm khi đo: stat không đi theo liên kết
Lần đo đầu tiên tôi dùng lệnh stat của shell và nó báo chuỗi 200 mức vẫn đọc được. Con số đó mâu thuẫn với mọi tài liệu, nên tôi kiểm lại:
stat KHÔNG -L: s60 -> symbolic link
stat -L : chuỗi 60 mức -> Too many levels of symbolic links
GNU stat mặc định không đi theo liên kết. Nó mô tả chính liên kết đó, nên chuỗi dài bao nhiêu cũng "thành công" — nó có bao giờ đi đâu.
Nghe hiển nhiên khi đã biết, nhưng lúc đo thì nó lặng lẽ cho ra một kết quả có vẻ hợp lý. Thứ cứu tôi là con số 200 quá tròn trịa so với hằng số 40 mà tôi từng đọc — lại một lần nữa, dấu hiệu cảnh báo là con số không khớp với hiểu biết nền, không phải một thông báo lỗi.
Ba lệnh, ba hành vi khác nhau:
| Lệnh | Đi theo liên kết? |
|---|---|
stat tep |
Không |
stat -L tep |
Có |
ls -l tep |
Không (hiện mũi tên) |
cat tep |
Có |
du tep |
Không (đếm chính liên kết) |
du -L tep |
Có |
Chọn cái nào
| Liên kết cứng | Liên kết mềm | |
|---|---|---|
| Qua được hệ tệp khác | ❌ | ✅ |
| Trỏ tới thư mục | ❌ | ✅ |
| Chi phí tra tên | 0 | ~80 ns mỗi mức |
| Xoá bản gốc | dữ liệu còn | thành liên kết hỏng |
Thấy được bằng ls -l |
❌ không phân biệt được | ✅ |
Điểm cuối đáng nhớ: không có cách nào nhìn một tệp mà biết nó có liên kết cứng hay không ngoài việc đọc st_nlink:
stat -c '%h liên kết, inode %i' tep
find /duong/dan -samefile tep # tim moi ten khac cua cung inode
find /duong/dan -links +1 -type f # tim tep co nhieu hon mot ten
Đó cũng là lý do liên kết cứng gây bất ngờ khi sửa tệp: sửa qua tên này là sửa luôn nội dung thấy qua tên kia. Trình soạn thảo nào ghi bằng cách tạo tệp mới rồi đổi tên — phần lớn đều vậy — sẽ phá liên kết cứng mà không báo gì.
Thử ba mươi giây
Kiểm tra máy bạn có sắp hết inode không, và tìm thủ phạm:
df -i | awk 'NR==1 || ($5+0) > 50'
# thu muc nao nhieu tep nhat
for d in /var/lib /var/log /var/spool /tmp /home; do
[ -d "$d" ] && printf "%9s %s\n" "$(find $d -xdev 2>/dev/null | wc -l)" "$d"
done | sort -rn
# tep nao co nhieu ten (lien ket cung)
find / -xdev -type f -links +1 -printf '%n %p\n' 2>/dev/null | sort -rn | head -5
Cột thứ năm của df -i trên 80% là lúc cần hành động, và hành động đó thường là xoá tệp nhỏ, không phải mở rộng ổ đĩa — mở rộng ổ đĩa không thêm inode nào cho ext4.
Phần sau: bộ nhớ ánh xạ tệp — đo mmap so với read.