当我们在 Linux 或 Unix 系统上使用 ls -l
命令列出目录的内容时,第一行通常会显示一个 total
后跟一个数字。这个数字表示当前目录下所有文件和子目录使用的总块数。
majn@tiger:~/C_Project$ ll -lh
total 100K
drwxrwxr-x 18 majn majn 4.0K 9月 28 11:58 ./
drwxr-x--- 29 majn csser 4.0K 9月 28 13:59 ../
-rwxrwxr-x 1 majn majn 1.5K 8月 10 12:26 a.out*
......
具体来说:
这里的“块”是文件系统存储的基本单位。不同的文件系统和不同的系统配置可能会有不同的块大小,但在很多文件系统上,块的大小是 512 字节、4096 字节或其他大小。
total
后面显示的数字是所有列出的文件和目录占用的块数的总和。它考虑了文件系统中的各种因素,如间接块和元数据。
ls
命令显示的这个值实际上是 st_blocks
字段的值,它是 stat
结构体中的一个字段。这个字段表示的是文件大小按照块大小(通常是 512 字节)进行四舍五入后的结果。
要注意的是,这个数字不仅仅是简单地将目录下所有文件的大小加起来,因为它还包括了其他文件系统的开销。
例如,如果有两个大小为 100 字节的文件,它们可能各自占用一个 4096 字节的块(取决于文件系统和配置)。因此,ls
显示的 total
可能远大于这两个文件的实际总大小。
在 UNIX-like 系统中,stat
结构体提供了关于文件的信息。这些信息大多是文件的元数据,如文件大小、权限、所有者、时间戳等。可以使用 stat
系统调用来获取文件的这些信息,并将结果存储在 stat
结构体中。
以下是 stat
结构体的一般形式(取决于特定的平台和版本,字段可能会有所不同):
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 filesystem 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 */
};
下面是这些字段的简要描述:
在某些系统上,时间戳可能会有更高的精度,例如 st_atim
, st_mtim
, st_ctim
,它们可能是 timespec
结构体类型,提供了纳秒级的精度。
要获取文件的 stat
信息,可以使用 stat()
函数或其变体(如 fstat()
或 lstat()
)。例如:
struct stat sb;
if (stat("/path/to/file", &sb) == -1) {
perror("stat");
exit(EXIT_FAILURE);
}
上述代码将 “/path/to/file” 的 stat
信息存储在 sb
结构体中。
stat
结构体和 inode 之间的关系非常紧密。简而言之,stat
结构体为程序提供了对 inode 中存储的信息的访问。先来看看 inode 的定义以便更好地理解它们之间的关系。
在 UNIX 和 UNIX-like 系统中,每个文件都有一个与之关联的 inode(索引节点)。inode 包含关于文件的元数据,例如:
每个 inode 在文件系统中都有一个唯一的编号,称为 inode 号。
当使用 stat()
, fstat()
, 或 lstat()
系统调用时,它们会填充一个 stat
结构体,该结构体包含与指定文件相关的信息。这些信息实际上是从文件的 inode 中取得的。也就是说,stat
结构体提供了一个方便的方式来在程序中访问 inode 的信息。
所以,可以说 stat
结构体是 inode 信息的一个映射或表示,它使得应用程序能够轻松访问 inode 中的数据,而不必直接与底层文件系统交互。每个 stat
结构体字段大多都对应于 inode 中的一个或多个属性。
例如,stat
结构体中的 st_mode
字段表示文件的权限和类型,这些都存储在 inode 中。同样,st_ino
字段表示文件的 inode 号,st_uid
和 st_gid
字段分别表示文件所有者的用户 ID 和组 ID,这些都是直接从 inode 中取得的。
总之,stat
结构体为程序提供了一个接口,使其能够访问和操作 inode 中的信息。
在大多数常见的文件系统中,一个块(或扇区)通常只能由一个文件占用。也就是说,如果一个文件只占用块的一部分,那么这个块的剩余部分通常是浪费的,并不能被另一个文件使用。
举个例子,假设文件系统的块大小为4KB:
这样的设计简化了文件系统的设计和数据的读取,因为每个块的地址指向一个确定的文件。
然而,需要注意的是有一种称为"尾部合并"或"块共享"的优化技术,可以允许两个文件共享一个块,但这通常在特定的情境下,如某些复制写入 (Copy-On-Write, COW) 文件系统中可能会看到这种情况。
总体而言,尽管技术上可能有办法让多个文件共享一个块,但在大多数传统的文件系统中,这是不允许的,因为它会增加管理复杂性并可能导致性能下降。
尾部合并或块共享是某些文件系统中使用的技术,尤其是在那些支持复制写入 (Copy-On-Write, COW) 机制的文件系统中。这种技术旨在优化存储使用和提高空间效率。
在许多文件系统中,由于块的大小固定,小文件和文件的尾部数据往往不会完全填充一个完整的块。在不使用尾部合并的传统文件系统中,这会导致存储浪费。
尾部合并的思想是将多个文件的这些"尾部"数据合并到单个存储块中,从而减少因未完全利用的块而造成的空间浪费。
COW 是一种优化技术,在写入数据之前,不是直接修改原始数据,而是复制原始数据,并在副本上执行修改。这种方法的优点是它提供了一种原子性的写入方式,可以在系统崩溃时保护数据的完整性,并为高效的快照和其他功能提供基础。
在某些使用 COW 机制的文件系统中,尾部合并可以很自然地实现。当文件进行修改时,而不是修改原始块,系统可以简单地将新数据和原始块的尾部数据合并到新块中。由于不直接修改原始数据,这种合并操作变得相对简单和高效。
例如,在 Btrfs 这种 COW 文件系统中,当多个小文件或文件尾部数据被写入到同一个数据块中时,可以实现尾部合并。这不仅优化了存储,还减少了因多次小写入操作而导致的性能开销。
尽管尾部合并和 COW 可以提供存储和性能上的优势,但它们也带来了额外的复杂性。例如,当执行文件删除或修改操作时,文件系统必须跟踪和管理块中的哪一部分属于哪个文件,并确保数据的完整性和一致性。但是,在权衡后,这些技术在很多现代文件系统中仍被认为是有价值的,并为提高存储效率和性能提供了有效的手段。
下面,我们通过一个简单的例子来解释尾部合并和 COW 如何工作。
假设我们有一个文件系统,其块大小为4KB。
结合尾部合并和 COW,一个文件系统可以在添加、删除或修改小文件时,有效地利用和管理存储空间,同时确保数据的安全性和完整性。