Linux 系统层次结构
内核交互
内核交互的定义: 内核交互是指用户空间程序请求操作系统内核执行某些操作的过程。由于内核控制着计算机的所有硬件资源,因此任何需要访问或控制这些资源的操作都需要通过内核来完成。
内核的作用: 内核负责管理计算机的硬件资源,包括但不限于:
- CPU 调度
- 内存管理
- 外部设备(如键盘、鼠标、显示器等)
- 进程和线程的创建、调度和管理
- 网络通信
何时需要与内核交互: 当程序需要执行以下操作时,通常需要与内核交互:
- 文件系统访问和管理
- 外部设备的输入/输出操作
- 动态内存分配(如使用
malloc
或free
) - 进程间通信或网络通信
不需要内核交互的操作: 并非所有程序操作都需要内核参与。如果操作仅限于用户空间内存内的数据变化,内核通常不需要介入。例如:
- 程序内的普通函数调用
- 基本的算术运算(加、减、乘、除)
- 变量的声明和赋值
- 控制流语句(如循环和条件判断)
与内核交互的三种方式
在与 Linux 内核进行交互时,有三种主要方式:
- 系统调用:这是最基本、最直接的方式,也是与内核交互的最终和唯一手段。系统调用为用户空间程序提供了一种触发内核执行特定功能的机制。
- POSIX 标准库函数:这些是间接的内核交互手段,它们通过系统调用来实现。POSIX 标准定义了一组操作系统应该提供的接口,以确保应用程序的可移植性。这些库函数非常常用,因为它们为开发者提供了一种更高级、更易于使用的与内核交互的方式。
- Shell 应用:Shell 本身是一种应用程序,提供了一种用户友好的接口来执行命令和脚本。Shell 应用程序提供的内核交互可能是基于系统调用的,也可能是基于库函数的。用户可以通过 Shell 命令来执行文件操作、进程控制等操作,这些操作最终会通过系统调用来实现。
这三种方式各有优势,开发者可以根据需要选择最合适的方法来与 Linux 内核交互。系统调用提供了底层的、强大的功能,而 POSIX 标准库函数和 Shell 应用则提供了更易于使用的抽象层。
系统调用
系统调用:系统调用是内核提供给用户空间程序的一种服务,允许应用程序通过这些调用请求内核执行特定的操作。在 Linux 和类 Unix 操作系统中,系统调用通常以 C 语言风格暴露给用户空间,因为这些系统的内核大多是用 C 语言编写的。
系统调用遵循 POSIX 标准,这意味着它们在类 Unix 系统间具有一致性,但通常不能在 Windows 平台上使用。
库函数
库函数与系统调用的关系: 库函数是对系统调用的封装,它们提供了一种更高级、更易于使用的与内核交互的方式。ISO C 库函数,如 printf
、scanf
、malloc
、fopen
等,背后都是通过系统调用来实现的。
为什么需要库函数:
- 跨平台性:库函数封装了系统调用,使得相同的库函数可以在不同平台上实现相同的功能,从而实现跨平台的代码可移植性。
- 简化复杂性:系统调用通常只提供基础的交互手段,而库函数可以在此基础上实现更复杂、更高级的功能,简化了应用程序的开发。
此外,一些库函数可能实现的功能非常简单,不需要进行系统调用,这进一步扩展了库函数的应用范围。
Shell
Shell 的定义: Shell 是一种命令行解释器应用程序,它提供了用户与操作系统之间的接口。用户可以通过在 Shell 中输入命令,由 Shell 解释并执行相应的程序或操作。
Shell 的功能:
- Shell 接收用户输入的命令,并根据这些命令执行特定的应用程序或系统操作。
- 它允许用户浏览文件系统、管理文件和目录、运行程序以及执行各种系统管理任务。
常见的 Shell 应用程序:
- bash(Bourne Again Shell):是 Linux 和许多类 Unix 系统上的默认 Shell,以其强大的脚本编写能力和用户友好性而广受欢迎。
- zsh(Z Shell):是 bash 的一个扩展,提供了更多的功能和改进,包括更好的命令补全、会话管理和更高级的脚本编程特性。
Shell 的应用场景:
- Shell 在系统管理和自动化任务中发挥着重要作用。系统管理员和开发人员经常使用 Shell 脚本来自动化复杂的工作流程。
- Shell 也是学习操作系统原理和进行基本系统操作的重要工具。
Shell 的发展:早期的 Mac OS 终端使用 bash 作为其默认 Shell 解释器。19 年后的 Mac OS 终端迁移到 zsh,以利用其增强的功能。
Linux 文件管理系统
虚拟文件系统 VFS
虚拟文件系统是建立在物理文件系统上层的,给用户使用的文件系统。它的特点是各平台统一,操作简单。
虚拟文件系统 VFS 和物理文件系统的映射关系
硬链接(Hard Link): 硬链接是物理文件系统中的一个特性,它允许多个文件名(目录项)指向同一个文件的 inode。每个文件系统都有一个记录硬链接数量的机制。例如,如果文件 A 有两个硬链接,表示有两个不同的文件名指向文件 A 的相同数据。
硬链接的特点:
- 硬链接与符号链接(Symbolic Link)不同,后者是一个指向另一个文件的路径的文件。
- 硬链接共享相同的 inode(在类 Unix 系统中用于存储文件元数据的数据结构),因此它们具有相同的文件大小、权限、时间戳等属性。
- 删除硬链接不会影响被链接的文件,只有当所有指向该文件的硬链接都被删除后,文件数据才会被删除。
VFS 与硬链接的关系:
- VFS 通过抽象层管理硬链接,使得操作系统和应用程序无需关心底层物理文件系统的实现细节。
- 当应用程序通过 VFS 接口操作文件时,VFS 负责处理硬链接的逻辑,确保文件操作的正确性和一致性。
硬链接在文件系统中是一种有用的特性,它允许用户以不同的路径访问同一个文件,同时保持文件的完整性和一致性。
VFS 中的硬链接数存储: 在虚拟文件系统(VFS)中,即使文件系统抽象层也存储硬链接数的信息。这是因为 VFS 需要维护文件的元数据,包括硬链接的数量。例如,文件 file1 和 file2 可能都是指向同一物理文件的硬链接,它们的硬链接数都是 2。
存储硬链接数的原因: 存储硬链接数是为了跟踪有多少个文件名指向同一个文件数据。这与引用(指针)计数法类似,当存在指针指向一块内存时,内存不会被自动释放;当没有指针指向时,内存会被自动释放。这是一些编程语言内存自动管理的核心机制。
在文件系统中,当硬链接数降至 0 时,意味着没有文件名指向该文件数据,文件系统会决定释放该文件占用的磁盘空间,从而真正删除该文件。
在 Linux VFS 中,每个目录都隐含地包含两个特殊的目录项,即使在空目录中也是如此:
.
(单个点):这个文件代表当前目录的引用。通过它,用户和程序可以访问当前目录内的文件和子目录。..
(双点):这个文件指向当前目录的父目录。它允许用户和程序从当前目录移动到其上级目录。
这两个特殊的目录项是 Linux 文件系统设计的一部分,它们帮助用户和程序更容易地导航文件系统。
文件系统和文件的存储
inode 的定义: inode 是文件系统中用于存储文件元数据的数据结构。它是虚拟文件系统(VFS)到物理文件系统的入口,可以被理解为文件的“开头”或索引。
inode 的作用:
- inode 包含了文件的元数据,如文件大小、权限、时间戳、数据块位置等,但不包含文件名。文件名和 inode 之间通过目录项进行关联。
- 在同一文件系统中,多个 VFS 中的文件可以映射到同一个物理文件系统中的文件。这种情况下,这些文件会共同指向同一个 inode,共享同一个 inode 结点。
inode 的共享:当多个文件名指向同一个 inode 时,它们实际上是引用了同一个文件。这种特性在硬链接(hard links)中得到体现,硬链接允许多个文件名共享相同的文件数据。
查询 inode:在 Linux 系统中,可以使用 ls -i
命令来查询当前目录下所有文件的 inode 编号。如果两个文件的 inode 编号相同,这意味着它们指向同一个 inode,即它们是同一个文件的不同引用。
文件名的存储位置: 文件名并不存储在 inode 中,因为 inode 是文件系统的一个通用数据结构,它包含了文件的元数据信息,如文件大小、权限、时间戳等,但并不包含文件名。
为什么 inode 不能存储文件名:
- 文件名是与 VFS(虚拟文件系统)相关的概念,与物理文件系统的具体实现无关。
- 硬链接的存在表明,多个文件名可以指向同一个 inode,共享相同的文件数据和元数据。因此,文件名不能存储在 inode 中,因为它们不是共享的,而是每个链接独有的。
文件名的存储:文件名必须存储在 VFS 中的某个结构里。在 UNIX 和类 UNIX 系统中,文件名通常与目录项(directory entry)关联,目录项包含了文件名和指向该文件 inode 的指针。
查询文件名:用户可以通过查看目录的内容来获取文件名。文件系统通过维护一个目录结构,将文件名映射到对应的 inode。
相关指令
ls
cp
cp
指令执行的文件复制: cp
指令复制文件时,是对物理文件系统的操作。这意味着它会创建文件的新副本,包括新的 inode 和 inode 背后的文件数据块。
复制文件夹的过程: 当使用 cp
指令复制文件夹时,该过程需要递归地深入到每一个子目录中,并对该目录下的所有子文件进行复制操作。这是因为文件夹包含其他文件和可能的子文件夹,这些都需要被复制到目标位置。
递归复制的必要性: 为了确保文件夹内的所有内容都被复制,必须使用 -r
(或 --recursive
)选项。没有这个选项,cp
指令将无法递归地复制子目录,从而导致复制操作失败。
mv
mv
指令的用途: mv
指令的主要作用是改变文件或目录的位置,或者重命名它们。当使用 mv
指令移动文件时,实际上是在文件系统中更新文件的路径和名称。
重命名文件: mv
指令可以用于重命名文件,因为文件的路径是文件名的一部分。例如,将文件从一个名称改为另一个名称,同时保持其在文件系统中的位置不变。
移动目录: 当 mv
指令用于目录时,它可以将目录从一个位置移动到另一个位置,成为另一个目录的子目录。
目录的移动特点:
- 目录移动不会覆盖目标位置的现有目录,除非目标目录是空的或者目标目录存在但与源目录具有相同的 inode 编号(在某些特殊情况下可能发生)。
- 如果目标位置已经存在一个同名目录,
mv
指令将不会执行覆盖操作,而是会报错。
目录文件的结构
目录文件的结构: 目录文件在虚拟文件系统(VFS)和物理文件系统中都有其存储结构。在物理文件系统中,目录文件存储的是其下所有子文件和子目录的相关信息。
目录项(Directory Entry):
- 每个子文件和子目录在目录文件中都对应一个目录项,这些目录项包含了文件名和文件的 inode 编号。
- 在 C 语言中,目录项通常由
struct dirent
结构表示。
目录项的组织:
- 为了便于理解和操作目录项,可以将其组织关系简化为单链表的形式。
- 然而,现代操作系统为了提高文件系统操作的性能,通常采用更复杂和高效的数据结构,如树形结构。
文件系统的内部结构:
- 不同的文件系统可能采用不同的数据结构来组织目录项。例如:
- NTFS 文件系统通常采用 B+树(多路自平衡搜索树)的形式。
- ext4 文件系统一般采用哈希树(HTree)。
目录项存储了文件的文件名和 inode 编号,这是文件系统中查找文件的关键信息。
硬链接的限制: 硬链接不能用于目录文件。这是因为硬链接通过指向相同的 inode 来实现文件的共享,而目录文件的 inode 包含了指向其子目录项的列表。
目录结构的保护:
- 如果允许硬链接到目录,那么在目录项的结构中可能会出现“环”,即目录项相互引用,形成一个循环。
- 这种环的存在会破坏目录的结构,导致无法通过目录项遍历到达文件系统中的其他部分。
为了防止这种情况,文件系统不允许对目录创建硬链接。相反,可以使用符号链接(也称为软链接)来指向目录,因为符号链接是指向目标文件或目录的路径的引用,而不是指向 inode 的直接链接。
硬链接仅适用于普通文件,不适用于目录文件,以维护文件系统的结构完整性和避免潜在的循环引用问题。
软连接与目录: 软连接可以链接到目录文件,这是因为软连接具有自己的 inode,它不会与目标目录共享 inode。因此,软连接不会导致目录结构中出现环,也不会破坏目录的结构。
软连接的工作原理:
- 软连接的 inode 存储了软连接文件的元数据信息。
- 与普通文件一样,软连接也有一个数据块,该数据块存储了软连接的内容,即目标文件或目录的路径名字符串。
软连接的操作:
- 当在文件系统中操作软连接文件时,文件系统首先会读取软连接文件的数据块,以获取其中存储的目标路径名字符串。
- 接着,文件系统会根据这个路径名字符串查找目标文件或目录,并执行相应的操作。
软连接的优点:软连接提供了一种灵活的方式,允许用户通过不同的路径名访问同一个文件或目录,而不需要复制文件或目录的数据。
软连接可以安全地链接到目录,因为它们不会影响文件系统的结构完整性。软连接的透明性和灵活性使其成为文件系统操作中的一个重要特性。
文件权限
目录文件的权限
目录文件的读权限:
- 拥有目录文件的读权限意味着用户可以读取目录项(directory entries),即目录下的文件名和 inode 编号。
- 这允许用户通过
ls -a
等命令列出目录中的文件信息。如果没有读权限,用户将无法查看目录中包含的文件。
目录文件的写权限:
- 拥有写权限意味着用户可以修改目录项的结构,例如添加、删除或修改目录中的文件和子目录。
- 因此,创建、删除、重命名、复制、移动文件等操作都需要目录的写权限。
- 受写权限影响的命令包括
mkdir
、rmdir
、mv
、cp
等。
目录文件的可执行权限:
- 目录的可执行权限是访问目录内容的基础。没有执行权限的目录,用户将无法进入、查看或操作目录中的文件。
- 如果用户没有目录的可执行权限,即使拥有文件的读或写权限,也无法访问该目录下的文件。
目录文件的权限决定了用户对目录内容的操作能力。读权限允许查看目录中的文件列表,写权限允许修改目录内容,而可执行权限则是进入和操作目录的基础。
读权限 (r
):普通文件的读权限允许用户查看文件的内容。这意味着用户可以读取文件的数据。
目录的读权限与文件访问:访问某个目录下的文件并不需要该目录的读权限。文件内容不是存储在目录项中的,因此查看文件内容只需要目录具有执行权限。
写权限 (w
):普通文件的写权限允许用户修改文件的内容。这意味着用户可以编辑或更改文件的数据。
文件操作与目录权限:文件的删除、重命名等操作实际上受目录的写权限控制,而不是文件本身的写权限。
执行权限 (x
):执行权限允许用户运行文件作为一个程序或脚本执行。如果用户没有对文件的执行权限,即使文件是一个可执行程序或脚本,用户也无法直接运行它。
执行权限的实际意义:如果文件本身不是可执行程序或脚本,那么即使赋予它执行权限,这个权限也没有实际作用。
chmod
chmod
是 “change mode” 的缩写,其中 “mode” 在计算机文件系统中特指文件或目录的权限设置。chmod
命令允许用户修改文件或目录的访问权限,以控制谁可以读、写或执行它们。
在使用 shell 命令改变文件或目录的权限时,推荐使用直观的 “文字设定法”。这种方法使用字母 u
(用户)、g
(组)、o
(其他)和 a
(所有)来指定权限的接收者,以及 +
(添加)、-
(删除)和 =
(赋予)来指定权限的操作。
chmod
命令支持四种用户类别:
u
(用户):代表文件的所有者(owner),使用u
可以修改文件所有者的权限。g
(组):代表文件所属的用户组(group),使用g
可以修改该组用户的权限。o
(其他):代表除了文件所有者和组用户之外的其他用户,使用o
可以修改这些用户的权限。a
(所有):代表所有用户,使用a
可以同时修改用户、组和其他用户的权限。
chmod
命令还支持三种操作符来修改权限:
+
(添加):为指定的用户类别添加特定的权限。-
(删除):从指定的用户类别删除特定的权限。=
(赋予):为指定的用户类别赋予确切的权限,替换当前的权限设置。
权限使用字母 r
(读)、w
(写)和 x
(执行)来表示:
r
表示可读权限。w
表示可写权限。x
表示可执行权限。
数字设定法
数字设定法: 在编程中设置文件或目录权限时,可以使用数字设定法,它通过三位八进制数来表示权限。
权限的二进制表示:
- 每个权限位(读、写、执行)用一个二进制数字表示,其中
0
表示没有权限,1
表示有权限。 - 例如,
rwx
(读、写、执行)的二进制表示为111
。
八进制转换:每三位二进制数转换为一个八进制数,因此文件的完整权限可以用三位八进制数表示。
记住一些常见的默认权限是很有用的。例如:
– 新建目录的默认权限通常是 775
(八进制),二进制表示为 111111101
。
– 新建普通文件的默认权限通常是 664
(八进制),二进制表示为 110100100
。
奇偶数规则:一个重要的结论是,如果权限的八进制数中任何一个数字是奇数,则对应用户类别具有执行权限;如果是偶数,则没有执行权限。
chmod
命令也可以使用数字设定法来改变权限,格式为 chmod <数字> <文件或目录>
。
在编程语言中使用数字设定法时,通常需要在八进制数前加上 0
作为前缀,以表示这是一个八进制数。
which
which
指令的用途: which
指令用于查询系统中可执行程序的路径名。当提供了一个命令或已知的可执行程序作为参数时,which
会显示该程序的完整路径。
如何查找可执行程序:
- 解释器的查找过程: Shell 解释器(如 bash)在执行命令时,会根据环境变量
PATH
指定的搜索路径来查找对应的可执行程序。PATH
环境变量包含了一系列的目录路径,Shell 解释器会按照这些路径的顺序进行搜索。 env
指令: 使用env
指令可以查看当前会话的所有环境变量,包括PATH
。PATH
变量定义了 Shell 解释器搜索可执行程序的顺序。- 当前目录的执行: 如果需要执行当前工作目录下的可执行程序,通常需要使用
./
前缀。这是因为如果没有指定路径,Shell 解释器会在PATH
环境变量定义的路径中搜索命令。 - 内置命令: 有些命令是 Shell 解释器的内置命令,它们是解释器的一部分,不需要外部的可执行文件。例如,
cd
就是一个内置命令。由于这些命令不是外部程序,which
指令无法查询到它们的路径。
示例:
- 要查找
ls
命令的可执行文件路径,可以使用:which ls
- 要查看当前会话的环境变量,可以使用:
env
命令的组合
进程间通信(IPC): 由于进程之间的隔离特性,它们通常不能直接交换数据。然而,在需要进程间数据交互的场景下,可以采用多种通信机制。
使用管道进行 IPC:
- 管道的概念:管道是一种特殊的文件类型,它允许两个进程通过一个缓冲区进行数据交换。
- 系统调用:使用管道时,需要进行系统调用,这会触发内核在内存中分配一段连续的内存空间,作为管道的缓冲区。
- 数据交换:数据可以通过管道的一端写入,然后从另一端读取出来,类似于生活中的管道。写入端对应于发送数据的进程,读取端对应于接收数据的进程。
- 实现 IPC:如果管道的两端分别连接到两个进程,就可以实现这两个进程之间的数据通信。
管道的类型:
- 匿名管道:不需要命名,通常用于父子进程或兄弟进程之间的通信。
- 命名管道(FIFO):具有文件系统中的名称,可以用于不相关的进程之间的通信。
命令组合与管道: 命令组合 cmd1 | cmd2
利用管道实现了两个进程间的通信。管道 |
允许 cmd1
的标准输出(stdout)直接成为 cmd2
的标准输入(stdin)。
工作原理:
- 重定向:
cmd1
将数据输出到它的标准输出,而cmd1|cmd2
结构将这个标准输出重定向到管道中。 - 进程间通信:
cmd2
从它的标准输入读取数据,而这个标准输入现在连接到了管道。 - 数据流:数据从
cmd1
输出流向管道,然后流向cmd2
的输入,实现了两个独立命令的数据流连接。
注意事项:
- 命令选择:
cmd1
必须是能够产生输出的命令,如history
、head
、tail
、ls
、grep
、find
、cat
、wc
等。 - 输入要求:
cmd2
必须是能够处理来自标准输入的数据流的命令,例如head
、tail
、cat
、wc
、grep
等。
命令组合 cmd1 | xargs cmd2
: 这种组合方式允许将 cmd1
的输出作为参数传递给 cmd2
。xargs
命令将输入数据(来自 cmd1
的输出)转换为命令行参数,并执行 cmd2
。
工作原理:
cmd1
产生输出,通常是一个列表或流。xargs
读取cmd1
的输出,并将输出逐行或根据空白字符分割成参数。cmd2
接收xargs
提供的参数,并执行。
使用场景:
- 搜索包含特定函数的源文件:
find . -name "*.C" | xargs grep -n "main"
这个命令组合搜索当前目录及子目录下所有以
.C
结尾的文件,并查找包含main
函数的行。 -
删除特定条件的文件:
-
删除家目录下所有大于 100MB 的普通文件:
find ~ -type f -size +100M | xargs rm
- 删除当前目录下所有以
.c
结尾的文件:find . -type f -name "*.c" | xargs rm
注意:
xargs
可以处理来自标准输入的输出,将其转换为命令行参数。- 命令组合提供了一种灵活的方式来执行复杂的任务,如搜索、过滤和删除文件。