前言
此篇文章为学习 Linux系统编程02:文件系统
部分的笔记
1. 文件存储
1.1 inode
inode
是 Linux 和 Unix 操作系统中的一个重要概念,它是文件系统中的一个 数据结构
,用于存储文件的元数据。每个文件和目录都有一个对应的 inode
来描述其属性和位置信息。
inode
结构包含了以下信息:不包含文件名
- 文件类型:标识文件是普通文件、目录、符号链接等类型。
- 文件权限:文件的读、写、执行权限。
- 文件所有者和所属组:标识文件的所有者和所属的用户组。
- 文件大小:文件的大小,以字节为单位。
- 文件时间戳:记录文件的创建时间、修改时间和访问时间等。
- 文件链接数:记录文件的硬链接数目。
- 数据块指针:指向文件存储数据的数据块。
当系统中创建一个新文件时,会为该文件分配一个唯一的 inode
,然后将文件的实际数据存储在数据块中,并将 inode
中的数据块指针指向这些数据块。在文件被访问或修改时,通过 inode
可以快速定位文件的数据块,而不需要遍历整个文件系统。
通过 ls -i
命令可以查看文件的 inode
号码。每个文件和目录在同一文件系统中具有唯一的 inode
号码。
inode
在文件系统的性能和管理中起着关键作用,它使得文件系统能够高效地管理文件和目录,并支持硬链接的使用。
1.2 dentry
dentry
, (即 directory entry
目录项)是 Linux 文件系统中的一个重要概念,用于管理文件系统中的目录。dentry
与 inode
相关联,共同组成了 Linux 文件系统中的目录项。其本质依然是结构体,重要成员变量有两个 {文件名,inode,...}
,而文件内容 (data) 保存在磁盘盘块中
每个目录都包含一个或多个 dentry
,每个 dentry
表示一个目录中的文件或子目录。dentry
记录了文件或目录的名称、文件类型和对应的 inode
号码等信息。当用户访问文件时,Linux 文件系统会通过 dentry
来快速定位文件的 inode
,从而访问文件的实际数据。
在 Linux 文件系统中,dentry
会被缓存在内存中,以提高文件系统的性能。当用户访问文件时,系统首先会查找该文件对应的 dentry
是否已经缓存,如果已经缓存,则直接从 dentry
中获取 inode
信息,避免了不必要的磁盘访问。如果文件对应的 dentry
不在缓存中,系统会通过目录索引进行查找,并将找到的 dentry
缓存起来,以便下次快速访问。
dentry
与目录层次结构一起形成了文件系统的层次结构,通过 dentry
可以在文件系统中快速定位文件和目录,提高了文件系统的访问效率和性能。同时,dentry
的缓存机制也减少了不必要的磁盘访问,提高了整个文件系统的效率。
1.3 文件系统
文件系统是一组规则,规定对文件的存储及读取的一般方法。文件系统在磁盘格式化过程中指定。
以下为常见文件系统:
- FAT32(File Allocation Table 32):FAT32 是一种较旧的文件系统,广泛应用于可移动介质(如 USB 闪存驱动器、SD 卡等)。它是 Windows 系统和其他操作系统的通用文件系统。
- NTFS(New Technology File System):NTFS 是 Windows 操作系统中使用的主要文件系统。它支持大文件和文件系统,并提供更高级的权限控制和数据安全性。
- exFAT(Extended File Allocation Table):exFAT 是 FAT32 文件系统的改进版本,特别设计用于支持更大的文件和分区。它通常在移动存储设备和外部驱动器中使用。
- ext2(Second Extended File System):ext2 是 Linux 系统早期的文件系统,不具备日志功能。虽然现在很少使用,但仍然是一些老旧系统的选择。
- ext3(Third Extended File System):ext3 是 ext2 文件系统的改进版本,具有日志记录功能,可提供更好的数据完整性和恢复能力。
- ext4(Fourth Extended File System):ext4 是 Linux 系统中目前最常用的文件系统,它是 ext3 文件系统的进一步改进,提供更高的性能和可靠性。ext4 支持更大的文件和文件系统,并具备更高级的特性。
1.4 硬链接、软连接
硬链接
和 软链接
(又称软连接)是 Linux 文件系统中两种不同类型的链接方式,用于在文件系统中创建文件或目录之间的关联。
硬链接(Hard Link):
- 硬链接是目录项(dentry)中指向相同 inode 号的不同目录项。
- 通过硬链接,多个文件名可以指向同一个数据块,实际上是同一个文件的不同访问入口。
- 硬链接创建后,可以像普通文件一样操作,读写内容,删除等,但是不能对目录进行硬链接。
- 硬链接不能跨文件系统创建,即硬链接必须位于同一个文件系统。
软链接(Symbolic Link / Soft Link):
- 软链接是一个特殊的文件,它包含了指向另一个文件或目录的路径名。
- 软链接类似于 Windows 系统的快捷方式,它只是一个指向目标的快捷方式而已。
- 软链接可以跨文件系统创建,因为它只保存了目标文件或目录的路径名。
- 删除软链接并不会影响目标文件或目录,但如果目标文件或目录被删除,软链接将变为 "断链"。
对比:
- 硬链接是多个目录项指向同一个 inode,它们是文件系统中同一个文件的不同名字,文件大小和权限都是相同的。
- 软链接是一个特殊的文件,它保存了指向目标文件或目录的路径名,它是目标文件或目录的 "快捷方式",不占用实际数据块。
注意事项:
- 删除硬链接或软链接并不会删除目标文件本身。
- 硬链接不能跨文件系统创建,而软链接可以。
示例:
2. 文件操作
2.1 stat、lstat 函数
概念
stat
和 lstat
函数都用于获取文件或目录的信息,但在处理符号链接时有所不同。
stat
函数:- 函数原型:
int stat(const char *path, struct stat *buf);
- 描述:
stat
函数通过指定的文件路径获取文件信息,并将结果存储在struct stat
类型的结构体buf
中。如果path
是一个符号链接,stat
函数将会获取符号链接指向的文件的信息。 - 返回值:成功时返回 0,失败时返回 - 1。
- 函数原型:
lstat
函数:- 函数原型:
int lstat(const char *path, struct stat *buf);
- 描述:
lstat
函数与stat
函数类似,也用于获取文件信息。不同之处在于,lstat
函数不会跟随符号链接,而是获取符号链接本身的信息,而不是它所指向的文件的信息。 - 返回值:成功时返回 0,失败时返回 - 1。
- 函数原型:
这两个函数对于获取文件的权限、大小、时间戳等信息非常有用,而在处理符号链接时,使用 lstat
函数可以避免不必要的问题。在编写程序时,需要根据具体需求选择使用 stat
函数还是 lstat
函数。
代码
stat.c
:查看文件大小
lstat.c
: 查看文件属性
2.2 link、unlink 函数
概念
link
函数和 unlink
函数用于创建硬链接和删除文件链接(硬链接或符号链接)。
link
函数:link
函数用于创建硬链接。硬链接是指在文件系统中创建一个新的链接指向同一个文件,这个新链接和原文件具有相同的 inode 号和数据块,但是在目录中显示为一个新的文件名。它的原型为:参数说明:
oldpath
:源文件路径名,即要创建硬链接的文件。newpath
:目标文件路径名,即新创建的硬链接的文件名。
返回值:
- 如果成功创建硬链接,返回 0。
- 如果出现错误,返回 - 1,并设置
errno
来指示错误类型。
unlink
函数:unlink
函数用于删除一个文件链接。如果删除的是硬链接,只会删除该链接,而不会删除原文件;如果删除的是符号链接,会删除链接指向的原文件。它的原型为:参数说明:
pathname
:要删除的文件路径名,可以是硬链接或符号链接。
返回值:
- 如果成功删除文件链接,返回 0。
- 如果出现错误,返回 - 1,并设置
errno
来指示错误类型。
这两个函数在 Linux 系统编程中经常用于文件链接的创建和删除操作。需要注意的是,link
函数只能用于同一个文件系统内的文件,而不能跨文件系统创建硬链接。对于跨文件系统的文件链接,可以使用符号链接(符号链接是指在文件系统中创建一个新的文件,它指向另一个文件的路径)来实现。
思考,为什么目录项要游离于 inode 之外,画蛇添足般的将文件名单独存储呢?这样 的存储方式有什么样的好处呢? 其目的是为了实现文件共享。
Linux 允许多个目录项共享一个 inode,即共享盘块 (data)。 不同文件名,在人类眼中将它理解成两个文件,但是在内核眼里是同一个文件
link 函数,可以为已经存在的文件创建目录项 (硬链接)。unlink 函数则是删除一个文件的目录项
mv 命令即是修改了目录项,而并不修改文件本身。
代码
注意 Linux 下删除文件的机制:不断将 st_nlink
-1,直至减到 0 为止。无目录项对应的 文件,将会被操作系统择机释放。(具体时间由系统内部调度算法决定) 因此,我们删除文件,从某种意义上说,只是让文件具备了被释放的条件。
unlink 函数的特征:清除文件时,如果文件的硬链接数到 0 了,没有 dentry 对应,但该 文件仍不会马上被释放。要等到所有打开该文件的进程关闭该文件,系统才会挑时间将该文 件释放掉。
mymv.c
:编程实现 mv 命令的改名操作
unlink.c
:通过观察临时文件 temp.txt
存在情况,了解 unlink 函数以及删除文件机制
2.3 隐式回收
当进程结束运行时,所有该进程打开的文件会被关闭,申请的内存空间会被释放。系统的这一特性称之为隐式回收系统资源。
写程序时一定不能忘记关闭文件
2.4 其他函数
readlink 函数
在 Linux 中,readlink
是一个命令行工具和系统调用,用于读取符号链接(Symbolic Link)所指向的目标路径。
命令行工具:
readlink
命令用于查看符号链接的目标路径。使用方法如下:其中,
LINK_PATH
是符号链接的路径。readlink
会输出该符号链接所指向的目标路径。系统调用:
readlink
也是一个系统调用,用于在 C/C++ 程序中访问符号链接的目标路径。函数原型:
参数说明:
path
:符号链接的路径。buf
:用于存储目标路径的缓冲区。
bufsiz
:缓冲区的大小,应该足够大以容纳目标路径的字符。
返回值:
- 成功时,返回读取的目标路径的长度(不包括终止空字符),如果目标路径长度大于
bufsiz
,则返回-1
。 - 失败时,返回
-1
,并设置errno
来指示错误类型。
rename 函数
rename
函数是一个 C 标准库函数,用于对文件或目录进行重命名。它在 <stdio.h>
头文件中声明,并且是一个较为简单的文件操作函数。
函数原型:
参数说明:
old_path
:旧的文件名或目录名。new_path
:新的文件名或目录名。
返回值:
- 如果重命名成功,则返回 0。
- 如果重命名失败,则返回 - 1,并设置
errno
来指示错误类型。
3. 目录操作
3.1 getcwd、chdir 函数
getcwd 函数
获取进程当前工作目录 (卷 3,标库函数)
成功:buf 中保存当前进程工作目录位置
失败: NULL
chdir 函数
改变当前进程的工作目录
成功:0
失败:-1 设置 errno 为相应值
3.2 文件、目录权限
注意:目录文件也是 “文件”。其文件内容是该目录下所有子文件的目录项 dentry。 可以尝试用 vim 打开一个目录。
r | w | x | |
---|---|---|---|
文件 | 文件的内容可以被查看 | 内容可以被修改 | 可以运行产生一个进程 |
cat、more、less… | vi、> … | ./ 文件名 | |
目录 | 目录可以被浏览 | 创建、删除、修改文件 | 可以被打开、进入 |
ls、tree… | mv、touch、mkdir… | cd |
目录设置黏住位:若有 w 权限,创建不变,删除、修改只能由 root、目录所有者、文件所 有者操作。
3.3 目录函数
opendir 函数
返回
:根据传入的目录名打开一个目录 (库函数) DIR * 类似于 FILE *
返回
:成功返回指向该目录结构体指针,失败返回 NULL
参数支持相对路径、绝对路径两种方式
例如:打开当前目录:
- getcwd() , opendir()
- opendir(".");
closedir 函数
作用
:关闭打开的目录
返回
:成功:0; 失败:-1 设置 errno 为相应值
readdir 函数
作用
:读取目录 (库函数)
返回
:成功返回目录项结构体指针;失败返回 NULL 设置 errno 为相应值
需注意返回值,读取数据结束时也返回 NULL 值,所以应借助 errno 进一步加以区分。
struct 结构体
:
代码
myls.c
:通过以上函数实现 ls
命令
4. 递归遍历目录
查询指定目录,递归列出目录中文件,同时显示文件大小
4.1 思路
判断命令行参数,获取用户要查询的目录名 argv [1]
argvc == 1 —> ./
判断用户指定的是否是目录。不是则打印文件名
stat S_ISDIR () —> 封装函数 isFile
读目录:
4.2 代码
ls-R.c
5. 重定向
dup
和 dup2
是 Linux 系统中用于复制文件描述符的函数,它们都是 C 语言的系统调用函数。它们的作用是创建一个新的文件描述符,该文件描述符是现有文件描述符的副本,指向同一个文件。
5.1 dup 函数
dup
函数会复制参数 oldfd
所指向的文件描述符,并返回一个新的文件描述符,该新的文件描述符是系统中当前可用的最小的未使用的文件描述符。
如果复制成功,则返回新的文件描述符;如果复制失败,则返回 -1。
代码
5.2 dup2 函数
dup2
函数与 dup
函数类似,但是它可以指定新的文件描述符的数值。如果 newfd
已经是一个打开的文件描述符,那么 dup2
将首先关闭 newfd
,然后将 oldfd
复制到 newfd
,确保 newfd
与 oldfd
指向相同的文件。
成功:返回一个新文件描述符; 如果 oldfd 有效,则返回的文件描述符与 oldfd 指向同一文件。
失败:如果 oldfd 无效,调用失败,关闭 newfd。返回 - 1,同时设置 errno 为相应值
代码
5.3 小结
dup
复制文件描述符,返回一个新的文件描述符,值为系统中当前可用的最小未使用的文件描述符。dup2
复制文件描述符到指定的新文件描述符,如果新文件描述符已经打开,则先关闭新文件描述符再复制。- 这两个函数在多线程环境下可能会存在竞态条件,使用时需要注意线程安全性。
记忆方法两种:
- 文件描述符的本质角度理解记忆。
- 从函数原型及使用角度,反向记忆。
练习:借助 dup 函数编写 mycat 程序,实现 cat file1 > file2 命令相似功能
mycat.c
5.4 fcntl 实现 dup
当 fcntl 的第二个参数为 F_DUPFD 时, 它的作用是根据一个已有的文件描述符,复制生成一个新的文件描述符。此时,fcntl 相当于 dup 和 dup2 函数。
参 3 指定为 0 时,因为 0 号文件描述符已经被占用。所以函数自动用一个最小可用文件描述符。
参 3 指定为 9 时,如果该文件描述符未被占用,则返回 9。否则,返回大于 9 的可用文件描述符。
fcntl_dup.c
相关链接
Linux 系列文章:Linux – Echo (liveout.cn)
GCC、GDB、Makefile:GCC、GDB、Makefile 学习笔记 – Echo (liveout.cn)
Linux 系统编程 1:文件 I/O 笔记:Linux 系统编程 1:文件 I/O
GitHub 仓库,包含教程讲义、代码以及笔记:https://github.com/PGwind/LinuxSystem
哇塞|´・ω・)ノ
受益匪浅