文件系统--软硬链接/动静态库
inode
是文件系统中的一个重要概念,用于存储文件的元数据。
inode 的结构和内容
- 文件权限:定义了文件所有者、所属组以及其他用户对文件的读、写、执行权限。
- 文件所有者和所属组:记录了文件的所有者和所属的用户组信息。
- 文件大小:以字节为单位表示文件的实际大小。
- 文件时间戳:包括文件的创建时间、最后修改时间和最后访问时间。这些时间戳对于文件的管理和跟踪非常重要。
- 链接计数:表示有多少个文件名指向该 inode。硬链接会增加链接计数,而删除硬链接会减少链接计数。当链接计数为 0 时,文件系统可以回收该 inode 和其关联的数据块。
- 数据块指针:这是 inode 结构中最重要的部分之一。它指向文件实际存储数据的数据块。由于文件大小可能不同,数据块指针的数量和组织方式也会有所不同。对于较小的文件,可能只需要几个直接数据块指针就能指向其所有数据。而对于较大的文件,可能还需要间接数据块指针,通过多层索引来访问大量的数据块。
inode 的管理方式
- inode 表:文件系统中维护着一个 inode 表,用于存储所有 inode 的信息。inode 表是一个固定大小的数组,每个元素对应一个 inode。当创建一个新文件时,文件系统会在 inode 表中分配一个空闲的 inode,并初始化其各项属性。
- 分配与回收:文件系统在创建文件时,会从 inode 表中查找一个空闲的 inode 分配给该文件。当文件被删除时,对应的 inode 会被标记为空闲,以便重新分配给其他文件。
inode 的运作原理
- 文件访问:当用户或程序访问一个文件时,文件系统首先根据文件名在目录中查找对应的 inode 编号。然后,文件系统从 inode 表或 inode 缓存中读取该 inode 的信息,以获取文件的权限、大小、数据块指针等关键信息。根据这些信息,文件系统可以确定是否允许用户访问文件,并找到文件数据所在的数据块,从而进行读取或写入操作。
- 文件系统一致性:inode 在维护文件系统的一致性方面起着关键作用。在文件系统进行写入操作时,会先更新 inode 中的相关信息,如文件大小、修改时间等,然后再将数据写入数据块。这样可以确保在系统崩溃或其他异常情况下,文件系统能够通过 inode 中的信息来恢复文件的一致性。
- 硬链接与软链接:硬链接是通过在不同的目录项中创建指向同一个 inode 的链接来实现的。因此,多个硬链接共享同一个 inode 和数据块,它们具有相同的 inode 编号。而软链接(符号链接)则是一个独立的文件,其 inode 中存储的是指向目标文件的路径信息。当访问软链接时,文件系统会根据软链接 inode 中的路径信息来找到目标文件的 inode,进而访问目标文件。
关于inode:
1,inode以分区为单位,一个分区对应一套inode
2,inode分配时,只要确定起始inode即可
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
inode 位图
文件系统中用于管理 inode 资源的数据结构,它通过位图的方式来记录 inode 的使用状态:
定义与结构
- inode 位图是一个由二进制位组成的数组,数组中的每一位对应着文件系统中的一个 inode。
- 当某一位的值为 0 时,表示对应的 inode 是空闲的,可供分配使用;当某一位的值为 1 时,则表示该 inode 已被占用,正在被某个文件或目录使用。
作用
- inode 分配与回收:在创建新文件或目录时,文件系统需要为其分配一个空闲的 inode。此时,文件系统会遍历 inode 位图,寻找值为 0 的位,找到后将其置为 1,表示该 inode 已被分配,并将该 inode 的编号返回给用户或程序,用于创建文件或目录。
- 快速查询:通过 inode 位图,文件系统可以快速判断某个 inode 是否被占用,无需遍历整个 inode 表。
与文件系统的关系
- 文件系统在初始化时会创建 inode 位图,并根据文件系统的大小和 inode 数量来确定位图的大小。在文件系统的运行过程中,inode 位图会随着文件的创建、删除和 inode 的分配、回收而动态更新,以准确反映 inode 的使用情况。
优缺点
- 优点:inode 位图具有高效、简洁的数据结构,能够快速实现 inode 的分配和回收,并且占用的存储空间相对较小。它可以方便地进行位操作,通过简单的逻辑运算就能完成对 inode 状态的查询和修改,适合文件系统频繁的 inode 管理操作。
- 缺点:随着文件系统中 inode 数量的增加,inode 位图的大小也会相应增大。在查找空闲 inode 时,虽然整体效率较高,但如果需要连续分配多个 inode,可能需要遍历较长的位图区域才能找到足够数量的空闲 inode,这在一定程度上会影响分配效率。此外,inode 位图本身需要被正确地维护和保存,如果位图损坏,可能会导致文件系统无法正确识别 inode 的状态,进而引发文件系统的一致性问题。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
block块号
在文件系统中,block 块号是统一分配的,这有助于高效地管理和组织磁盘空间。
block与 inode 的关系
inode和block的索引关系:
1. 直接索引:inode直接存储数据块编号,访问速度快,适合小文件。
2. 间接索引:inode指向索引块,索引块存储数据块编号,可支持较大文件,但增加一次磁盘I/O。
3. 双重间接索引和三重间接索引:通过多层索引支持超大型文件,不过多次磁盘I/O导致访问速度降低。
- inode 中记录了文件所占用的 block 块号信息。当文件被创建时,文件系统会为其分配一个 inode,并根据文件的大小和分配策略为其分配相应的 block 块号,然后将这些 block 块号记录在 inode 中。当访问文件时,系统通过 inode 中存储的 block 块号来定位和读取文件数据。
- 在文件系统的管理中,inode 和 block 块号的分配是相互配合的。inode 负责管理文件的元数据,而 block 块号则具体指向文件数据在磁盘上的存储位置,两者共同构成了文件系统对文件的完整管理机制。
———————————————————————————————————————————
文件系统操作文件的流程
文件系统增删查改操作流程:
————————————————————————————————
增加文件:分配inode -> 检查空间并分配数据块 -> 更新inode和目录项 -> 写入文件数据
删除文件:查找inode -> 释放数据块 -> 释放inode -> 删除目录项
查询文件:路径解析 -> 查找inode -> 读取inode信息 -> 读取文件数据(可选)
修改文件:查找inode -> 检查权限 -> 确定修改位置和所需空间 -> 分配新数据块(如果需要) -> 更新数据块内容 -> 更新inode信息
———————————————————————————————
文件系统进行增、删、查、改文件的操作细节:
增加文件
- 分配 inode:当要创建新文件时,文件系统首先会从 inode 表中查找一个空闲的 inode。一旦找到空闲 inode,就将其分配给新文件,并初始化 inode 中的元数据,如文件的所有者、权限、创建时间等。
- 检查空间并分配数据块:接着,文件系统会根据新文件预计的大小,在磁盘上寻找足够的连续或非连续空闲数据块(block)。如果采用连续分配策略,就需要找到一段连续的空闲块;若使用链接分配或索引分配,则可以更灵活地分配分散的块。文件系统会通过块位图(记录哪些块已使用、哪些块空闲)等数据结构来管理和查找空闲块。
- 更新 inode 和目录项:将分配的数据块的信息(如块号)记录到 inode 中。同时,在文件所在的目录中添加一个新的目录项,该目录项包含文件名和对应的 inode 编号,以此建立文件名和 inode 之间的关联。
- 写入文件数据:用户程序将文件内容写入内存缓冲区,文件系统会在合适的时机将缓冲区中的数据写入到之前分配的数据块中。
删除文件
- 查找 inode
- 释放数据块:通过 inode 中记录的数据块信息,将文件占用的所有数据块标记为空闲。更新块位图等数据结构,表明这些块现在可以被其他文件使用。
- 释放 inode:将该文件对应的 inode 标记为空闲。
- 删除目录项:从文件所在的目录中删除该文件对应的目录项。
查询文件
- 路径解析:用户提供文件的路径名,文件系统会从根目录开始,逐步解析路径中的各个目录名,通过目录的 inode 找到对应的目录数据块,在其中查找下一级目录或文件的目录项。
- 查找 inode:最终找到目标文件的目录项后获取文件的 inode 编号。
- 读取 inode 信息:根据 inode 编号读取对应的 inode,获取文件的元数据,如文件大小、权限、创建时间等。
- 读取文件数据(可选):如果需要读取文件内容,根据 inode 中记录的数据块信息,依次读取数据块并将其内容返回给用户。
修改文件
- 查找 inode:到对应的 inode。
- 检查权限:检查当前用户是否有修改该文件的权限,若权限不足则拒绝操作。
- 确定修改位置和所需空间:根据用户的修改操作(如追加内容、覆盖部分内容等),确定需要修改的数据块位置。如果修改后文件大小增加,需要检查磁盘上是否有足够的空闲块可供分配。
- 分配新数据块(如果需要):若文件增大,按照文件系统的分配策略分配新的空闲数据块,并将新块的信息记录到 inode 中。
- 更新数据块内容:将修改后的数据写入到对应的磁盘数据块中。可以是覆盖原有数据,也可能涉及到在新分配的块中写入新数据。
- 更新 inode 信息
——————————————————————————————————————————
struct dentry
结构
struct dentry
简介:
struct dentry
是目录项对象,用于表示文件系统中的一个目录项,它是文件系统路径查找的关键结构。每个目录项都对应着文件系统中的一个文件名或目录名,通过dentry
可以快速定位到文件或目录的相关信息。- 连接关系:
dentry
结构中有一个成员d_inode
,它是指向该目录项所对应的inode
结构的指针。通过这个指针,dentry
可以获取到文件或目录的详细元数据,如文件的权限、大小、创建时间等,这些信息都存储在inode
中。 - 路径查找:在进行文件路径查找时,文件系统从根目录开始,根据路径中的各个文件名或目录名,通过
dentry
结构逐步解析路径。每个dentry
都代表路径中的一个节点,当找到目标文件或目录的dentry
后,就可以通过其d_inode
指针获取对应的inode
信息,从而完成路径查找并获取文件或目录的详细信息。 - 缓存机制:
dentry
结构还参与了文件系统的缓存机制。为了提高文件系统的性能,系统会将经常访问的dentry
和inode
结构缓存在内存中。 - 目录项操作:对目录项的一些操作,如创建、删除、重命名等,都需要通过
dentry
找到对应的inode
,然后在inode
上进行相应的操作。例如,创建一个新文件时,会创建一个新的dentry
并分配一个新的inode
,然后将dentry
的d_inode
指针指向新分配的inode
。
———————————————————————————————————————————
创建文件系统
在 Linux 系统中,mkfs
是一个用于创建文件系统的工具集合,它可以在磁盘分区或其他存储设备上初始化文件系统,同时也会构建相应的 inode
结构。
mkfs
命令的基本用法
mkfs
实际上是多个文件系统创建工具的前端命令,它会根据指定的文件系统类型调用相应的工具:
mkfs [-t 文件系统类型] [-b 块大小] [-i inode大小] 设备文件名
- 参数说明:
-t
:指定要创建的文件系统类型,常见的有ext2
、ext3
、ext4
、xfs
、btrfs
等。-b
:指定文件系统的块大小,单位为字节。不同的文件系统支持不同的块大小,例如ext4
支持 1024、2048 或 4096 字节。-i
:指定inode
的大小,单位为字节。不过,并不是所有的文件系统都支持该选项。- 设备文件名:指定要创建文件系统的设备,如
/dev/sda1
表示硬盘的第一个分区。
举个例子:
创建 ext4
文件系统
mkfs -t ext4 /dev/sda1
上述命令会在 /dev/sda1
分区上创建一个 ext4
文件系统。在执行该命令时,mkfs.ext4
工具会完成以下主要操作:
- 初始化超级块:超级块包含了文件系统的整体信息,如文件系统的类型、块大小、
inode
数量等。 - 分配
inode
表:根据文件系统的大小和指定的参数,分配一定数量的inode
并初始化inode
表。 - 创建块组:
ext4
文件系统会将磁盘空间划分为多个块组,每个块组都有自己的inode
表、数据块位图和数据块。 - 初始化数据块:将数据块标记为空闲,以便后续文件存储使用。
指定块大小和 inode
数量
mkfs -t ext4 -b 4096 -i 16384 /dev/sda1
这个命令在 /dev/sda1
分区上创建一个 ext4
文件系统,块大小为 4096 字节,每个 inode
占用 16384 字节的空间。通过调整这些参数,可以根据实际需求优化文件系统的性能和空间利用率。
注意事项
- 数据丢失风险:在使用
mkfs
命令时要格外小心,因为它会擦除指定设备上的所有数据。在执行命令之前,请务必备份重要数据。 - 权限要求:需要使用具有 root 权限的用户来执行
mkfs
命令,因为创建文件系统是一个需要高权限的操作。
———————————————————————————————————————————
挂载 - - 分区如何转化为路径
在 Linux 系统里,分区转化为路径主要借助挂载操作达成,这样分区才可以以路径的形式被访问。
分区概述
分区是对物理磁盘进行划分得到的逻辑区域,每个分区都有自己独立的文件系统,像ext4
、xfs
等。例如,新硬盘可以被划分为多个分区,像/dev/sda1
、/dev/sda2
等。
挂载的概念
挂载是把分区和文件系统树中的某个目录关联起来的操作,这个目录被叫做挂载点。挂载之后,分区里的文件和目录就能够通过挂载点路径进行访问。
分区转化为路径的具体步骤
1. 识别分区
2. 创建挂载点
mkdir /mnt/data
3. 挂载分区
mount /dev/sda1 /mnt/data
4. 自动挂载设置
/dev/sda1 /mnt/data ext4 defaults 0 0
把/dev/sda1
分区挂载到/mnt/data
目录,文件系统类型为ext4
,使用默认挂载选项。
5. 卸载分区
当不需要访问分区时,可使用umount
命令卸载分区。例如,卸载/mnt/data
挂载点:
umount /mnt/data
——————————————————————————————————————————
硬链接和软链接
1.如何理解软硬连接?
- 软连接有独立的inode,软连接内容上,保存的是目标文件的婚径,windows 快捷方式
- 硬链接不是独立的文件,没有独立的inode,硬髓擅本质就星一组文件名和已经存在的文件的映射关系!
2.为什么要有软硬连接?各种应用场景软连接:快捷方法
在 Linux 系统中,硬链接和软链接(符号链接)是两种不同的文件链接方式:
硬链接(Hard Link)
理解
- 硬链接本质上是一个指向同一个 inode(索引节点)的文件名。一个文件可以有多个硬链接,这些硬链接都指向同一个 inode,它们共享相同的文件数据。
- 当你创建一个硬链接时,实际上是在文件系统的目录结构中增加了一个新的目录项,该目录项指向原文件的 inode。
作用
- 数据备份与共享:通过创建硬链接,可以在不同的目录下访问同一个文件,而无需复制文件内容。、因为所有硬链接都指向同一个 inode,对其中任何一个链接所做的修改都会反映到其他链接上。
- 防止误删除:只要文件的 inode 引用计数不为 0(即至少有一个硬链接存在),文件的数据就不会被真正删除。即使删除了原文件名,只要还有其他硬链接存在,文件数据仍然可以通过这些链接访问。
意义
- 节省磁盘空间:避免了因复制文件而造成的磁盘空间浪费。
- 数据一致性:确保所有硬链接访问的是同一文件数据,不会出现数据不一致的问题。
注意事项:
- 不能跨文件系统:由于硬链接是通过 inode 来实现的,而不同文件系统的 inode 编号是独立的,所以硬链接不能跨越不同的文件系统创建
- 不能指向目录(避免形成环状链接):通常情况下,不允许对目录创建硬链接。这是因为目录的硬链接可能会破坏文件系统的目录结构和链接关系,导致文件系统的不一致性和难以管理的问题。不过,在某些特殊情况下,如系统内部管理等,可能会有对目录创建硬链接的需求,但这需要非常小心谨慎地操作。
- 原文件删除问题:虽然硬链接可以防止文件被误删除,但如果原文件被删除后,再创建一个同名的新文件,那么新文件与原来的硬链接将没有任何关系。新文件会有自己独立的 inode,而硬链接仍然指向原来文件的 inode。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
软链接(符号链接,Symbolic Link)
理解
- 软链接是一种特殊的文件,它包含了另一个文件或目录的路径名。当访问软链接时,系统会自动将其解析为所指向的目标文件或目录。软链接有自己独立的 inode,它与目标文件的 inode 是不同的。
- 软链接类似于 Windows 系统中的快捷方式,它只是一个指向目标文件的指针,而不包含实际的文件数据。
作用
- 跨文件系统链接:软链接可以跨越不同的文件系统,即使目标文件位于不同的磁盘分区或挂载点上,也可以创建软链接指向它。这在需要在不同文件系统之间建立联系时非常有用。
- 简化路径:当文件或目录的路径很长或复杂时,可以创建一个软链接来简化访问。
- 版本管理:在软件版本管理中,软链接可以用于指向不同版本的软件。
意义
- 灵活性:软链接提供了更高的灵活性,允许在不同文件系统和位置之间建立链接,方便文件和目录的组织和管理。
- 兼容性:软链接可以在不同的文件系统和操作系统之间保持兼容,使得文件和目录的共享和访问更加方便。
注意事项:
软链接
- 目标文件移动或删除:如果软链接所指向的目标文件或目录被移动、重命名或删除,软链接将失效。此时访问软链接会提示 “无法访问,目标文件或目录不存在” 等错误信息。所以在使用软链接时,要确保目标文件的稳定性,避免随意移动或删除目标文件。
- 权限问题:软链接本身有自己的权限设置,但它并不影响目标文件的权限。同时,修改软链接的权限不会影响目标文件的权限,反之亦然。
- 相对路径和绝对路径:创建软链接时,可以使用相对路径或绝对路径。使用相对路径时,要注意软链接所在目录与目标文件之间的相对位置关系。如果软链接所在目录被移动,可能会导致软链接失效。而绝对路径则不受软链接所在目录位置的影响,但如果目标文件的绝对路径发生变化,软链接也会失效。
创建方法
以下是创建硬链接和软链接的命令示例:
# 创建硬链接
ln /path/to/original_file /path/to/hard_link
# 创建软链接
ln -s /path/to/original_file /path/to/symbolic_link
——————————————————————————————————————————
inode 引用计数
inode 引用计数,用于记录指向同一个 inode 的硬链接数量。
基本概念
- 每个文件在文件系统中都有一个唯一的 inode 与之对应,inode 引用计数表示有多少个不同的文件名(硬链接)指向这个 inode。
工作原理
- 当创建一个新文件时,文件系统会为其分配一个 inode,并将 inode 引用计数初始化为 1。此时,该文件只有一个文件名指向它的 inode。
- 当创建一个指向该文件的硬链接时,文件系统会在目录中创建一个新的目录项,将其指向相同的 inode,并将 inode 引用计数加 1。这样,就有两个文件名指向同一个 inode,它们共享相同的文件数据和元数据。
- 当删除一个硬链接(即删除一个文件名)时,文件系统会将 inode 引用计数减 1。只要 inode 引用计数大于 0,文件的数据就会保留在磁盘上,不会被删除。只有当 inode 引用计数减到 0 时,文件系统才会认为该文件不再被使用,从而释放其占用的磁盘空间,包括 inode 和数据块。
作用
- 判断文件是否可删除:inode 引用计数是文件系统判断一个文件是否可以被删除的重要依据。只要有至少一个硬链接存在,即 inode 引用计数大于 0,文件就不会被真正删除,即使所有指向该文件的文件名都被删除,只要 inode 引用计数不为 0,文件数据仍然可以通过其他硬链接访问。
- 资源管理:通过 inode 引用计数,文件系统可以有效地管理磁盘空间和 inode 资源。当多个硬链接指向同一个文件时,它们共享相同的文件数据,避免了重复存储,节省了磁盘空间。同时,文件系统可以根据 inode 引用计数来跟踪文件的使用情况,以便进行优化和管理。
查看 inode 引用计数
- 可以使用
ls -li
命令来查看文件的 inode 信息,其中i
选项用于显示 inode 编号,而文件权限后面的数字就是 inode 引用计数。
123456 -rw-r--r-- 2 user group 1024 Jan 1 00:00 file.txt
2
就是 inode 引用计数,表示有两个硬链接指向这个文件。
———————————————————————————————————————————
动态库和静态库
基本概念
- 静态库:静态库文件的扩展名通常为
.a
(archive),它是一组目标文件(.o
文件)的集合。在编译时,链接器会将静态库中被程序使用的代码直接复制到可执行文件中。 - 动态库:动态库文件的扩展名通常为
.so
(shared object),也称为共享库。在程序运行时,动态链接器会将动态库加载到内存中,多个程序可以共享同一个动态库的副本。
作用
- 静态库
- 代码复用:多个程序可以使用同一个静态库,避免了代码的重复编写,提高了开发效率。
- 独立性:生成的可执行文件包含了所有需要的代码,不依赖于外部的库文件,方便在不同环境中部署。
- 动态库
- 节省内存:多个程序可以共享同一个动态库的副本,减少了内存的使用。
- 更新方便:如果动态库进行了更新,只需要替换库文件,而不需要重新编译所有使用该库的程序。
使用方法
# === 静态库操作 ===
# 编译源文件生成目标文件,-c 选项表示只编译不链接
gcc -c add.c sub.c
# 使用 ar 命令创建静态库
# r 表示将目标文件插入到静态库中,如果静态库已存在则更新
# c 表示创建静态库,如果静态库不存在则创建
# s 表示写入一个目标文件索引到静态库中,加快链接速度
ar rcs libmath.a add.o sub.o
# 编译并链接静态库
# -o main 表示生成名为 main 的可执行文件
# main.c 是主程序源文件
# -L. 表示在当前目录下查找库文件
# -lmath 表示链接名为 libmath.a 的静态库
gcc -o main main.c -L. -lmath
# 运行使用静态库链接生成的程序
./main
动态库
# === 动态库操作 ===
# 编译源文件生成与位置无关的代码(PIC),-fPIC 选项用于生成动态库所需的代码
gcc -fPIC -c add.c sub.c
# 使用 gcc 命令创建动态库
# -shared 选项表示生成共享库(动态库)
# -o libmath.so 表示生成名为 libmath.so 的动态库
gcc -shared -o libmath.so add.o sub.o
# 编译并链接动态库,参数含义与静态库链接时相同
gcc -o main main.c -L. -lmath
# 设置动态库的搜索路径,将当前目录添加到 LD_LIBRARY_PATH 环境变量中
# 这样动态链接器在运行时可以找到我们创建的动态库
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
# 运行使用动态库链接生成的程序
./main
在文件系统中的意义
- 静态库:静态库文件通常存放在系统的标准库目录(如
/usr/lib
或/usr/local/lib
)中,或者项目的特定目录下。静态库在编译时被嵌入到可执行文件中,因此在文件系统中,可执行文件是独立的,不依赖于静态库文件的存在。 - 动态库:动态库文件也通常存放在标准库目录中。多个程序可以共享同一个动态库文件,这减少了文件系统中重复代码的存储,提高了磁盘空间的利用率。
注意事项
- 静态库
- 文件大小:由于静态库会将代码复制到可执行文件中,因此可执行文件的大小会增加,特别是在使用多个大型静态库时。
- 更新困难:如果静态库进行了更新,需要重新编译所有使用该库的程序。
- 动态库
- 依赖问题:程序运行时需要确保动态库文件存在,并且动态链接器能够找到它们。如果动态库文件缺失或版本不兼容,程序可能无法正常运行。
- 版本管理:需要注意动态库的版本管理,避免不同版本的动态库之间的兼容性问题。
———————————————————————————————————————————
三条 gcc
命令:用于编译和链接 C 语言源文件
# 编译 main.c,使用默认输出文件名,添加 mystdio 为库搜索路径
-Lmystdio 表示将 mystdio 这个目录添加到库文件的搜索路径里
gcc main.c -Lmystdio
# 编译 main.c,指定输出文件名为 main,添加 mystdio 为库搜索路径
gcc main.c -o main -Lmystdio
# 编译 main.c,指定输出文件名为 main,添加当前目录和 mystdio 为库搜索路径
# gcc 在链接库文件时,会先在当前目录查找,接着在 mystdio 目录查找
(-L. 表示将当前目录添加到库文件的搜索路径)
gcc main.c -o main -L. -Lmystdio
输出文件名:第一条命令使用默认的输出文件名 a.out,而后两条命令通过 -o main 选项将输出文件名指定为 main。
库搜索路径:第一条命令仅将 mystdio 目录添加到库搜索路径;第二条命令同样只添加了 mystdio 目录;第三条命令添加了两个目录,即当前目录(.)和 mystdio 目录。
———————————————————————————————————————————
生成动态库/静态库
编写 Makefile
生成和发布库
项目结构
假设项目结构如下:
project/
├── src/
│ ├── add.c
│ ├── sub.c
│ └── math.h
├── Makefile
└── output/ # 用于存放发布文件
静态库 Makefile
# 编译器和编译选项
CC = gcc
CFLAGS = -Wall -c
# 源文件和目标文件
SRCS = src/add.c src/sub.c
OBJS = $(SRCS:.c=.o)
# 静态库名称
LIBRARY = libmath.a
# 发布目录
OUTPUT_DIR = output
LIB_DIR = $(OUTPUT_DIR)/lib
INCLUDE_DIR = $(OUTPUT_DIR)/include
# 默认目标
all: $(LIBRARY) output
# 创建静态库
$(LIBRARY): $(OBJS)
ar rcs $(LIBRARY) $(OBJS)
# 编译源文件生成目标文件
%.o: %.c
$(CC) $(CFLAGS) $< -o $@
# 发布目标
output: $(LIBRARY)
@mkdir -p $(LIB_DIR) $(INCLUDE_DIR)
@cp $(LIBRARY) $(LIB_DIR)
@cp src/math.h $(INCLUDE_DIR)
# 清理生成的文件
clean:
rm -f $(OBJS) $(LIBRARY)
rm -rf $(OUTPUT_DIR)
动态库 Makefile
makefile
# 编译器和编译选项
CC = gcc
CFLAGS = -Wall -fPIC -c
LDFLAGS = -shared
# 源文件和目标文件
SRCS = src/add.c src/sub.c
OBJS = $(SRCS:.c=.o)
# 动态库名称
LIBRARY = libmath.so
# 发布目录
OUTPUT_DIR = output
LIB_DIR = $(OUTPUT_DIR)/lib
INCLUDE_DIR = $(OUTPUT_DIR)/include
# 默认目标
all: $(LIBRARY) output
# 创建动态库
$(LIBRARY): $(OBJS)
$(CC) $(LDFLAGS) $^ -o $@
# 编译源文件生成目标文件
%.o: %.c
$(CC) $(CFLAGS) $< -o $@
# 发布目标
output: $(LIBRARY)
@mkdir -p $(LIB_DIR) $(INCLUDE_DIR)
@cp $(LIBRARY) $(LIB_DIR)
@cp src/math.h $(INCLUDE_DIR)
# 清理生成的文件
clean:
rm -f $(OBJS) $(LIBRARY)
rm -rf $(OUTPUT_DIR)
Makefile
解释
- 变量定义:
CC
:指定编译器为gcc
。CFLAGS
:编译选项,静态库使用-Wall -c
,动态库额外添加-fPIC
生成位置无关代码。LDFLAGS
:动态库链接选项,使用-shared
生成共享库。SRCS
:源文件列表。OBJS
:目标文件列表,通过替换.c
为.o
生成。LIBRARY
:库的名称。OUTPUT_DIR
:发布目录。LIB_DIR
:存放库文件的子目录。INCLUDE_DIR
:存放头文件的子目录。
- 目标规则:
all
:默认目标,先构建库,再执行发布操作。$(LIBRARY)
:创建静态库或动态库。%.o: %.c
:将.c
源文件编译成.o
目标文件。output
:创建发布目录,将库文件和头文件复制到相应目录。clean
:删除生成的目标文件、库文件和发布目录。
后续发布流程
静态库发布
- 生成静态库和发布文件:在项目根目录下执行
make
命令,Makefile
会自动编译源文件、生成静态库,并将库文件和头文件复制到output
目录。 - 打包发布文件:将
output
目录打包成一个压缩文件,例如使用tar
命令:
tar -czvf libmath-static.tar.gz output
- 分发:将生成的
libmath-static.tar.gz
文件分发给其他开发者。其他开发者解压该文件后,可在自己的项目中使用静态库。使用时,需要在编译命令中指定库文件路径和头文件路径,例如:
gcc main.c -o main -I/path/to/output/include -L/path/to/output/lib -lmath
动态库发布
- 生成动态库和发布文件:同样在项目根目录下执行
make
命令,生成动态库并完成发布操作。 - 打包发布文件:将
output
目录打包成压缩文件:
tar -czvf libmath-dynamic.tar.gz output
- 分发:将
libmath-dynamic.tar.gz
文件分发给其他开发者。其他开发者解压后,在编译时指定库文件路径和头文件路径:
gcc main.c -o main -I/path/to/output/include -L/path/to/output/lib -lmath
在运行程序时,需要设置动态库的搜索路径,例如:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/output/lib
./main
——————————————————————————————————————————
系统查找动态库
如何给系统指定路径,查找我自己的动态库
- 拷贝到系统默认路径下,
- 在系统路径,建立软连接: ln -s 源文件路径 目标软连接路径
- Linux系统中,OS查找动态库,环境变量,LD_LIBRAY_PATH (export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/user/mylibs)
- ldconfig配置/etc/ld.so.conf.d/,ldconfig更新
注意:
如果同时提供动态库,静态库,g++/gcc默认使用动态库
如果强制静态库,必须提供相应的静态库
如果只提供静态库,但是连接方式是动态链接的的,g++/gcc只能针对你的.a局部性采用静态链接。
——————————————————————————————————————————
动态库加载过程
- 启动加载:当可执行文件被运行时,操作系统的加载器会负责将可执行文件和它依赖的动态库加载到内存中。加载器首先会读取可执行文件的头部信息,获取其依赖的动态库列表。
- 查找动态库:根据动态库的名称,加载器会按照一定的路径顺序查找动态库文件。通常,系统会先在默认的系统库路径(如
/lib
、/usr/lib
等)中查找,然后再检查环境变量LD_LIBRARY_PATH
指定的路径,如果还找不到,可能会在当前目录下查找。 - 加载动态库:找到动态库后,加载器会将动态库映射到进程的地址空间中。它会为动态库分配一段内存区域,并将动态库的代码和数据加载到该区域。同时,加载器还会根据动态库的依赖关系,递归地加载其依赖的其他动态库。
- 符号解析:加载完成后,加载器会对动态库中的符号进行解析。它会将可执行文件和动态库中相互引用的符号地址进行修正,使得它们能够正确地相互调用。这一过程涉及到重定位操作,即根据符号在内存中的实际地址,修改代码中对该符号的引用地址。
——————————————————————————————————————————
ELF(Executable and Linking Format)概述
ELF 即可执行与可链接格式,是一种在 Linux里广泛使用的文件格式,主要用于可执行文件、目标文件、共享库以及核心转储文件。其设计具有灵活性和可扩展性,为操作系统、编译器、链接器和调试器等工具提供了统一接口,增强了软件的可移植性和互操作性。
ELF 文件结构
- ELF 头部(ELF Header):处在文件起始位置,包含了文件的基本信息,像文件类型(可执行文件、目标文件、共享库等)、目标机器架构、程序入口地址等。
- 程序头部表(Program Header Table):描述了如何把 ELF 文件映射到内存中,涵盖了各个段(Segment)的信息,如段的类型、偏移量、大小、内存地址等。
- 节头部表(Section Header Table):包含了各个节(Section)的信息,例如节的名称、类型、大小、偏移量等。节是文件中具有特定用途的数据块。
- 节(Section):存储具体的数据和代码。
- 符号表(Symbol Table)
- 重定位表(Relocation Table)
ELF 文件的作用
- 可执行文件
- 目标文件:链接器可以把多个目标文件和库文件链接成一个可执行文件或共享库。
- 共享库(动态链接库):多个程序在运行时可以共享同一个共享库的代码和数据,从而节省内存空间。
- 核心转储文件
——————————————————————————————————————————
Section(节)介绍
基本概念
Section 是 ELF 文件里具有特定用途的数据块,每个 section 都有自己的名字、大小、偏移量等属性。编译器、链接器和加载器会依据 section 的信息对 ELF 文件进行处理和操作。
常见 Section 类型
- 代码段(
.text
):存储程序的可执行代码,也就是程序的机器指令。通常这部分内容是只读的,防止程序在运行过程中意外修改自身代码。 - 数据段(
.data
):存储已初始化的全局变量和静态变量。这些变量在程序启动时就已经被赋予了初始值。
- 未初始化数据段(
.bss
):存储未初始化的全局变量和静态变量。这些变量在程序启动时会被自动初始化为 0。因为它们初始值都为 0,所以在 ELF 文件中不需要为它们分配实际的存储空间,只需要记录它们的大小即可,这样可以节省文件空间。例如: - 只读数据段(
.rodata
):存储只读数据,如字符串常量。这些数据在程序运行过程中不能被修改。 - 符号表
- 重定位表
Section 的作用
- 模块化组织:将不同类型的数据和代码分开存储在不同的 section 中,使 ELF 文件的结构更清晰,便于编译器、链接器和加载器处理。
- 优化存储:如
.bss
节只记录未初始化变量的大小,不实际分配存储空间,节省文件空间。 - 方便调试和分析:符号表和重定位表等 section 提供了程序的详细信息,便于开发人员进行调试和分析。
查看 ELF 文件 Section 信息的工具
- readelf:使用
readelf -S your_program.elf
命令可列出 ELF 文件中所有 section 的详细信息,包括名称、类型、大小、偏移量等。
- objdump:使用
objdump -h your_program.elf
命令可显示 ELF 文件的 section 头部信息,包括 section 的编号、名称、大小等。