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.

Giới hạn của inode và liên kết

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 chungrsnapshot, 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ó 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
ls -l tep Không (hiện mũi tên)
cat tep
du tep Không (đếm chính liên kết)
du -L tep

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.