当前位置: 首页 > news >正文

怎样在网站上做免费的推广郑州网站推广策

怎样在网站上做免费的推广,郑州网站推广策,企业网站建设基本思路,大连优化公司目录 1. 理解文件系统中inode的概念 1.1. 了解磁盘 1.1.1. 认识磁盘 1.1.2. 磁盘的物理结构 1.1.3. 简单了解磁盘如何读写数据的 1.1.4. 磁头和盘面没有物理上的接触 1.1.5. 扇区的了解 1.1.6. 如何在物理上找到一个具体的扇区 1.2. 站在OS的角度看待磁盘 1.2.1. …目录 1. 理解文件系统中inode的概念 1.1. 了解磁盘 1.1.1. 认识磁盘  1.1.2. 磁盘的物理结构 1.1.3. 简单了解磁盘如何读写数据的 1.1.4. 磁头和盘面没有物理上的接触 1.1.5. 扇区的了解  1.1.6. 如何在物理上找到一个具体的扇区 1.2.  站在OS的角度看待磁盘 1.2.1. Boot Block Block Group  1.2.2. Super Block  1.2.3. Group Descriptor Table 1.2.4. Data Blocks  1.2.5. Inode Table 1.2.6. Block Bitmap 1.2.7. Inode Bitmap 1.2.8. inode与 block的关系 1.2.9. inode 与 文件名的关系 1.3. 分析三个问题 1. 创建文件操作系统做了什么 2. 删除文件操作系统做了什么 3. 打开文件操作系统做了什么 2. 认识软硬链接 2.1. 软链接 2.2. 硬链接 2.3. 软链接和硬链接的区别 2.4. ACM时间  3. 认识并制作动静态库 3.1. 认识动静态库 3.2. 静态库的制作 3.2.1. add.c 文件 3.2.2. my_add.h 文件 3.2.3. sub.c 文件 3.2.4. my_sub.h 文件 3.2.5. makefile文件的实现 3.3. 静态库的使用 3.4. 动态库的制作 3.4.1. add.c 文件 3.4.2. my_add.h 文件 3.4.3. sub.c 文件 3.4.4. my_sub.h 文件 3.4.5. Makefile 文件的实现  3.5. 动态库的使用 3.5.1. test.c 3.5.2. 使用动态库  3.5.3. 动态库的加载 方法一将动态库文件拷贝到系统库文件的默认搜索路径下 方法二设置LD_LIBRARY_PATH环境变量 方法三添加配置文件 方法四利用软链接 补充 1. 理解文件系统中inode的概念 我们在基础IO --- 上 谈论的文件都是被打开的文件而被打开的文件是在内存中那么有没有没被打开的文件呢答案是有的这种文件在磁盘上而我们将这种文件称之为磁盘级别的文件 根据冯诺依曼体系被打开的文件是在内存中而未打开的文件是在磁盘上所以这两种文件所处的场景都不一样因此不能将这两种文件混为一谈 那么我们学习磁盘级别的文件侧重点在哪里呢 站在单个文件的角度来看我们更关心的是这个文件在哪里 这个文件多大这个文件的其他属性是什么等等其他话题 而站在系统的角度来看我们更关心的是磁盘中一共有多少个文件各自属性在哪里如何快速找到磁盘还可以存储多少个文件如何快速的找到指定的文件等等其他话题 那么如何对磁盘文件进行分门别类的存储用来支持更好的存取 要了解这个话题我们必须要对磁盘以及文件系统有一定的了解 1.1. 了解磁盘 内存是一种掉电易失的存储介质也称为随机访问存储器Random Access MemoryRAM。当计算机断电时内存中的数据会被清空因此内存中的数据是临时存储的需要持续供电来维持数据的保存。 而磁盘则是一种永久性的存储介质通常是通过磁性材料在磁盘上记录数据。即使断电磁盘上的数据仍然可以被保留。因此磁盘适用于长期存储和持久化存储如操作系统、应用程序以及用户文件等。 1.1.1. 认识磁盘  磁盘或类似的存储介质 SSDSolid State Drive固态硬盘与传统机械硬盘不同SSD采用闪存芯片作为存储介质具有更高的读写速度和更低的访问延迟。在外观形态上与传统机械硬盘相似但工作原理不同。 U盘USB Flash Drive也被称为闪存盘它是一种便携式存储设备内部采用闪存芯片作为存储介质通过USB接口与计算机进行连接和数据传输。 Flash卡也被称为闪存卡或存储卡是一种通用的可擦写存储介质常见的有SD卡、MicroSD卡、CF卡等。它们通常用于相机、手机、平板电脑等设备中扩展存储空间或作为移动存储介质。 光盘包括CD、DVD和Blu-ray Disc等采用光学记录原理通过激光读取信息。光盘通常用于存储音频、视频、软件安装等数据。 磁带磁带是一种较为老旧但仍在某些特定领域使用的存储介质。磁带使用磁性材料作为记录介质通过磁头读写数据。磁带主要用于大规模数据备份和存档具有较大的存储容量。 1.1.2. 磁盘的物理结构 而我们在这里谈论磁盘的是机械硬盘机械硬盘的内部结构如下 下面是机械硬盘的主要结构具体如下  磁盘盘片Platters磁盘通常由多个金属或玻璃盘片组成盘片表面涂有磁性材料。这些盘片一般通过中心轴垂直堆叠在一起。 磁头Head磁头是位于磁盘上方和下方的读写装置用于读取和写入数据到磁盘盘片的表面。每个盘片都有一个磁头用于在不同的磁道上读写数据。 柱面Cylinder表示数据存储介质上的一个圆柱面由许多同心圆组成。同一磁盘的所有磁头对应的柱面被视为一个柱面组cylinder group称为柱面号cylinder number。 扇区Sector每个磁道被划分为多个等长的扇区。扇区是磁盘最小的物理存储单位通常为512字节或4KB的大小。 磁道Track磁盘表面被划分为许多同心圆轨道称为磁道。磁头可以在磁道之间移动访问特定磁道上的数据。 磁道间隔Inter-Track Spacing相邻磁道之间有一些间隔用于帮助磁头准确定位到特定磁道。 主轴Spindle磁盘盘片通过主轴固定在磁盘驱动器的中心。主轴可以旋转盘片以使磁头能够在盘片的不同位置读取和写入数据。 控制器Controller磁盘驱动器中的控制器负责管理磁头的移动、磁盘的旋转和数据的读写等操作。控制器也处理与主机系统的通信并负责磁盘的高级功能如缓存和错误检测校正。 磁盘是一个外设并且是计算机中唯一的一个机械设备因此这个磁盘设备非常的慢这个慢是相对于CPU和内存而言的我们也可以量化一下CPU、内存、磁盘处理数据的能力如下 CPU中央处理器处理数据的级别通常是以纳秒10^-9秒为单位。CPU是计算机的核心组件具有非常高的处理速度可以在纳秒级别内执行指令和操作。 内存的访问速度通常以微秒10^-6秒为单位。相对于CPU来说内存的访问速度较慢一些但仍然很快。内存是计算机中用于临时存储数据的地方可以迅速地读取和写入数据。 磁盘的访问速度通常以毫秒10^-3秒或秒为单位具体取决于磁盘的类型和性能。相对于CPU和内存来说磁盘的读写速度较慢。这是因为磁盘是机械设备需要进行旋转和磁头寻道等操作来读取和写入数据 因此实际上磁盘是一种很慢的设备但操作系统OS会采取一些策略和技术来提高对磁盘的访问效率以尽量减少延迟并提升整体性能例如 缓存操作系统可以使用缓存技术将频繁访问的数据存储在内存中。通过缓存可以减少对慢速磁盘的访问次数减少IO次数加快数据的读取速度。 提前读取Pre-fetching操作系统可以根据访问模式和预测算法在磁盘读取数据时提前读取相邻的数据块到内存中。这样当需要访问这些数据时可以直接从内存中读取而不用等待磁盘的旋转和寻道时间。 磁盘调度算法操作系统可以采用不同的磁盘调度算法如先来先服务FCFS、最短寻道时间优先SSTF、扫描SCAN等来优化磁盘的读写顺序减少寻道时间和延迟。 磁盘分区和文件系统合理的磁盘分区和优化的文件系统可以提高读写效率和快速查找文件。常见的文件系统如NTFS、FAT32、ext4等都有针对性的优化策略以提供更好的性能。 1.1.3. 简单了解磁盘如何读写数据的 我们知道磁盘上的数据是存于磁盘盘面上的也就是说盘面上会存储数据而我们知道计算机只认识二进制数据存储的数据本质就是二进制数据二进制数据本质上是一种两态的数据那么磁盘中是如何定义这种两态的数据的呢 而实际上盘面中是存在着大量的小的磁性区域的 而写入数据的本质是通过改变磁性区域的磁化方向使该磁性区域磁化向上或向下来实现的 读取数据的过程并不是说磁性区域N级向上代表为1S级在上代表为0而实际上是磁头会在磁道上移动通过感测相邻磁性区域的磁性变化来解读存储的二进制数据。每次相邻的两个磁性区域改变时磁头会将其解析为二进制的1不变时磁头将其解析为二进制的0简单来说就是相邻的两个磁性区域的仓磁化方向相同为0相异为1  总而言之通过改变磁性区域的磁化方向磁盘可以存储和读取二进制数据计算机根据相邻磁性区域的磁性变化来解释和识别数据的不同状态。这种基于磁性的储存和读取机制是现代磁盘驱动器的基础。 1.1.4. 磁头和盘面没有物理上的接触 我们要知道实际上磁盘中的磁头和盘面是没有相互接触的(物理上的)因为盘面是高速旋转的如果相互接触那么极有可能由于磁头和盘面的相互摩擦导致盘面被刮花盘面挂花也就意味着盘面的物理结构受到了破坏那么可能导致数据丢失等其他问题 磁盘的盘面是通过主轴旋转的旋转速度通常在每分钟数千转到一万转之间(目前来看市面上的磁盘主流还是7200转)。而磁头则悬浮在盘面上方以纳米级的高度通常是几十纳米悬浮在盘面表面的空气或润滑膜中利用气体动压或润滑膜来保持磁头的稳定悬浮。 通过这种非接触式的设计磁头可以在盘面上方快速移动而不会直接接触到盘面避免了因摩擦而导致的物理损坏和数据丢失的风险。这种设计还可以确保读写过程的可靠性和精度因为任何磁头和盘面之间的直接接触都可能导致不可预测的结果。 因此在正常的使用情况下磁头和磁盘盘面之间不会有物理上的接触以保护磁盘的完整性和数据的安全性。 1.1.5. 扇区的了解  扇区Sector是磁盘存储数据的基本单位一般扇区的大小是512字节然而近年来随着技术的发展和容量需求的增加一些磁盘驱动器也开始采用更大的扇区大小如4KB4096字节。这种更大的扇区大小有助于提高磁盘的性能和容量利用效率并且可以更好地支持现代文件系统和应用程序的需求。但需要注意的是使用4KB扇区的磁盘通常需要与特定的操作系统和文件系统相兼容才能正常使用。 总体而言512字节的扇区大小是常见且广泛采用的标准而4KB扇区大小则是一种新兴趋势尤其在较大容量的磁盘驱动器和高性能存储系统中使用。 1.1.6. 如何在物理上找到一个具体的扇区 在物理上如何找到一个具体的扇区呢分为三个过程 1、确定在哪一个磁道 (柱面号 Cylinder Number) 上柱面号表示磁头在磁道上的位置。它的范围通常从0开始逐渐增加到磁盘上的最大柱面数。 2、确定在哪一个盘面上 (磁头号 Head Number) 磁头号表示磁头在柱面中的位置。它的范围通常从0开始逐渐增加到磁盘上的最大磁头数。 3、确定在哪一个扇区(扇区号 Sector Number)扇区号表示在给定柱面和磁头中的特定扇区位置。它的范围通常从1开始逐渐增加到磁盘上的最大扇区数通常是63或255。 上面这种寻址方案在物理上称之为CHS( Cylinder Head Sector )寻址方案当我们有了CHS寻址方案那么就可以找到任意一个扇区即所有扇区我们就都能找到 1.2.  站在OS的角度看待磁盘 站在操作系统的角度即在软件层面上我们可以将磁盘抽象成一种线性结构 即将一个磁盘抽象成一个大数组数组的每一个元素代表一个扇区访问一个特定的扇区那么只要知道该扇区在该数组中对应的下标即可而这种寻址方案我们称之为 LBA (Logical Block Addressing)寻址方案LBA又称为逻辑块寻址操作系统使用的地址是通过LBA寻址方案得到某个具体扇区的地址而最终为了访问磁盘中具体的某个扇区操作系统会将LBA地址转换为CHSCylinder-Head-Sector方案地址最终才能访问到磁盘中具体的某一个扇区 将数据存储到磁盘等价于将数据存储到抽象出来的这个数组中那么首先要找到磁盘特定扇区的位置等价于找到该数组特定的下标那么对磁盘的管理就可以转化为对该数组的管理 我们将磁盘抽象为线性结构如下 可是磁盘太大了啊那么抽象出来的这个数组就会非常大那么就会导致管理成本太高因此为了降低管理成本 我们采用分治的思想即对这个抽象出来的大数组进行分区如下 那么此时对磁盘抽象出来的这个数组的管理就转化为对某一个小分区的管理只要管理好某一个具体的分区那么就可以通过这份管理经验管理好其他分区那么整个磁盘也就可以管理好 但是我们分出的这个区域还是太大了其管理成本依旧很高因此我们将某一个具体的小分区在进行采用分治的思想分为不同的小区域 如下 1.2.1. Boot Block Block Group  Boot Block引导块/启动块它是一个针对计算机引导过程设计的特殊区域它位于存储设备的开头。它包含有关引导加载程序的信息用于启动计算机并加载操作系统。 Block Group块组ext2文件系统根据分区的大小划分为多个Block Group而每个Block Group都有着相同的组成结构块组是文件系统的逻辑分割单元用于组织和管理文件系统中的数据、元数据和其他相关信息。 而事实上Block Group也会分为不同的区域组成如图所示 1.2.2. Super Block  Super Block 超级块 Super Block是 ext2 文件系统的一个重要组成部分。它包含了关于文件系统的关键信息和元数据Super Block 在一般情况下在多个块组中会有备份 但并不是每一个块组都有具体来说Super Block 包含了以下信息 文件系统的总块数 和 总 inode 数用于计算文件系统的容量。块大小和块组大小文件系统中每个块Block的大小以及每个块组Block Group中的块数。空闲块和空闲 inode 的数量用于记录文件系统中当前可用的块和 inode 的数量。文件系统的挂载时间和最近的检测时间用于记录文件系统的挂载信息和检测时间。inode 表的起始块地址指向存储 inode 的起始块。特殊文件的 inode指向根目录、lostfound 目录等特殊文件的 inode 号。块组描述符表的起始位置指向块组描述符表其中存储了每个块组的元数据信息。 Super Block 的信息对于文件系统的正常运行和恢复非常重要。通过读取 Super Block 操作系统和工具可以了解文件系统的结构、大小和状态从而正确地访问和管理文件系统中的数据。Super Block的信息被破坏可以说整个文件系统结构就被破坏了 1.2.3. Group Descriptor Table Group Descriptor Table块组描述符表 在 Linux 的 ext2 文件系统中Group Descriptor Table块组描述符表是一个关键数据结构用于跟踪文件系统中每个块组的信息。它位于文件系统的一定位置是与超级块Super Block和 inode 表一起组成文件系统元数据的重要组成部分之一。 块组描述符表记录了文件系统中每个块组的元数据信息包括块组的起始块地址、块位图Block Bitmap的块地址inode 位图Inode Bitmap的块地址和 inode 表的起始块地址等。块组描述符表中的每个条目与文件系统的每个块组相对应因此文件系统中块组的数量等于块组描述符表中的条目数。 块组描述符表的作用是为文件系统提供有关每个块组的信息并支持文件系统的核心操作如分配和释放块、分配和释放 inode 等。每个块组描述符表条目的内容取决于文件系统的配置如块大小、inode 大小和块组大小等。 总之块组描述符表是 ext2 文件系统中的重要元数据结构之一提供了有关文件系统中每个块组的信息。这些信息对于文件系统的正常运行和管理非常重要。 我们知道在Linux系统中文件 文件内容 文件属性而 Linux 在磁盘上存储文件的时候是将文件内容 和 文件属性 进行分别存储的 而Data Blocks 就是存放文件内容的地方而Inode Table 是保存文件属性的地方 1.2.4. Data Blocks  Data Blocks 虽然磁盘存储数据的基本单位是一个扇区 (512 byte) 硬件上的要求但是OS (文件系统)和磁盘进行IO的基本单位是4KB (8 * 512 byte) 软件上的要求 有人就很好奇为什么不以512字节为单位呢 1、首先较大的 IO 单位可以减少磁盘访问的次数 (减少IO次数)。磁盘的访问是相对较慢的操作因此减少 IO 次数可以减少磁盘寻址和传输的时间。如果使用较小的 512 字节作为 IO 单位那么对于相同的数据量需要进行更多次的 IO 操作才能完成数据传输这将导致系统的整体效率降低 2、其次使用固定的 IO 单位与硬件要求分离使操作系统能够更好地适应不同硬件平台的变化。磁盘存储技术在不断发展未来可能会出现更大的扇区大小或其他新的存储技术。如果操作系统和磁盘的 IO 单位以完全相同的大小绑定在一起那么在硬件发生变化时操作系统的源代码可能需要进行修改才能适应新的硬件要求。通过将 IO 单位设置为 4KB操作系统与具体磁盘存储技术解耦可以更容易地适应不同的硬件平台和存储技术变化。 一个数据块Block 其默认大小为4KB而多个4KB扇区 * 8大小的集合我们称之为Data Blocks其保存的都是特定文件的内容数据块是文件系统中用于存储文件数据的最小单位它提供了碎片化存储空间和协调磁盘空间管理的功能。 注意Data Block 的数量也是有限的如果当文件系统中的Data Block已经用完时如果尝试创建一个新文件并向其写入内容将会导致写入失败。 1.2.5. Inode Table Inode Table Inode Table索引节点表是文件系统中用于存储文件元数据的数据结构。每个文件和目录在文件系统中都有一个对应的索引节点Inode而 Inode Table 就是用来管理和存储所有这些索引节点Inode的表格。 索引节点Inode是文件系统中的一个重要概念在 ext2 文件系统中Inode的大小固定为128字节。它用于存储文件的元数据包括文件的权限、所有者、文件类型、大小、时间戳等信息。具体而言每个索引节点中包含了文件的唯一标识符inode编号、文件的访问权限、文件大小、链接数文件被引用的次数、文件的物理地址指向数据块等信息。 Inode Table将所有文件的索引节点Inode组织在一起通常以数组或其他数据结构的形式进行存储。每个索引节点都有一个独特的索引节点号inode 编号通过该索引节点号可以在索引节点表中快速检索到对应的索引节点。 当文件系统需要读取或修改文件时会先通过 Inode Table 找到对应的索引节点Inode并从索引节点Inode中获取文件的元数据和数据块的物理地址。通过这种方式文件系统可以有效地管理和访问文件包括创建、删除、读取和修改文件的操作。 需要注意的是Inode Table的大小是固定的它限制了文件系统中可以创建的最大文件数量。当Inode Table已满时文件系统将无法再创建新文件即使存储设备还有可用空间。 综上所述Inode Table是文件系统中用于存储和管理文件索引节点Inode的一种数据结构它包含了文件的元数据和用于定位文件数据的物理地址信息帮助文件系统有效地管理和访问文件。 1.2.6. Block Bitmap Block Bitmap块位图是文件系统中的一种数据结构用于跟踪和管理文件系统中的数据块的使用情况。 在文件系统中存储设备被划分为固定大小的数据块block这些数据块是文件系统进行数据存储和管理的最小单位。数据块位图Block Bitmap用于记录每个数据块的使用情况它通常以位图的形式表示。 数据块位图中的每个位(bit)表示一个数据块的状态通常用 0 或 1 表示分别表示未使用或已使用。当一个数据块被分配给文件时相应的位会被标记为1表示该数据块已被占用。当一个文件被删除或移动时相应的位会被标记为0表示该数据块又变为空闲可用状态。 通过数据块位图文件系统可以快速查找和管理可用的数据块。当需要为文件分配新的数据块时文件系统会查找位图中的空闲位找到一个未被使用的数据块并将其分配给文件。而当文件被删除时文件系统会将相应的位标记为空闲表示该数据块可以被后续文件再次使用。 数据块位图是文件系统的重要组成部分它帮助文件系统有效地管理磁盘空间和数据块的分配。通过跟踪数据块的使用情况文件系统可以优化磁盘空间的利用并避免数据块的碎片化。同时数据块位图也可以用于检测文件系统的完整性和一致性以确保数据块被正确地分配和释放。 总之数据块位图是文件系统中的一种数据结构用于记录和管理数据块的使用情况。它帮助文件系统管理磁盘空间优化数据块的分配并确保文件系统的一致性和完整性。 1.2.7. Inode Bitmap Inode Bitmap索引节点位图是文件系统中的一种数据结构用于跟踪和管理使用中和空闲的 inode索引节点。 Inode Bitmap是一个位图bitmap用于表示文件系统中 Inode结构对象的分配情况。每个位对应一个 Inode 结构对象如果该位为0表示对应的 Inode 结构对象未被分配未被使用如果该位为1表示对应的 Inode 结构对象已被分配已被使用。 通过索引节点位图文件系统可以快速查找和管理可用的 Inode 结构对象。当需要分配新的 Inode 结构对象 给一个文件时文件系统会查找位图中的空闲位找到一个未被使用的 Inode结构对象 并将其分配给该文件。 索引节点位图是文件系统的重要组成部分它帮助文件系统有效地管理 Inode结构对象 并避免 Inode结构对象碎片化。同样索引节点位图也可以用于检测文件系统的完整性和一致性以确保 Inode 结构对象 的正确分配和释放。 总之索引节点位图是文件系统中一种用于记录和管理 Inode结构对象 状态的数据结构。通过它文件系统可以快速地分配和释放 Inode结构对象避免 Inode 结构对象的碎片化并确保文件系统的一致性和完整性。 注意 inode编号是由文件系统内部分配和管理的。通常情况下文件系统会使用一个单独的数据结构如Inode Table来维护inode 编号和对应的 Inode结构对象之间的映射关系。通过这个映射关系操作系统可以根据 inode编号 来找到对应的Inode结构对象并读取或修改相应的属性和数据。 在文件系统中每个文件都对应一个inode编号可以通过该编号来唯一标识一个文件站在操作系统的角度来看文件是一个由 Inode 结构对象和数据块组成的实体。每个文件都有一个的 inode 编号Inode结构对象中记录该文件的属性信息如文件类型、访问权限、最近访问时间、最近修改时间等。数据块则用来存储文件的实际内容。 因此在操作系统中可以通过 inode编号 来查找和处理文件如打开文件、读取文件、修改文件属性等。文件名只是一个用户友好的视觉标识方便用户来记忆和使用文件并且通过文件名可以方便地在文件系统中查找和定位文件的 Inode结构对象信息。 有了上面的这些信息那么就能够让一个文件的信息可追溯可管理而我们将一个块组分割成上面的内容并且写入相关的管理数据当每一个块组都这么操作那么整个分区都被写入了文件系统信息而这个过程我们就称之为格式化 1.2.8. inode与 block的关系 一个文件对应一个 Inode 结构对象 对应一个 inode 编号那么一个文件只能有一个block吗 答案是不一定因为一个block 只有4KB的大小可是一个文件有时候可不仅仅只有4KB的大小那么结论就是一个文件是可以有多个block的 那么问题来了哪些 block 属于同一个文件呢 要找某一个文件那么只要找到该文件对应的 inode 编号通过 inode 编号就能找到该文件的 Inode 属性集合可是文件的内容呢即 blcok 如何找到呢 在一个文件的 Inode 属性节点中通常会有一个blocks数组或指针用于记录文件占用的数据块的编号。通过这个数组或者指针就可以找到这个 Inode 结构对象与之关联的 block了 我们知道一个block块的大小是4KB那如果这个文件特别的大 那怎么办呢 由于一个数据块的大小是有限的无法存储大文件的全部内容因此使用间接块的方式来解决这个问题。 间接块可以存放其他块的编号形成一个链式的结构。文件系统通过遍历这个链式结构就可以找到存储文件实际内容的数据块的编号。每个间接块中可以存储一定数量的块编号依此类推通过多级间接块的方式就可以存储大量的数据。 举例来说如果一个文件占用了10个数据块那么前10个块的编号可能直接存放在Inode 结构对象的 blocks 数组中。如果一个文件占用了更多的数据块多余的块的编号可能会存放在一个间接块中而这个间接块的编号则会存放在 Inode 结构对象的 blocks 数组中。通过这种方式文件系统可以构建一个链式结构通过连续的寻址找到存储文件实际内容的数据块。 简而言之block 不一定存放的全是文件内容还有可能存放的是其他块的编号 通过间接块的方式文件系统可以支持存储大文件充分利用磁盘空间并提高文件系统的效率。 综上所述一个文件可以占用多个数据块而文件系统使用间接块的方式来支持大文件的存储。通过建立链式结构将数据块的编号存放在 Inode 结构对象的 blocks 数组中文件系统可以寻址并找到存储文件实际内容的数据块。 为了更好地理解我们简单列举一下 Inode 结构的属性 注意内核里的实现可能没有这么简单可能会进行多次封装在这里只是举例罢了。 struct inode {// 文件的大小// 其他属性// 文件的inode编号int inode_number;// 这个数组里面的内容就是// 这个inode与之关联的blockint blocks[15]; };1.2.9. inode 与 文件名的关系 在Linux下文件名在操作系统层面没有意义(包括文件后缀)文件名是给用户使用的在Linux中真正标识一个文件是通过文件的inode编号一般情况下一个文件一个inode编号 我们知道要找到一个磁盘文件那么必须要先找到这个 inode 编号才能在某个分区特定的block group中找到对应的 Inode 属性即 struct inode 才能找到这个 Inode 结构对象与之对应的数据块 block 而在Linux下struct inode 属性里面是没有文件名这样的说法那么OS怎么知道一个文件的 inode 编号呢 为了说明这个问题我们需要两个储备知识 1、 一个目录下是可以有很多文件的但是这些文件是没有重复的文件名的即在同一个目录下没有相同文件名的文件 2、 目录是文件吗当然是文件那么既然是文件那么就必须得有自己的 Inode 结构对象 和 自己的 数据块 block 。 那么目录的 block 中存储的内容是什么呢答案是 目录文件的 block 存储的是 该目录下文件的文件名 和 inode编号 的映射关系 而在以前我们学习Linux权限的时候说过创建(删除)文件是需要具有 w 权限的当时我们是经过测试得出的结论可是为什么呢 为什么在一个目录下创建文件这个目录需要具有w权限呢而现在我们有了上面的一些理解我们就可以解释这个现象了(我们在这里以创建文件举例) 目录是文件吗是的既然是文件那么必然有自己的 Inode 结构对象和 block而我们说了目录的 block 中的内容存储的是 其目录下的文件的文件名 和 inode编号的映射关系那么你在这个目录下创建文件本质上就是将你所创建的这个文件的文件名 和 文件系统所分配的 inode编号 关联起来并写入这个目录文件的 block 中既然是写入那么当然要有 w 权限咯 换句话说当我们使用 shell 命令创建文件时实际上是通过在目录文件中添加新的文件名及其对应的 inode编号 来完成的因此要求目录文件具有写权限。 同样我们以前也说过用 ls命令 显示一个目录下的文件信息(文件名 文件属性信息等等)是需要该目录具有r权限的以前我不知道为什么但现在我们也可以解释这个现象了解释如下 你要显示一个目录下文件的文件名以及文件的属性信息先说文件名而我们知道inode 属性中是没有文件名的只有该目录下的数据块 block 中有因此我们必须去读取目录的 block 中的数据既然是读取当然要求目录具有 r 权限咯同样当我们要显示文件的其他属性信息时那么必须要知道文件的 inode 编号可是谁才有这些文件的 inode编号 呢答案是目录文件的 block 里面有该目录下文件的 inode编号因此还是要去读取目录文件的数据块 block那么当然要求目录具有 r 权限咯 1.3. 分析三个问题 有了上面的分析我们就可以站在OS的角度分析下面的三个问题 1. 创建文件操作系统做了什么 当我在 bash 调用命令 touch log.txt操作系统层面做了什么呢 1、首先当我们创建一个文件时其所在目录是确定的操作系统会定位到该目录所属的块组 Block Group 。 2、然后操作系统会去遍历该块组中的 Inode Bitmap得到第一个为 0 的比特位所在的位置而这个位置就可以充当所要创建 log.txt 的 inode 编号这个 inode编号将成为被创建文件的唯一标识。 3、接下来操作系统会将文件属性信息填充 struct inode 结构对象 (包括文件的权限、所有者、时间戳等信息)因为当前是空文件因此可以暂时不分配数据块 block 给这个 Inode 结构对象。 4、与此同时操作系统会将用户提供的文件名以及文件系统分配的 inode 编号关联起来填充到该目录文件的数据块 block 中 通过上述步骤操作系统完成了文件的创建过程。此时该文件的 Inode 结构对象已经创建并且其 inode编号与文件名相关联文件内容尚未分配数据块。 需要注意的是上述过程是简化的描述实际上还涉及到文件系统的一些细节实现如磁盘空间分配策略、磁盘块的写入等。 2. 删除文件操作系统做了什么 首先我们可以确定一点删除文件用户必然会提供被删除文件的目录以及被删除文件的文件名 1、因此当删除一个文件时OS可以是确定这个被删除文件所在的目录那么操作系统会定位到该目录所属的块组 Block Group 。 2、然后操作系统遍历该块组的 Inode Table找到该目录文件的 Inode 结构对象。每个目录文件都有一个 Inode 结构对象其中记录着该目录文件的属性和数据块的索引。 3、接下来操作系统根据目录文件的 Inode结构对象 去访问该目录文件的数据块 block通过用户提供的文件名在数据块 block 中索引对应的 inode 编号这个 inode 编号就是被删除文件的 inode 编号。 4、通过这个 inode 编号找到对应的 Inode 结构对象通过这个结构对象得到所对应数据块 block 的编号在遍历当前块组的 Block Bitmap将所对应的数据块的编号所在的位置由 1 置为 0表示该 block 编号可用 5、遍历当前块组的 Inode Bitmap找到所要删除 inode编号的位置在由OS遍历并将该位置上的位由 1 设置为 0 表示该 inode 编号可用。 6、最后将该目录的 block 中的内容也就是这个被删除文件的 inode编号 和 文件名的映射关系给删除掉。 通过上述步骤操作系统完成了文件的删除过程。 从上面我们也可以看出删除文件并不一定意味着文件内容真正地从磁盘上被清除而是将对应的inode编号 和 block 编号 标记为可用。 3. 打开文件操作系统做了什么 首先我们可以确定一点打开文件用户必然会提供被打开文件的目录以及被打开文件的文件名 1、首先当用户打开一个文件时操作系统可以是确定这个被打开文件所在的目录那么操作系统会定位到该目录所属的块组 Block Group 。 2、随后操作系统通过遍历块组中的 Inode Table找到该目录文件的 Inode 结构对象。 3、接下来操作系统通过该结构对象找到该目录文件所对应的数据块 block 。 4、然后操作系统根据用户提供的文件名访问目录文件的 block 得到与该文件名关联的 inode 编号。 5、接着操作系统遍历该块组的 Inode Table找到该文件的 Inode 结构对象。 6、最后操作系统通过该结构对象找到该 inode编号与之对应的 block这些数据块 block 存放的就是该文件的内容OS通过读取这些内容并返回给上层用户 通过上述步骤操作系统完成了文件的打开过程可以将文件的内容提供给用户进行读取或其他操作。 2. 认识软硬链接 2.1. 软链接 ln -s log.txt log_s     ln 表示链接link的意思”-s 参数表示创建软链接symbolic link也称为符号链接。该命令的作用是在当前目录下创建一个名为 log_s 的新的符号链接(软链接)文件指向名为 log.txt 的文件。 unlink log_s  删除软链接文件 log_s  (当然也可以用 rm 命令) [XqVM-24-4-centos 12_16]$ ll total 8 -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile -rw-rw-r-- 1 Xq Xq 189 Dec 15 13:08 test.c [XqVM-24-4-centos 12_16]$ ln -s test.c log_s //创建软链接 log_s [XqVM-24-4-centos 12_16]$ ll total 8 lrwxrwxrwx 1 Xq Xq 6 Dec 15 13:10 log_s - test.c -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile -rw-rw-r-- 1 Xq Xq 189 Dec 15 13:08 test.c [XqVM-24-4-centos 12_16]$ unlink log_s //删除软链接log_s [XqVM-24-4-centos 12_16]$ ll total 8 -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile -rw-rw-r-- 1 Xq Xq 189 Dec 15 13:08 test.c [XqVM-24-4-centos 12_16]$ 那么软链接有什么应用场景呢 [XqVM-24-4-centos 12_16]$ pwd /home/Xq/2023_12/12_16 [XqVM-24-4-centos 12_16]$ ll total 12 -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile drwxrwxr-x 3 Xq Xq 4096 Dec 15 13:19 other -rw-rw-r-- 1 Xq Xq 189 Dec 15 13:08 test.c [XqVM-24-4-centos 12_16]$ tree other/ other/ -- bin-- my_test1 directory, 1 file [XqVM-24-4-centos 12_16]$ ll other/bin/my_test -rwxrwxr-x 1 Xq Xq 8360 Dec 15 13:19 other/bin/my_test [XqVM-24-4-centos 12_16]$ 假如我要在当前目录下运行 other/bin/这个目录下的my_test这个可执行程序我是不是要这样运行 [XqVM-24-4-centos 12_16]$ ./other/bin/my_test cowsay hello cowsay hello cowsay hello cowsay hello cowsay hello [XqVM-24-4-centos 12_16]$ 如果这个可执行程序所在的目录足够深或者这个目录下有非常多的文件那么这样操作是不是非常复杂而如果我们有了软链接那么我们就可以简化这个过程 [XqVM-24-4-centos 12_16]$ ll total 12 -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile drwxrwxr-x 3 Xq Xq 4096 Dec 15 13:19 other -rw-rw-r-- 1 Xq Xq 189 Dec 15 13:08 test.c [XqVM-24-4-centos 12_16]$ ln -s ./other/bin/my_test myexe // 创建软链接 [XqVM-24-4-centos 12_16]$ ll total 12 -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile lrwxrwxrwx 1 Xq Xq 19 Dec 15 13:27 myexe - ./other/bin/my_test drwxrwxr-x 3 Xq Xq 4096 Dec 15 13:19 other -rw-rw-r-- 1 Xq Xq 189 Dec 15 13:08 test.c [XqVM-24-4-centos 12_16]$ ./myexe // 利用软链接在当前目录下调用这个可执行程序文件 cowsay hello cowsay hello cowsay hello cowsay hello cowsay hello [XqVM-24-4-centos 12_16]$ Linux下的软链接非常类似我们在Windows下的快捷方式 2.2. 硬链接 ln ./other/bin/my_test my_exe_hard      这个命令中ln 表示链接link的意思。它可以用来创建硬链接hard link该命令的作用是在当前目录下创建一个名为 my_exe_hard 的 新硬链接文件指向路径为 ./other/bin/my_test 的文件 [XqVM-24-4-centos 12_16]$ ln ./other/bin/my_test my_exe_hard //创建硬链接文件 [XqVM-24-4-centos 12_16]$ ll total 24 -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile lrwxrwxrwx 1 Xq Xq 19 Dec 15 13:27 myexe - ./other/bin/my_test -rwxrwxr-x 2 Xq Xq 8360 Dec 15 13:19 my_exe_hard drwxrwxr-x 3 Xq Xq 4096 Dec 15 13:19 other -rw-rw-r-- 1 Xq Xq 189 Dec 15 13:08 test.c [XqVM-24-4-centos 12_16]$ ./my_exe_hard // 在当前目录下指向硬链接文件 cowsay hello cowsay hello cowsay hello cowsay hello cowsay hello [XqVM-24-4-centos 12_16]$ 可以看到硬链接也有快捷方式的作用那它和软链接有什么区别呢 2.3. 软链接和硬链接的区别 [XqVM-24-4-centos 12_16]$ ll -il total 8 1443607 -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile 1443608 -rw-rw-r-- 1 Xq Xq 189 Dec 15 13:08 test.c [XqVM-24-4-centos 12_16]$ [XqVM-24-4-centos 12_16]$ ln -s test.c test_s // 创建软链接 test_s [XqVM-24-4-centos 12_16]$ ll -il total 8 1443607 -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile 1443608 -rw-rw-r-- 1 Xq Xq 189 Dec 15 13:08 test.c 1443609 lrwxrwxrwx 1 Xq Xq 6 Dec 15 13:39 test_s - test.c [XqVM-24-4-centos 12_16]$ [XqVM-24-4-centos 12_16]$ ln test.c hard // 创建硬链接 hard [XqVM-24-4-centos 12_16]$ ll -il total 12 1443608 -rw-rw-r-- 2 Xq Xq 189 Dec 15 13:08 hard 1443607 -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile 1443608 -rw-rw-r-- 2 Xq Xq 189 Dec 15 13:08 test.c 1443609 lrwxrwxrwx 1 Xq Xq 6 Dec 15 13:39 test_s - test.c [XqVM-24-4-centos 12_16]$ 如图所示  软链接是具有自己独立的 inode编号 的软链接是一个独立文件有自己的 Inode 属性也有自己的数据块(保存的是指向文件的所在路径 文件名) 硬链接本质上根本就不是一个独立的文件而是文件系统中对同一文件的多个命名入口由于硬链接自己没有独立的 inode编号因此创建硬链接本质是在特定目录下的 block中填写一对文件名 和 inode编号的映射关系(即特定inode编号多了一个映射的文件名) 综上所述软硬链接最本质的区别就是 是否具有独立的 inode 编号(inode编号可以映射到 Inode属性) 这些框起来的数字代表什么呢 现在我们就知道了这个数字叫做硬链接数(即有多少个文件名和这个 inode 编有映射关系) 那么请问这个硬链接数保存在哪呢 --- 保存在 Inode 结构变量中即可以这样理解 struct inode{//文件的所有属性//数据int inode_number;int blocks[N];int ret; //硬链接数... };因此创建文件的本质是: 这个文件inode编号所映射的Inode属性中的硬连接数加一删除文件的本质是:  这个文件inode编号所映射的Inode属性中的硬连接数减1当硬连接数等于0时才会去真正删除这个文件这种方式基于引用计数reference counting维护着每个文件的硬链接数量。只有当硬链接数为0时文件系统才会清理并删除文件。这种方式能够确保文件不会被意外删除只有当没有任何硬链接引用它时才会被真正删除。 有了上面的理解我们就可以解释一个现象了 为什么我创建一个目录,它的默认硬连接数是2 如下 [XqVM-24-4-centos 12_16]$ ll -i total 8 1443607 -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile 1443608 -rw-rw-r-- 1 Xq Xq 189 Dec 15 13:08 test.c [XqVM-24-4-centos 12_16]$ mkdir bin/ [XqVM-24-4-centos 12_16]$ ll -i total 12 1443609 drwxrwxr-x 2 Xq Xq 4096 Dec 15 14:49 bin // 硬链接为2 1443607 -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile 1443608 -rw-rw-r-- 1 Xq Xq 189 Dec 15 13:08 test.c [XqVM-24-4-centos 12_16]$ 答案是每个目录下都会有这个 . 目录如下所示 . 我们之前学过叫做当前目录它为什么叫做当前目录呢那是因为这个目录的inode 编号和 bin目录的 inode编号 一样因此它们指向一个目录这就是硬链接的常见的应用场景 为什么在bin目录下创建一个新目录后此时bin目录的硬连接数变为3了呢答案是 .. 这个我们知道它代表着当前目录的上级路径那么在这里就是bin所在的目录了故bin的硬连接数为3  2.4. ACM时间  [XqVM-24-4-centos Tmp]$ stat test.cFile: ‘test.c’Size: 0 Blocks: 0 IO Block: 4096 regular empty file Device: fd01h/64769d Inode: 920992 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:13:50.890756209 0800 Modify: 2023-07-31 16:13:50.890756209 0800 Change: 2023-07-31 16:13:50.890756209 0800Birth: - Access: 文件最近被访问的时间 Modiefy:最后一次修改文件内容的时间 Change:最后一次修改文件属性的时间 [XqVM-24-4-centos Tmp]$ stat test.c File: ‘test.c’Size: 7 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920992 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:16:42.631007790 0800 Modify: 2023-07-31 16:16:40.873005223 0800 Change: 2023-07-31 16:16:40.873005223 0800Birth: - [XqVM-24-4-centos Tmp]$ cat test.c // 访问文件 cowsay [XqVM-24-4-centos Tmp]$ stat test.c File: ‘test.c’Size: 7 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920992 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:16:42.631007790 0800 Modify: 2023-07-31 16:16:40.873005223 0800 Change: 2023-07-31 16:16:40.873005223 0800Birth: - [XqVM-24-4-centos Tmp]$ echo i am a supercow test.c // 修改文件内容 [XqVM-24-4-centos Tmp]$ stat test.c File: ‘test.c’Size: 16 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920992 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:18:22.631153513 0800 Modify: 2023-07-31 16:18:21.313151596 0800 Change: 2023-07-31 16:18:21.313151596 0800Birth: - [XqVM-24-4-centos Tmp]$ 但是为什么我有时候访问文件的时候Access时间没有立即刷新有时候又立即刷新了呢那是因为在较新的Linux内核中Access时间不会被立即刷新而是有一定的时间间隔OS才会自动进行更新Access时间这样可以一定程度上防止用户和磁盘的频繁交互导致Linxu系统效率降低 Change:最后一次修改文件属性的时间 [XqVM-24-4-centos Tmp]$ ll total 4 -rw-rw-r-- 1 Xq Xq 10 Jul 31 16:21 test.c [XqVM-24-4-centos Tmp]$ stat test.c File: ‘test.c’Size: 10 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920992 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:21:54.648460680 0800 Modify: 2023-07-31 16:21:52.770457971 0800 Change: 2023-07-31 16:21:52.770457971 0800Birth: - [XqVM-24-4-centos Tmp]$ chmod ux test.c //修改文件属性 [XqVM-24-4-centos Tmp]$ stat test.c File: ‘test.c’Size: 10 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920992 Links: 1 Access: (0764/-rwxrw-r--) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:21:54.648460680 0800 Modify: 2023-07-31 16:21:52.770457971 0800 Change: 2023-07-31 16:24:29.633683738 0800 //只有Change时间改变了Birth: - [XqVM-24-4-centos Tmp]$ Modify:最后一次修改文件内容的时间 [XqVM-24-4-centos Tmp]$ clear [XqVM-24-4-centos Tmp]$ ll total 4 -rw-rw-r-- 1 Xq Xq 5 Jul 31 16:21 test.c [XqVM-24-4-centos Tmp]$ stat test.c File: ‘test.c’Size: 5 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920992 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:21:18.632408669 0800 Modify: 2023-07-31 16:21:18.571408581 0800 Change: 2023-07-31 16:21:18.571408581 0800Birth: - [XqVM-24-4-centos Tmp]$ echo haha test.c //修改文件内容 [XqVM-24-4-centos Tmp]$ stat test.c File: ‘test.c’Size: 10 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920992 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:21:54.648460680 0800 Modify: 2023-07-31 16:21:52.770457971 0800 Change: 2023-07-31 16:21:52.770457971 0800Birth: - [XqVM-24-4-centos Tmp]$ 修改文件内容Modify时间确实更改了但是令人感到奇怪的是为什么Change也改变了呢 原因是因为当我们在修改文件内容的时候也有可能会修改文件属性例如可能会更改文件的大小属性 [XqVM-24-4-centos Tmp]$ ll total 8 -rw-rw-r-- 1 Xq Xq 58 Jul 31 16:44 makefile -rw-rw-r-- 1 Xq Xq 67 Jul 31 16:44 test.c [XqVM-24-4-centos Tmp]$ make //第一次可以make gcc -o test test.c [XqVM-24-4-centos Tmp]$ make //第二次不能make了呢 make: test is up to date. [XqVM-24-4-centos Tmp]$ vim test.c //修改test.c的内容 [XqVM-24-4-centos Tmp]$ make //又能make了 gcc -o test test.c [XqVM-24-4-centos Tmp]$ make //又不能make了 make: test is up to date. [XqVM-24-4-centos Tmp]$ 为什么会出现这种现象呢 我们的gcc是如何判定这个源文件是最新的呢 [XqVM-24-4-centos Tmp]$ stat test.c //源文件的ACMFile: ‘test.c’Size: 87 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920993 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:44:43.309440972 0800 Modify: 2023-07-31 16:44:41.890438945 0800 Change: 2023-07-31 16:44:41.890438945 0800Birth: - [XqVM-24-4-centos Tmp]$ stat test //可执行程序的ACMFile: ‘test’Size: 8360 Blocks: 24 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920991 Links: 1 Access: (0775/-rwxrwxr-x) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:44:43.642441448 0800 Modify: 2023-07-31 16:44:43.339441015 0800 Change: 2023-07-31 16:44:43.339441015 0800Birth: - [XqVM-24-4-centos Tmp]$ 此时这个可执行文件的Modify时间较源文件的Modify更新/相等gcc认为此时就没有必要再去编译了因为源文件的内容已经编译完了没有新的改变。 [XqVM-24-4-centos Tmp]$ vim test.c //修改源文件的内容 [XqVM-24-4-centos Tmp]$ ls makefile test test.c [XqVM-24-4-centos Tmp]$ stat test.c //源文件的ACMFile: ‘test.c’Size: 127 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920993 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:50:54.634968344 0800 Modify: 2023-07-31 16:50:53.673966987 0800 Change: 2023-07-31 16:50:53.673966987 0800Birth: - [XqVM-24-4-centos Tmp]$ stat test //可执行文件的ACMFile: ‘test’Size: 8360 Blocks: 24 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920991 Links: 1 Access: (0775/-rwxrwxr-x) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:44:43.642441448 0800 Modify: 2023-07-31 16:44:43.339441015 0800 Change: 2023-07-31 16:44:43.339441015 0800Birth: -  [XqVM-24-4-centos Tmp]$ make   //此时我们发现可以make了 gcc -o test test.c [XqVM-24-4-centos Tmp]$ 当这个源文件的 Modify 时间比可执行程序 Modify 时间更新的时候gcc认为此时就有必要对这个源文件重新编译了此时就可以make了。 [XqVM-24-4-centos Tmp]$ ll total 20 -rw-rw-r-- 1 Xq Xq 58 Jul 31 16:44 makefile -rwxrwxr-x 1 Xq Xq 8360 Jul 31 16:51 test -rw-rw-r-- 1 Xq Xq 127 Jul 31 16:50 test.c [XqVM-24-4-centos Tmp]$ stat test.cFile: ‘test.c’Size: 127 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920993 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:50:54.634968344 0800 Modify: 2023-07-31 16:50:53.673966987 0800 Change: 2023-07-31 16:50:53.673966987 0800Birth: - [XqVM-24-4-centos Tmp]$ stat testFile: ‘test’Size: 8360 Blocks: 24 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920991 Links: 1 Access: (0775/-rwxrwxr-x) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 16:51:06.631985283 0800 Modify: 2023-07-31 16:51:06.480985070 0800 Change: 2023-07-31 16:51:06.480985070 0800Birth: - [XqVM-24-4-centos Tmp]$ make make: test is up to date. //当一个文件存在的情况touch代表更新这个文件的ACM时间 [XqVM-24-4-centos Tmp]$ touch test.c    [XqVM-24-4-centos Tmp]$ stat test.cFile: ‘test.c’Size: 127 Blocks: 8 IO Block: 4096 regular file Device: fd01h/64769d Inode: 920993 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1001/ Xq) Gid: ( 1001/ Xq) Access: 2023-07-31 17:00:29.631773689 0800 Modify: 2023-07-31 17:00:28.348771906 0800 Change: 2023-07-31 17:00:28.348771906 0800Birth: - [XqVM-24-4-centos Tmp]$ make  //此时既可以make了 gcc -o test test.c [XqVM-24-4-centos Tmp]$ makefile和gcc会根据ACM时间来判定源文件和可执行文件谁更新从而指导操作系统哪些源文件需要被重新编译。 3. 认识并制作动静态库 动态库Dynamic Library和静态库Static Library是两种不同的库文件形式它们有以下区别 1. 静态库    1. 静态库是在编译时将库文件的代码和数据复制到目标程序中。    2. 静态库以静态链接的方式与目标程序进行链接链接时将库中需要的代码复制到目标程序中生成一个包含所有代码的可执行文件。    3. 静态库的优点是易于使用在编译时已经包含了所有需要的代码程序执行时不依赖外部库文件。    4. 缺点是会增加最终可执行文件的大小并且当静态库更新时需要重新编译使用该库的程序。 2. 动态库    1. 动态库是在进程运行时被加载和链接的库文件。    2. 动态库以动态链接的方式与目标程序进行链接进程在运行时会动态加载并链接所需的库文件。    3. 动态库的优点是节省内存空间多个进程可以共享同一个动态库的实例减少重复代码的加载。    4. 缺点是需要依赖外部的库文件如果所需的动态库不可用或版本不匹配进程可能无法正常执行。 选择使用静态库还是动态库通常取决于具体的需求和场景。静态库适合于独立运行、较小的应用程序以及需要确保库的版本和稳定性的情况。动态库适合于共享使用、大型应用程序以及允许更新和替换库的情况。 3.1. 认识动静态库 首先有一个问题我们之前写C/C的可执行程序有使用过库吗 答案是的我们使用过库如何证明呢 [XqVM-24-4-centos 12_15]$ ll total 20 -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile -rwxrwxr-x 1 Xq Xq 8360 Dec 15 15:32 my_test -rw-rw-r-- 1 Xq Xq 200 Dec 15 15:32 test.c [XqVM-24-4-centos 12_15]$ ldd my_test //ldd显式可执行程序依赖的动态库linux-vdso.so.1 (0x00007fffd70fb000)libc.so.6 /lib64/libc.so.6 (0x00007f891a7e6000) // C标准库/lib64/ld-linux-x86-64.so.2 (0x00007f891abb4000) [XqVM-24-4-centos 12_15]$ ll /lib64/libc.so.6 lrwxrwxrwx 1 root root 12 Jul 25 2022 /lib64/libc.so.6 - libc-2.17.so // 一个软链接 [XqVM-24-4-centos 12_15]$ ll /lib64/libc-2.17.so -rwxr-xr-x 1 root root 2156592 May 19 2022 /lib64/libc-2.17.so //动态库本质上是一个文件 [XqVM-24-4-centos 12_15]$ 一般库分为两种动态库和 静态库 在Linux中如果是动态库库文件是以 .so 作为后缀的 如果是静态库库文件是以 .a 作为后缀的动静态库就是一个正常的磁盘文件 库文件的命名规则lib为库文件的前缀.a(.so)为静(动)态库的后缀前缀和后缀之间是库的真实名字。例如这个C标准库 libc-2.17.so 当去掉lib前缀去掉动态库的后缀 .so剩下的就是它的真实名字即 c-2.17 静态链接和动态链接 在Linux上例如C语言当gcc编译C的源程序的时候默认情况下是使用的动态链接即链接的是C标准的动态库而如果要采取静态链接的方法需要加上选项 -static告诉gcc编译器链接C标准的静态库如下 [XqVM-24-4-centos 8_1]$ ll total 8 -rw-rw-r-- 1 Xq Xq 59 Aug 1 18:53 makefile -rw-rw-r-- 1 Xq Xq 66 Aug 1 18:50 test.c [XqVM-24-4-centos 8_1]$ make gcc -o test test.c [XqVM-24-4-centos 8_1]$ ll total 20 -rw-rw-r-- 1 Xq Xq 59 Aug 1 18:53 makefile -rwxrwxr-x 1 Xq Xq 8360 Aug 1 18:57 test -rw-rw-r-- 1 Xq Xq 66 Aug 1 18:50 test.c [XqVM-24-4-centos 8_1]$ gcc test.c -o test_static -static // 采取静态链接 [XqVM-24-4-centos 8_1]$ ll total 864 -rw-rw-r-- 1 Xq Xq 59 Aug 1 18:53 makefile -rwxrwxr-x 1 Xq Xq 8360 Aug 1 18:57 test -rw-rw-r-- 1 Xq Xq 66 Aug 1 18:50 test.c -rwxrwxr-x 1 Xq Xq 861288 Aug 1 18:58 test_static [XqVM-24-4-centos 8_1]$ ldd test linux-vdso.so.1 (0x00007ffceb2af000)libc.so.6 /lib64/libc.so.6 (0x00007f7184b8a000)/lib64/ld-linux-x86-64.so.2 (0x00007f7184f58000) [XqVM-24-4-centos 8_1]$ file test //共享库 test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]be7ec904ebefa05706bd6f184f141636f998daaa, not stripped [XqVM-24-4-centos 8_1]$ ldd test_static not a dynamic executable [XqVM-24-4-centos 8_1]$ file test_static test_static: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]55ed29bbb2416686059f3d457ca0367ea6e946bc, not stripped [XqVM-24-4-centos 8_1]$ 如果没有安装静态库可以采取如下命令安装C/C的静态库 sudo yum install -y glibc-static //C的静态库 sudo yum install -y libstdc-static //CC的静态库 3.2. 静态库的制作 [XqVM-24-4-centos lib]$ pwd /home/Xq/2023_12/12_15/lib [XqVM-24-4-centos lib]$ ll total 20 -rw-rw-r-- 1 Xq Xq 63 Dec 15 18:51 add.c -rw-rw-r-- 1 Xq Xq 374 Dec 15 18:54 makefile -rw-rw-r-- 1 Xq Xq 62 Dec 15 18:58 my_add.h -rw-rw-r-- 1 Xq Xq 62 Dec 15 17:13 my_sub.h -rw-rw-r-- 1 Xq Xq 63 Dec 15 18:51 sub.c [XqVM-24-4-centos lib]$ 目标在lib这个目录下生成 libmy-math.a 这个静态库 3.2.1. add.c 文件 #include my_add.hint add(int x, int y) {return x y; } 3.2.2. my_add.h 文件 #pragma once #include stdio.hextern int add(int x,int y); 3.2.3. sub.c 文件 #include my_sub.hint sub(int x,int y) {return x - y; } 3.2.4. my_sub.h 文件 #pragma once #include stdio.hextern int sub(int x,int y);3.2.5. makefile文件的实现 libmy-math.a:add.o sub.oar -rc $ $^ # ar 是一个归档工具, r --- replace c --- creat,没有就创建,有了就替换%.o:%.cgcc -c $ # 将上面所有的源文件一个一个的编写生成 .o 文件.PHONY:output output:mkdir ./output# my_lib 是 库文件所在的目录mkdir ./output/my_libcp libmy-math.a ./output/my_lib # 将库文件拷贝到 my_lib 中# my_include 是头文件所在的目录mkdir ./output/my_includecp *.h ./output/my_include # 将头文件拷贝到 my_include 中.PHONY:install install:# 头文件gcc的搜索路径: /usr/includecp ./output/my_include/*.h /usr/include -f# 库文件的搜索路径是 /lib64cp ./output/my_lib/libmy-math.a /lib64/ -f.PHONY:clean clean:rm -rf output *.o libmy-math.a %是一个通配符%.o 可以把当前路径的所有.o文件展开 汇编形成的 .o 叫做可重定向目标文件 %.o:%.c gcc -c $ 的作用就是 将上面的%.c展开后的源文件一个一个的进行编译生成目标文件 *代表通配符 rm *.o 代表将当前目录下所有.o文件删除 生成静态库 ar -rc $ $$^ ar 是 gnu归档工具rc表示(replace and create) //没有就创建有了就替代 查看静态库中的目录列表 ar -tv  libmy-math.a t列出静态库中的文件 vverbose 详细信息 测试 [XqVM-24-4-centos lib]$ ll total 20 -rw-rw-r-- 1 Xq Xq 63 Dec 15 18:51 add.c -rw-rw-r-- 1 Xq Xq 374 Dec 15 18:54 makefile -rw-rw-r-- 1 Xq Xq 62 Dec 15 18:58 my_add.h -rw-rw-r-- 1 Xq Xq 62 Dec 15 17:13 my_sub.h -rw-rw-r-- 1 Xq Xq 63 Dec 15 18:51 sub.c [XqVM-24-4-centos lib]$[XqVM-24-4-centos lib]$ make //生成对应的.o文件,并将其归档生成库文件本身 gcc -c add.c gcc -c sub.c ar -rc libmy-math.a add.o sub.o [XqVM-24-4-centos lib]$ ll total 32 -rw-rw-r-- 1 Xq Xq 63 Dec 15 18:51 add.c -rw-rw-r-- 1 Xq Xq 1240 Dec 15 19:03 add.o -rw-rw-r-- 1 Xq Xq 2688 Dec 15 19:03 libmy-math.a -rw-rw-r-- 1 Xq Xq 374 Dec 15 18:54 makefile -rw-rw-r-- 1 Xq Xq 62 Dec 15 18:58 my_add.h -rw-rw-r-- 1 Xq Xq 62 Dec 15 17:13 my_sub.h -rw-rw-r-- 1 Xq Xq 63 Dec 15 18:51 sub.c -rw-rw-r-- 1 Xq Xq 1240 Dec 15 19:03 sub.o [XqVM-24-4-centos lib]$ [XqVM-24-4-centos lib]$ make output // 打包到output这个目录 mkdir ./output mkdir ./output/my_lib cp libmy-math.a ./output/my_lib mkdir ./output/my_include cp *.h ./output/my_include [XqVM-24-4-centos lib]$ ll total 36 -rw-rw-r-- 1 Xq Xq 63 Dec 15 18:51 add.c -rw-rw-r-- 1 Xq Xq 1240 Dec 15 19:03 add.o -rw-rw-r-- 1 Xq Xq 2688 Dec 15 19:03 libmy-math.a -rw-rw-r-- 1 Xq Xq 374 Dec 15 18:54 makefile -rw-rw-r-- 1 Xq Xq 62 Dec 15 18:58 my_add.h -rw-rw-r-- 1 Xq Xq 62 Dec 15 17:13 my_sub.h drwxrwxr-x 4 Xq Xq 4096 Dec 15 19:04 output -rw-rw-r-- 1 Xq Xq 63 Dec 15 18:51 sub.c -rw-rw-r-- 1 Xq Xq 1240 Dec 15 19:03 sub.o [XqVM-24-4-centos lib]$[XqVM-24-4-centos lib]$ tree output/ output/ |-- my_include | |-- my_add.h | -- my_sub.h -- my_lib-- libmy-math.a2 directories, 3 files [XqVM-24-4-centos lib]$[XqVM-24-4-centos lib]$ ar -tv ./output/my_lib/libmy-math.a // 查看静态库的内容 rw-rw-r-- 1001/1001 1240 Dec 15 19:03 2023 sub.o rw-rw-r-- 1001/1001 1240 Dec 15 19:03 2023 add.o [XqVM-24-4-centos lib]$ 什么的现象符合我们的预期 。 //如果没有ar命令那么需要我们手动安装: sudo yum install -y binutils-aarch64-linux-gnu //ar命令安装 静态库本质是就是将所有的 .o 文件进行打包打包后生成的文件称之为库文件但一个库不仅包含库文件本身还包含头文件说明文档等等而我们在这里将库文件和头文件分别保存到output/my_lib和output/my_include中。 3.3. 静态库的使用 现在我们自己的静态库制作完成那么如何使用呢 #include my_add.h #include my_sub.hint main() {int left 10;int right 20;int ret1 add(left,right);int ret2 sub(left,right);printf(ret1 %d\n,ret1);printf(ret2 %d\n,ret2);return 0; }现象如下 [XqVM-24-4-centos 12_15]$ gcc -o my_test test.c test.c:1:20: fatal error: my_add.h: No such file or directory#include my_add.h^ compilation terminated. //错误原因: 编译器只会默认在当前目录下找这两个头文件 [XqVM-24-4-centos 12_15]$------------------------------------------------------ //因此需要加上选项 -I 告诉编译器这个库的头文件所在路径 [XqVM-24-4-centos 12_15]$ gcc -o my_test test.c -I ./lib/output/my_include/ /tmp/cc9FF66o.o: In function main: test.c:(.text0x21): undefined reference to add test.c:(.text0x33): undefined reference to sub collect2: error: ld returned 1 exit status // 但是又发生了链接错误,因为编译器找不到add和sub的实现 // 需要我们显式声明库文件所在的路径 [XqVM-24-4-centos 12_15]$------------------------------------------------------ //因此我们需要告诉gcc我们的库所在的路径 [XqVM-24-4-centos 12_15]$ gcc -o my_test test.c -I ./lib/output/my_include/ -L ./lib/output/my_lib/ /tmp/ccH46v2n.o: In function main: test.c:(.text0x21): undefined reference to add test.c:(.text0x33): undefined reference to sub collect2: error: ld returned 1 exit status // 但是很不幸依然报了链接错误因为编译器不知道链接 // 哪一个库(有可能这个路径下不止一个库)因此我们要明确库的真实名字 [XqVM-24-4-centos 12_15]$ ------------------------------------------------------ [XqVM-24-4-centos 12_15]$ gcc -o my_test test.c -I ./lib/output/my_include/ -L ./lib/output/my_lib/ -l libmy-math.a /usr/bin/ld: cannot find -llibmy-math.a collect2: error: ld returned 1 exit status //依然报错gcc不认识这个库的名字因为库的真实名字是 去掉lib前缀和 .a-(.so-)后缀 [XqVM-24-4-centos 12_15]$------------------------------------------------------ // 要带上库的真实名字 [XqVM-24-4-centos 12_15]$ gcc -o my_test test.c -I ./lib/output/my_include/ -L ./lib/output/my_lib/ -l my-math// 编译成功 [XqVM-24-4-centos 12_15]$ ll total 24 drwxrwxr-x 3 Xq Xq 4096 Dec 15 19:04 lib -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile -rwxrwxr-x 1 Xq Xq 8472 Dec 15 19:09 my_test -rw-rw-r-- 1 Xq Xq 231 Dec 15 18:55 test.c [XqVM-24-4-centos 12_15]$ ./my_test ret1 30 ret2 -10 [XqVM-24-4-centos 12_15] 解释: -I(这里是大写的 i)  指明的是头文件搜索路径 -L 指定库文件搜索路径 -l 在特定搜索路径下使用哪一个库即明确库的真实名字 但是有个问题我们之前写过的代码也用了库(eg:C/C标准库)为什么就没有指明这些选项呢 这是因为之前的库在系统的默认路径下: eg:/lib64//usr/lib//usr/include/等编译器是可以识别这些路径的换句话说如果我不想带这些选项我是不是可以把对应的库和头文件拷贝到默认路径下这样是可以的但是会带来污染问题尽量不要这样做上面的过程也就是一般软件的安装过程 但是我们在这里测试一下将我们的头文件拷贝到gcc编译器的默认搜索路径即 /usr/include/下将库文件拷贝到库文件的默认搜索路径即 /lib64/ 或者 usr/lib64/如下 [XqVM-24-4-centos lib]$ sudo make install [sudo] password for Xq: cp ./output/my_include/*.h /usr/include -f // 拷贝头文件 cp ./output/my_lib/libmy-math.a /lib64/ -f // 拷贝库文件// 上面这个过程就是软件安装的过程// 上面这个过程就是软件安装的过程[XqVM-24-4-centos 12_15]$ ll /lib64/libmy-math.a -rw-r--r-- 1 root root 2688 Dec 15 19:13 /lib64/libmy-math.a [XqVM-24-4-centos 12_15]$ ll /usr/include/my_add.h -rw-r--r-- 1 root root 62 Dec 15 19:13 /usr/include/my_add.h [XqVM-24-4-centos 12_15]$ ll /usr/include/my_sub.h -rw-r--r-- 1 root root 62 Dec 15 19:13 /usr/include/my_sub.h [XqVM-24-4-centos 12_15]$// 可以看到,我们成功将库文件和头文件拷贝到了特定目录下 [XqVM-24-4-centos 12_15]$ ll total 12 drwxrwxr-x 3 Xq Xq 4096 Dec 15 19:04 lib -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile -rw-rw-r-- 1 Xq Xq 231 Dec 15 19:16 test. [XqVM-24-4-centos 12_15]$ gcc test.c -o my_test /tmp/ccWtBaL4.o: In function main: test.c:(.text0x21): undefined reference to add test.c:(.text0x33): undefined reference to sub collect2: error: ld returned 1 exit status [XqVM-24-4-centos 12_15]$ 可是为什么报错了呢 注意我们自己写的库是第三方库该库既不是语言提供的也不是OS自带的而是用户自己实现的因此当我们将库文件拷贝到系统的默认搜索路径后编译器并不知道要连接这个库因此需要我们指明库文件的名字如下 [XqVM-24-4-centos 12_15]$ gcc test.c -o my_test -l my-math // 指明库的真实名字 // 编译成功 [XqVM-24-4-centos 12_15]$ ll total 24 drwxrwxr-x 3 Xq Xq 4096 Dec 15 19:04 lib -rw-rw-r-- 1 Xq Xq 65 Dec 15 13:07 makefile -rwxrwxr-x 1 Xq Xq 8472 Dec 15 19:44 my_test -rw-rw-r-- 1 Xq Xq 231 Dec 15 19:16 test.c [XqVM-24-4-centos 12_15]$ ./my_test ret1 30 ret2 -10 [XqVM-24-4-centos 12_15]$ 我们在这里是做测试演示这个结果实际上我们最好不要将我们写的库拷贝到头文件或者库文件的默认搜索路径因为这会带来命名污染等其他问题。 3.4. 动态库的制作 shared 表示生成共享库格式。 fPIC编译器生成位置无关代码用于编译动态链接库确保生成的动态库可以在内存中的任何位置加载和执行而不会与其他库或代码发生冲突。 3.4.1. add.c 文件 #include my_add.hint add(int x, int y) {return x y; } 3.4.2. my_add.h 文件 #pragma once #include stdio.hextern int add(int x,int y); 3.4.3. sub.c 文件 #include my_sub.hint sub(int x,int y) {return x - y; } 3.4.4. my_sub.h 文件 #pragma once #include stdio.hextern int sub(int x,int y); 3.4.5. Makefile 文件的实现  libmy-math.so:sub.o add.ogcc -shared -o $ $^ # -shared就是告诉gcc编译器,我要生产动态库%.o:%.cgcc -fPIC -c $ # -fPIC 每一个.o 文件的生成都需要 -fPIC 生成位置无关码.PHONY:output output:mkdir output mkdir ./output/my_include # 将所有头文件拷贝到 my_include 目录下cp ./*.h ./output/my_includemkdir ./output/my_lib # 将库文件拷贝到 my_lib 目录下cp libmy-math.so ./output/my_lib .PHONY:install install:# 将头文件安装到头文件的默认搜索路径 /usr/include/目录下cp ./output/my_include/*.h /usr/include/# 将库文件安装到库文件的默认搜索路径 /lib64/ cp ./output/my_lib/libmy-math.so /lib64/.PHONY:clean clean:rm -rf libmy-math.so *.o output测试如下 [XqVM-24-4-centos lib]$ ll total 20 -rw-rw-r-- 1 Xq Xq 63 Dec 16 16:40 add.c -rw-rw-r-- 1 Xq Xq 388 Dec 16 16:50 Makefile -rw-rw-r-- 1 Xq Xq 62 Dec 16 16:41 my_add.h -rw-rw-r-- 1 Xq Xq 62 Dec 16 16:41 my_sub.h -rw-rw-r-- 1 Xq Xq 63 Dec 16 16:40 sub.c [XqVM-24-4-centos lib]$ make // 生成库文件 gcc -fPIC -c sub.c gcc -fPIC -c add.c gcc -shared -o libmy-math.so sub.o add.o [XqVM-24-4-centos lib]$ ll total 36 -rw-rw-r-- 1 Xq Xq 63 Dec 16 16:40 add.c -rw-rw-r-- 1 Xq Xq 1240 Dec 16 16:54 add.o -rwxrwxr-x 1 Xq Xq 7936 Dec 16 16:54 libmy-math.so -rw-rw-r-- 1 Xq Xq 388 Dec 16 16:50 Makefile -rw-rw-r-- 1 Xq Xq 62 Dec 16 16:41 my_add.h -rw-rw-r-- 1 Xq Xq 62 Dec 16 16:41 my_sub.h -rw-rw-r-- 1 Xq Xq 63 Dec 16 16:40 sub.c -rw-rw-r-- 1 Xq Xq 1240 Dec 16 16:54 sub.o [XqVM-24-4-centos lib]$ make output // 打包 mkdir output mkdir ./output/my_include cp ./*.h ./output/my_include mkdir ./output/my_lib cp libmy-math.so ./output/my_lib [XqVM-24-4-centos lib]$ ll total 40 -rw-rw-r-- 1 Xq Xq 63 Dec 16 16:40 add.c -rw-rw-r-- 1 Xq Xq 1240 Dec 16 16:54 add.o -rwxrwxr-x 1 Xq Xq 7936 Dec 16 16:54 libmy-math.so -rw-rw-r-- 1 Xq Xq 388 Dec 16 16:50 Makefile -rw-rw-r-- 1 Xq Xq 62 Dec 16 16:41 my_add.h -rw-rw-r-- 1 Xq Xq 62 Dec 16 16:41 my_sub.h drwxrwxr-x 4 Xq Xq 4096 Dec 16 16:54 output -rw-rw-r-- 1 Xq Xq 63 Dec 16 16:40 sub.c -rw-rw-r-- 1 Xq Xq 1240 Dec 16 16:54 sub.o [XqVM-24-4-centos lib]$ tree output/ output/ |-- my_include | |-- my_add.h | -- my_sub.h -- my_lib-- libmy-math.so2 directories, 3 files [XqVM-24-4-centos lib]$ 符合预期动态库的制作成功 3.5. 动态库的使用 此时我这个test.c想用 ./lib/output 下的方法那么如何使用呢即如何使用动态库 [XqVM-24-4-centos 12_16]$ ll total 4 drwxrwxr-x 3 Xq Xq 4096 Dec 16 16:54 lib -rw-rw-r-- 1 Xq Xq 0 Dec 16 16:39 test.c [XqVM-24-4-centos 12_16]$ tree . |-- lib | |-- add.c | |-- add.o | |-- libmy-math.so | |-- Makefile | |-- my_add.h | |-- my_sub.h | |-- output | | |-- my_include | | | |-- my_add.h | | | -- my_sub.h | | -- my_lib | | -- libmy-math.so | |-- sub.c | -- sub.o -- test.c4 directories, 12 files [XqVM-24-4-centos 12_16]$ 3.5.1. test.c #include my_add.h #include my_sub.hint main() {int left 10;int right 20;int ret1 add(left,right);int ret2 sub(left,right);printf(ret1 %d\n,ret1);printf(ret2 %d\n,ret2);return 0; }3.5.2. 使用动态库  [XqVM-24-4-centos 12_16]$ ll total 8 drwxrwxr-x 3 Xq Xq 4096 Dec 16 18:15 lib -rw-rw-r-- 1 Xq Xq 230 Dec 16 17:01 test.c [XqVM-24-4-centos 12_16]$ gcc -o my_test test.c test.c:1:20: fatal error: my_add.h: No such file or directory#include my_add.h^ compilation terminated. # 编译报错, gcc 在库文件的默认搜索路径/usr/include找不到所需要的头文件 # 因此我们需要 -I 指源代码所依赖的头文件的路径[XqVM-24-4-centos 12_16]$ gcc -o my_test test.c -I ./lib/output/my_include/ /tmp/ccNz26Vr.o: In function main: test.c:(.text0x21): undefined reference to add test.c:(.text0x33): undefined reference to sub collect2: error: ld returned 1 exit status # 链接错误, 在库文件的默认搜索路径找不到我们所依赖的库,即找不到对应方法的实现 # 故我们需要 -L 指明源代码所依赖的库文件的路径[XqVM-24-4-centos 12_16]$ gcc -o my_test test.c -I ./lib/output/my_include/ -L ./lib/output/my_lib/ /tmp/ccErAZ1P.o: In function main: test.c:(.text0x21): undefined reference to add test.c:(.text0x33): undefined reference to sub collect2: error: ld returned 1 exit status # 仍然报了链接错误,原因,我们指明的库文件所在的路径下不止一个库 # 因此我们需要用 -l 指明库的真实名字(去掉lib前缀,去掉动态库的.so后缀)[XqVM-24-4-centos 12_16]$ gcc -o my_test test.c -I ./lib/output/my_include/ -L ./lib/output/my_lib/ -l my-math # 编译链接成功,生成了可执行程序my_test[XqVM-24-4-centos 12_16]$ ll total 20 drwxrwxr-x 3 Xq Xq 4096 Dec 16 18:15 lib -rwxrwxr-x 1 Xq Xq 8432 Dec 16 18:17 my_test -rw-rw-r-- 1 Xq Xq 230 Dec 16 17:01 test.c [XqVM-24-4-centos 12_16]$ 可以看到我们通过让该C源代码编译后链接我们自己实现的动态库生成了我们的可执行程序可是这个可执行程序可以运行吗现象如下 什么情况为什么这个当可执行程序变为进程后运行失败了呢 从现象来看我们的动态库是没有地址的也就是说当可执行程序变为进程后找不到我们实现的动态库的地址可是我们不是在形成可执行程序的时候指明了我的动态库所在的路径吗为什么进程运行的时候找不到呢 3.5.3. 动态库的加载 可执行程序本质上是磁盘上的一个二进制文件而动态库本质上也是磁盘上的一个二进制文件即动态库是一个独立的库文件动态链接的时候动态库和可执行程序是进行分批加载的也就是说会先将 test.c 的代码加载到物理内存中如图所示 当 test.c 需要调用动态库的方法的时候此时再将动态库加载到物理内存中 此时当 test.c 要用动态库的实现那么只需要通过地址跳转跳到共享区对应的代码实现即可 甚至这时候如果其他进程也想用这个动态库的实现那么此时只需要将物理内存的动态库代码与这些进程的页表构建映射关系即可那么此时就可以让动态库只被加载一次 就可以让多个进程共享该库的代码如图所示 可是你说了这么多我们看到的现象是运行失败了啊为什么呢首先我们知道要加载动态库首先得要知道动态库在哪里可是我不是告诉了动态库的的路径吗 答案是我们只是告诉了 gcc 编译器我们的动态库的路径却没有告诉加载器动态库所在的路径啊当我这个进程运行起来且要使用动态库的方法的时候需要将该动态库加载到物理内存而加载这个动作是需要加载器帮助的并且加载动态库的前提是需要知道动态库所在的路径可是我们只是将动态库的路径告诉了 gcc 编译器加载器不知道啊 故加载动态库失败因此进程运行时找不到动态库的实现那么报错就合乎情理了因此在上面的现象中我们也可以看出当找不到动态库的时候就会报类似于 cannot open shared object file: No such file or directory 的错误 OK你说动态库有这样的问题那为什么静态库没有这种问题呢 答案是静态库在编译链接形成可执行程序后就已将将静态库的实现一并链接到了这个可执行程序中它将完全独立于静态库的存在并不依赖于静态库文件的加载和链接。 由于静态库已经包含在可执行程序中所以可执行程序在运行时不需要加载外部的静态库文件。这使得静态库没有动态库的路径查找和加载的问题。即使静态库文件在运行时被删除或不可用生成的可执行程序依然可以正常运行因为它已经包含了所需的静态库的实现。 静态库的优势在于它的便携性和可独立性在分发可执行程序时不需要担心共享库版本的兼容性问题但也因此导致了更大的可执行程序体积且如果多个可执行程序共用了该静态库可能导致内存中存在着大量的重复的代码造成空间浪费 好了你说了这么多我该如何解决动态库加载和路径查找的问题呢我们在这里提供三种方法用于解决动态库的加载和路径查找的问题 方法一将动态库文件拷贝到系统库文件的默认搜索路径下 例如 /lib64/或者 /usr/lib/但不推荐这种做法太过于粗糙带来了命名污染问题甚至是版本不兼容等问题。 方法二设置LD_LIBRARY_PATH环境变量 LD_LIBRARY_PATH 是一个环境变量它用于指定动态链接器查找共享库动态库时的路径。因此我们的做法就是将我们实现的动态库所在的路径添加到这个环境变量中如下 [XqVM-24-4-centos 12_16]$ ll total 20 drwxrwxr-x 3 Xq Xq 4096 Dec 16 18:15 lib -rwxrwxr-x 1 Xq Xq 8432 Dec 16 18:17 my_test -rw-rw-r-- 1 Xq Xq 230 Dec 16 17:01 test.c [XqVM-24-4-centos 12_16]$ ./my_test ./my_test: error while loading shared libraries: libmy-math.so: cannot open shared object file: No such file or directory// 找不到动态库所在的路径 [XqVM-24-4-centos 12_16]$ ldd my_test linux-vdso.so.1 (0x00007ffc6b5bc000)libmy-math.so not found libc.so.6 /lib64/libc.so.6 (0x00007f71bf792000)/lib64/ld-linux-x86-64.so.2 (0x00007f71bfb60000)// 查看LD_LIBRARY_PATH 环境变量的内容 [XqVM-24-4-centos 12_16]$ echo $LD_LIBRARY_PATH :/home/Xq/.VimForCpp/vim/bundle/YCM.so/el7.x86_64 [XqVM-24-4-centos 12_16]$ tree lib/ lib/ |-- add.c |-- add.o |-- libmy-math.so |-- Makefile |-- my_add.h |-- my_sub.h |-- output | |-- my_include | | |-- my_add.h | | -- my_sub.h | -- my_lib | -- libmy-math.so |-- sub.c -- sub.o3 directories, 11 files// 导环境变量,将动态库所在的路径添加到LD_LIBRARY_PATH环境变量中 [XqVM-24-4-centos 12_16]$ export LD_LIBRARY_PATH$LD_LIBRARY_PATH:~/2023_12/12_16/lib/output/my_lib/ // 添加成功 [XqVM-24-4-centos 12_16]$ echo $LD_LIBRARY_PATH :/home/Xq/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/Xq/2023_12/12_16/lib/output/my_lib/ [XqVM-24-4-centos 12_16]$ ldd my_test linux-vdso.so.1 (0x00007fff485d3000)// 此时这个动态库就找到了libmy-math.so /home/Xq/2023_12/12_16/lib/output/my_lib/libmy-math.so (0x00007f55bf5d3000)libc.so.6 /lib64/libc.so.6 (0x00007f55bf205000)/lib64/ld-linux-x86-64.so.2 (0x00007f55bf7d5000)// 可执行程序运行成功 [XqVM-24-4-centos 12_16]$ ./my_test ret1 30 ret2 -10 [XqVM-24-4-centos 12_16]$ 但需要注意的是我们现在设置的这个环境变量只是内存级别的环境变量当这个shell关闭后这个环境变量就会消失如果想让这个环境变量永久有效那么需要更改配置文件 ~/.bash_profile 或者  ~/.bashrc。 方法三添加配置文件 在 /etc/ld.so.conf.d/ 目录下有一系列的配置文件在大多数 Linux 系统中动态链接器使用这些配置文件来指定需要搜索的动态库的路径。 每个配置文件以一行表示一个目录路径动态链接器将按照配置文件的顺序依次搜索这些路径以寻找共享库文件。可以在这些配置文件中添加额外的路径使动态链接器能够找到非默认搜索路径下的动态库。 要注意的是在修改或创建新的配置文件后一般需要使用 ldconfig 命令来重新加载动态库配置以使新的配置文件生效即刷新动态库缓存如下 [XqVM-24-4-centos 12_16]$ ll total 20 drwxrwxr-x 3 Xq Xq 4096 Dec 16 18:15 lib -rwxrwxr-x 1 Xq Xq 8432 Dec 16 18:17 my_test -rw-rw-r-- 1 Xq Xq 230 Dec 16 17:01 test.c [XqVM-24-4-centos 12_16]$ ldd my_test linux-vdso.so.1 (0x00007fffe85c0000)libmy-math.so not foundlibc.so.6 /lib64/libc.so.6 (0x00007fb27425c000)/lib64/ld-linux-x86-64.so.2 (0x00007fb27462a000) // LD_LIBRARY_PATH这个环境变量是没有这个动态库的路径的 [XqVM-24-4-centos 12_16]$ echo $LD_LIBRARY_PATH :/home/Xq/.VimForCpp/vim/bundle/YCM.so/el7.x86_64[XqVM-24-4-centos 12_16]$ ll /home/Xq/2023_12/12_16/lib/output/my_lib/ total 8 -rwxrwxr-x 1 Xq Xq 7936 Dec 16 18:15 libmy-math.so // 在/etc/ld.so.conf.d这个目录下新建一个配置文件 [XqVM-24-4-centos 12_16]$ sudo touch /etc/ld.so.conf.d/Xq.conf // 将我这个动态库的绝对路径写入这个配置文件Xq.conf即可 [XqVM-24-4-centos 12_16]$ sudo vim /etc/ld.so.conf.d/Xq.conf // 写入成功 [XqVM-24-4-centos 12_16]$ cat /etc/ld.so.conf.d/Xq.conf /home/Xq/2023_12/12_16/lib/output/my_lib/// 刷新动态库缓存,让Xq.conf这个新的配置文件生效 [XqVM-24-4-centos 12_16]$ sudo ldconfig [XqVM-24-4-centos 12_16]$ ldd my_test linux-vdso.so.1 (0x00007ffd9fb81000)// 此时这个动态库就有路径了libmy-math.so /home/Xq/2023_12/12_16/lib/output/my_lib/libmy-math.so (0x00007f85550d4000)libc.so.6 /lib64/libc.so.6 (0x00007f8554d06000)/lib64/ld-linux-x86-64.so.2 (0x00007f85552d6000) // 运行成功 [XqVM-24-4-centos 12_16]$ ./my_test ret1 30 ret2 -10 [XqVM-24-4-centos 12_16]$ 注意只有这个配置文件存在那么这个动态库的路径就可以找到也就是说即使这次将shell关闭重新打开一个shell也不会影响动态链接器找到这个动态库的路径 方法四利用软链接 软链接Symbolic link也称为符号链接或软链接是一个指向目标文件或目录的特殊文件。它包含了指向目标文件或目录的路径信息。 软链接这种方法同样不推荐使用但是在这里主要是看看软链接如何使用的如下 [XqVM-24-4-centos 12_16]$ ll total 20 drwxrwxr-x 3 Xq Xq 4096 Dec 16 18:15 lib -rwxrwxr-x 1 Xq Xq 8432 Dec 16 18:17 my_test -rw-rw-r-- 1 Xq Xq 230 Dec 16 17:01 test.c[XqVM-24-4-centos 12_16]$ ldd my_test linux-vdso.so.1 (0x00007fff74990000)libmy-math.so not foundlibc.so.6 /lib64/libc.so.6 (0x00007f1ae7d99000)/lib64/ld-linux-x86-64.so.2 (0x00007f1ae8167000) // 在库文件的默认搜索路径 /lib64/ 下创建软链接 libmy-math.so, [XqVM-24-4-centos 12_16]$ sudo ln -s /home/Xq/2023_12/12_16/lib/output/my_lib/libmy-math.so /lib64/libmy-math.so [sudo] password for Xq: // 创建成功 [XqVM-24-4-centos 12_16]$ ll /lib64/libmy-math.so lrwxrwxrwx 1 root root 54 Dec 16 20:46 /lib64/libmy-math.so - /home/Xq/2023_12/12_16/lib/output/my_lib/libmy-math.so // 此时 加载器就可以找到该动态库的地址了 [XqVM-24-4-centos 12_16]$ ldd my_test linux-vdso.so.1 (0x00007ffe08bb2000)libmy-math.so /lib64/libmy-math.so (0x00007fb6bd6bf000)libc.so.6 /lib64/libc.so.6 (0x00007fb6bd2f1000)/lib64/ld-linux-x86-64.so.2 (0x00007fb6bd8c1000) // 运行成功 [XqVM-24-4-centos 12_16]$ ./my_test ret1 30 ret2 -10 [XqVM-24-4-centos 12_16]$ 至此我们提供了四种方案解决动态库加载和路径查找的问题 补充 动态链接器Dynamic Linker也被称为加载器Loader。 动态链接器是操作系统的一部分负责在进程运行时将动态库加载到内存中并将进程与这些库进行链接。它的主要任务是解析和处理程序中对动态库的引用将动态库的代码和数据加载到合适的内存地址并完成地址重定位、符号解析等操作以实现动态链接的过程。 加载器Loader是一个更广泛的术语涵盖了动态链接器和其他类型的加载器。加载器的主要目标是从存储介质如硬盘中将可执行文件加载到内存中并执行它们。因此加载器还包括操作系统中负责加载可执行文件的静态链接器、装载器Loader、执行器Executor等。而动态链接器是加载器的一个特定类型用于处理动态链接的情况。 总的来说动态链接器是加载器的一个子集其在程序运行时负责加载动态库并进行链接。加载器是一个更广义的术语包括了动态链接器在内负责将可执行文件加载到内存并执行。 a. 如果我们只有静态库没办法gcc只能针对该库进行静态链接 b. 如果动静态库同时存在gcc 默认链接的就是动态库 c. 如果动静态库同时存在我非要链接静态库呢 那么就需要带上 -static 选项告诉 gcc 编译器编译时采用静态链接因此 -static 的意义摒弃默认优先链接动态库的原则而直接链接静态库的方案 我们在C/C中为什么有时候写代码的时候有时候在 .h 放上声明在 .c/.cpp 放上实现为什么要这么设计呢 一方面是方便维护但最主要的是为了方便我们制作库但如果是开源的那么可以将实现和声明放在一起 库的存在一方面是方便使用可以大大减少我们开发的周期提高软件自身的质量另一方面是私密安全不想暴露源文件的实现细节
http://www.yutouwan.com/news/425759/

相关文章:

  • 双城网站建设哪家好企业网站优化分为
  • 百达翡丽手表网站商城微网站建设多少钱
  • 广州 企业网站建设ui设计包括哪些场景
  • 梅花手表网站垄断了网站建设
  • 网站访问量的单位做网站包括哪些
  • 佛山网站建设公司3lue自创品牌策划方案范文
  • 添加网站绑定主机名代码做网站的软件
  • 网站建设在电子商务中的作用wordpress 推荐 主题
  • php 设置网站根目录生鲜网站建设规划书范文
  • 手机设计软件官方下载新乡网站seo
  • 曼网企业名录搜索软件襄樊seo快速排名
  • 南宁购物网站建设企业网站建设费用计入哪个科目
  • 广州建站方法动易网站 价格
  • 上海沙龙网站建设有没有网站免费的
  • 网站制作品牌有哪些什么是网络营销定价中除免费策略外
  • 孝感市建设局网站杭州建站平台
  • 电商网站主题针对人群不同 网站做细分
  • 苏州做网站公司有哪些cpa个人网站怎么做
  • 给自己家的公司做网站好做吗网页制作与设计教程
  • 网站制作价格怎么算服装设计图片
  • 网站制作公司浩森宇特网站建设的固定资产包括哪些
  • 网站后缀意思wordpress 调用文章分类
  • 深圳网站建设黄浦网络-技术差淘宝购物网
  • 想做个赚钱的网站不知道做那种网站首页设计风格有哪些
  • 签合网站是哪个多媒体展厅哪家公司好
  • 广州市住房和建设局网站手机在线制作图片
  • 邢台学校网站建设报价网站制作开发
  • 商务网站建设与维护论文抖音电商官网
  • 快速搭建网站视频做微商如何引流推广?怎么找客源?
  • 网站建设汇卓摄影网站建设策划书