
🔥个人主页 :Quitecoder
🔥专栏:linux笔记仓

目录
01.软硬连接
我们先看一下它的用法

这个是软连接

这个是硬连接
软连接是一个独立的文件,因为有独立的inode号
硬连接不是一个独立的文件,因为它没有的独立的inode号,用的是目标文件的inode
属性中有一列为硬连接数
软链接的内容:目标文件所对应的路径字符串,类似与Windows中的快捷方式
软连接可以跨越不同的文件系统、分区和磁盘创建,非常适合用于指向不同位置的文件或目录,尤其是当需要方便快捷地访问文件时
在bin里面的a目录下有我们的可执行文件,我们就可以使用软连接快捷访问
硬连接是指向文件数据块的指针。它使得多个文件名指向同一个 inode。在 Linux 文件系统中,每个文件都有一个唯一的 inode,inode 存储了文件的元数据(如文件的权限、大小、数据块的位置等),而硬连接通过创建多个文件名来共享同一个 inode。这样,硬连接是多个文件名指向同一数据块的一种机制
- 共享 inode:硬连接创建后,原始文件和硬连接文件都共享同一个 inode 编号,因此它们都指向相同的数据块。
- 相同权限 :硬连接与原文件共享相同的权限、所有者和组信息。
- 不可跨文件系统 :硬连接只能在同一个文件系统内创建,不能跨越不同的分区或磁盘。
- 删除一个硬连接不影响其他硬连接 :当删除一个硬连接(即删除一个文件名)时,实际的文件数据(inode 和数据块)并没有被删除,只有当 所有硬连接 都被删除时,文件的数据才会从磁盘上彻底删除。
硬连接适用于需要多个文件名指向同一文件内容的场景。最常见的应用是 备份 或 多个文件引用相同内容 的情况。
特性 | 硬连接 | 软连接 |
---|---|---|
inode 号 | 同一个 inode | 不同的 inode |
数据存储位置 | 指向相同的数据块 | 存储原文件路径(不直接存储数据) |
能否跨文件系统 | 不能跨文件系统 | 可以跨文件系统 |
目录链接 | 不能指向目录(除 root 外) | 可以指向目录 |
链接失效 | 不会失效,删除一个连接不影响其他链接 | 如果原文件被删除,软链接失效 |
创建命令 | ln |
ln -s |
查看 | ls -li |
ls -l (显示目标路径) |
定位一个文件的两种方式:找到路径,或者直接找到目标文件的inode,最终还是要通过inode number的

任何一个目录里面都有.
和 ..
,.
就相当于conf目录的重命名,有两个文件指向1179677,所以硬连接数为2
..
相当于上一级目录的重命名
任何一个目录,刚开始新建的时候,引用计数一定是2,目录A内部新建一个目录,会让A目录的引用计数自动+1
linux中不允许给目录建立硬连接,避免形成路径环绕
02.动静态库
我们用过很多c/c++的标准库,strerror,strstr,STL等,必须要有具体的实现,实现在库当中
ldd可以帮我们查看可执行程序执行时所连接的库

这个软连接对应的就是c语言的标准库
在 C/C++ 编程中,库 (Library)是指一组函数、类、对象等的集合,用于提供特定功能的代码模块。库的使用可以使得开发者避免重复实现相同的功能,提升代码的复用性。库 通常分为 静态库(Static Library) 和 动态库(Dynamic Library),它们在编译和链接阶段的行为不同。
静态库(Static Library)
静态库是一个包含预编译代码的文件,在编译时与程序的源代码一起进行链接。静态库文件的扩展名通常是 .a
(Linux/Unix)或 .lib
(Windows)。
工作原理:
- 链接时绑定 :在编译程序时,静态库的代码会被直接复制到最终的可执行文件中。换句话说,编译器会在链接过程中将静态库的代码和应用程序的代码"静态"地链接成一个最终的可执行文件。
- 不需要运行时依赖 :程序执行时,不需要依赖静态库文件,因为它们已经被包含在可执行文件中。
前面我们自己封装了一个mystdio.h文件
这里拷贝到另一个目录下的代码是两个头文件和两个.o目标文件,不需要完全提供实现。
我们这里可以创建一个静态库:
bash
[dyx@VM-8-13-centos Stdio]$ ll
total 24
-rw-rw-r-- 1 dyx dyx 107 Mar 5 16:38 mymath.c
-rw-rw-r-- 1 dyx dyx 63 Mar 5 16:38 mymath.h
-rw-rw-r-- 1 dyx dyx 1344 Mar 5 16:59 mymath.o
-rw-rw-r-- 1 dyx dyx 1316 Mar 5 16:36 mystdio.c
-rw-rw-r-- 1 dyx dyx 622 Mar 5 16:36 mystdio.h
-rw-rw-r-- 1 dyx dyx 2792 Mar 5 16:59 mystdio.o
[dyx@VM-8-13-centos Stdio]$ ar -rc libmyc.a *.o
使用 ar 命令将目标文件打包为静态库文件
ar是gnu归档工具,rc表示(replace and create)
所谓的库文件,本质就是.o文件打包
查看静态库中的目录列表:
bash
[dyx@VM-8-13-centos Stdio]$ ar -tv libmyc.a
rw-rw-r-- 1001/1001 1344 Mar 5 16:59 2025 mymath.o
rw-rw-r-- 1001/1001 2792 Mar 5 16:59 2025 mystdio.o
bash
[root@localhost linux]# gcc main.c -L. -lmymath
-L 指定库路径
-l 指定库名
测试目标文件生成后,静态库删掉,程序照样可以运行
头文件是一个手册,提供函数的声明,告诉用户该怎么用,.o提供实现,我们只需要补上一个main函数,调用头文件提供的方法,然后和.o进行连接,就能形成可执行文件
c
[dyx@VM-8-13-centos Stdio]$ mkdir mylib
[dyx@VM-8-13-centos Stdio]$ cd mylib/
[dyx@VM-8-13-centos mylib]$ mkdir include
[dyx@VM-8-13-centos mylib]$ mkdir lib
[dyx@VM-8-13-centos mylib]$ ll
total 8
drwxrwxr-x 2 dyx dyx 4096 Mar 5 19:12 include
drwxrwxr-x 2 dyx dyx 4096 Mar 5 19:12 lib
[dyx@VM-8-13-centos mylib]$ cp ../*.h include/
[dyx@VM-8-13-centos mylib]$ cp ../*.a lib/
[dyx@VM-8-13-centos mylib]$
这里创建两个目录,把所有.h文件放到include目录里,把.a文件放到lib目录里
bash
[dyx@VM-8-13-centos other]$ sudo cp ../Stdio/mylib/include/*.h /usr/include/
[dyx@VM-8-13-centos other]$ sudo cp ../Stdio/mylib/lib/*.a /lib64/
头文件 存放在 /usr/include/ 目录中,提供给编译器引用,以便在源代码中使用库的功能。
静态库 存放在 /lib64/ 目录中,提供给链接器使用,将库的预编译代码嵌入到最终的可执行文件中
这里还是无法完成编译,是因为这里我们第三方提供的库gcc/g++不认识,这里需要-l选项

-l指定库名,但是我们标准库就只留下myc了
bash
[dyx@VM-8-13-centos other]$ sudo rm /usr/include/mystdio.h
[sudo] password for dyx:
[dyx@VM-8-13-centos other]$ sudo rm /usr/include/mymath.h
[dyx@VM-8-13-centos other]$ sudo rm /lib64/libmyc.a
[dyx@VM-8-13-centos other]$ gcc main.c -lmyc
main.c:3:20: fatal error: mystdio.h: No such file or directory
#include"mystdio.h"
^
compilation terminated.
我们卸载库后就没办法编译了
bash
[dyx@VM-8-13-centos other]$ gcc main.c -I ./mylib/include/ -L ./mylib/lib/ -lmyc
[dyx@VM-8-13-centos other]$ ll
total 20
-rwxrwxr-x 1 dyx dyx 9032 Mar 5 20:27 a.out
-rw-rw-r-- 1 dyx dyx 311 Mar 5 18:56 main.c
drwxrwxr-x 4 dyx dyx 4096 Mar 5 20:26 mylib
[dyx@VM-8-13-centos other]$ ./a.out
a+b=30

如果不想把库安装到系统里,就需要向上面这种方法进行使用
动态库(Dynamic Library)
动态库是一个在程序运行时加载的库文件,通常有 .so
(Linux/Unix)或 .dll
(Windows)扩展名。与静态库不同,动态库的代码不会在编译时直接复制到可执行文件中,而是由操作系统在程序运行时动态加载。
工作原理:
-
运行时加载:动态库的代码在程序运行时加载,并且在程序执行期间被共享。不同程序可以共享同一个动态库实例,因此它们不会重复加载相同的库代码。
-
动态链接 :程序编译时会记录动态库的路径及库文件名称,程序运行时由操作系统通过动态链接器(如 Linux 上的
ld.so
)加载动态库。 -
创建动态库:
bash
[dyx@VM-8-13-centos Stdio]$ gcc -fPIC -c mymath.c mystdio.c
[dyx@VM-8-13-centos Stdio]$ ll
total 40
-rw-rw-r-- 1 dyx dyx 4404 Mar 5 19:05 libmyc.a
-rw-rw-r-- 1 dyx dyx 311 Mar 5 19:00 main.c
drwxrwxr-x 4 dyx dyx 4096 Mar 5 19:12 mylib
-rw-rw-r-- 1 dyx dyx 107 Mar 5 16:38 mymath.c
-rw-rw-r-- 1 dyx dyx 63 Mar 5 16:38 mymath.h
-rw-rw-r-- 1 dyx dyx 1344 Mar 6 09:10 mymath.o
-rw-rw-r-- 1 dyx dyx 1316 Mar 5 16:36 mystdio.c
-rw-rw-r-- 1 dyx dyx 622 Mar 5 16:36 mystdio.h
-rw-rw-r-- 1 dyx dyx 2848 Mar 6 09:10 mystdio.o
[dyx@VM-8-13-centos Stdio]$ gcc -shared -o libmyc.so *.o
[dyx@VM-8-13-centos Stdio]$ ll
total 52
-rw-rw-r-- 1 dyx dyx 4404 Mar 5 19:05 libmyc.a
-rwxrwxr-x 1 dyx dyx 8544 Mar 6 09:11 libmyc.so
-fPIC
参数生成位置无关的代码,-shared
参数指定生成共享库。
这里链接跟上面一样,指定执行第三方库的名称,但是需要去掉前缀和后缀,那么这里动静态库名相同,这里使用了动态库
程序运行时找不到动态库,这里选择路径只是让编译器知道头文件和库的位置,操作系统不知道,程序运行的时候,要找到动态库并加载运行!
静态库没有这个问题,是因为编译期间,已经将库中的代码拷贝到我们的可执行程序内部了,后续加载和库就没有关系了
bash
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH # 在当前目录下查找库

这种导入环境变量,但是环境变量是内存极的,可以修改.bashrc配置文件,让环境变量永久生效
这样每一次重新登录时库都会被找到,除非库非常重要,要不然没有必要这样
bash
[dyx@VM-8-13-centos ~]$ ls /etc/ld.so.conf.d/
bind-export-x86_64.conf dyninst-x86_64.conf kernel-3.10.0-1160.108.1.el7.x86_64.conf mariadb-x86_64.conf
还可以新增动态库搜索的配置文件,ldconfig
在新文件中加入库的路径即可
执行ldconfig让其生效

gcc 在不使用static选项的时候,默认使用动态库,如果你没有使用static,并且只提供.a,那只能静态连接当前的.a库,其他的库正常动态连接
-static必须强制的将我们的程序静态连接,这就要求我们连接的任何库都必须提供对应的静态库版本
** 静态库与动态库的对比**
特性 | 静态库(Static Library) | 动态库(Dynamic Library) |
---|---|---|
文件扩展名 | .a (Linux/Unix)或 .lib (Windows) |
.so (Linux/Unix)或 .dll (Windows) |
链接方式 | 编译时链接到可执行文件 | 运行时动态加载,程序运行时链接 |
生成的可执行文件大小 | 较大,因为库代码被复制到可执行文件中 | 较小,因为库代码不包含在可执行文件中 |
内存使用 | 每个程序都需要加载一份库的副本 | 不同程序可以共享同一个库的内存实例 |
更新库时 | 需要重新编译程序 | 只需更新动态库文件,无需重新编译程序 |
运行时依赖 | 不依赖外部库文件 | 需要依赖外部的动态库文件 |
兼容性 | 只要库接口不变,兼容性较好 | 可能存在库版本兼容性问题(例如,库的符号变化) |
03.动态库加载
动态库的加载和可执行程序以及地址空间的关系是操作系统内存管理和程序执行的一个重要部分

当一个程序启动时,操作系统会为该程序分配一个 地址空间,即程序运行时使用的内存区域。这个地址空间通常包括几个主要部分:
- 代码段(Text Segment):存放程序的机器代码。
- 数据段(Data Segment):存放全局变量、静态变量等程序数据。
- 堆(Heap):用于动态分配内存。
- 栈(Stack):用于存储局部变量、函数调用等。
- 共享库段(Shared Library Segment) :存放程序依赖的共享库。
程序在运行时,共享库段 是动态加载的。这个段是由操作系统的 动态链接器 来管理和加载的。
库加载到内存中也要通过页表映射到共享区,这个动态库不是只给一个进程使用,如果有另一个进程也需要使用这个库文件,就不需要再将库加载到内存中了,所以动态库也叫做共享库
动态库的加载流程
-
程序启动:
- 当程序启动时,操作系统首先加载可执行文件,并为其分配地址空间。
- 如果程序依赖于 动态库 (如
.so
文件),操作系统会在运行时通过动态链接器(通常是ld-linux.so
)来加载这些共享库。
-
加载器(Dynamic Linker)工作:
- 动态链接器负责将 动态库 的 符号(函数名、变量等)解析到程序的地址空间中。
- 动态链接器会检查可执行文件中记录的 库的路径 (通常通过 ELF 格式中的动态段
DT_NEEDED
来查找),并加载需要的共享库。 - 如果库是共享库(如
.so
文件),操作系统会将它加载到程序的地址空间中。如果这些库已经被加载到内存中(例如,其他程序也在使用相同的库),操作系统会共享 这些库的内存空间,从而节省内存。
-
重定位(Relocation):
- 在加载库时,动态链接器会 重定位 这些库的符号地址。由于库的加载地址可能并不是固定的,程序在编译时无法确定这些符号的实际地址,动态链接器会在程序运行时解析符号并将它们绑定到合适的内存地址。
- 这个过程可能涉及 符号表 和 重定位表,这些表包含了程序中所有外部符号(如函数和变量),它们需要在程序执行时解析。
-
共享库的映射:
- 操作系统通过 内存映射 (
mmap
)将动态库加载到程序的地址空间中。在加载时,共享库 可以被 映射到相同的内存地址 ,从而 多个进程共享 同一份库代码,避免重复加载,节省内存。
- 操作系统通过 内存映射 (
-
执行程序:
- 一旦所有的库加载完毕,程序可以开始执行,所有对库函数的调用都会通过已经解析的符号地址来执行。
我们的可执行程序,编译成功,没有加载到内存,没有加载运行,二进制代码有"地址"吗?
bash
[dyx@VM-8-13-centos code]$ cat code.c
#include<stdio.h>
int Sum(int top)
{
int i=1;
int ret=0;
for(;i<=top;i++)
{
ret+=i;
}
return ret;
}
int main()
{
int top=100;
int result = Sum(top);
printf("result: %d\n",result);
return 0;
}
[dyx@VM-8-13-centos code]$ gcc code.c
[dyx@VM-8-13-centos code]$ ll
total 16
-rwxrwxr-x 1 dyx dyx 8384 Mar 7 12:40 a.out
-rw-rw-r-- 1 dyx dyx 244 Mar 7 12:40 code.c
[dyx@VM-8-13-centos code]$

将其结果反汇编到code.s中

发现它是有地址的,ELF 是一种标准化的文件格式,它定义了如何存储程序的代码、数据、符号信息和调试信息等内容。它使得程序可以被有效地加载到内存中执行
ELF格式的可执行程序,二进制是有自己的固定格式的,elf可执行程序的头部,有可执行程序的属性,
可执行程序编译之后,会变成很多汇编语句,每条汇编语句都有它的地址
如何编址的呢?这里是从00000000000 ~ FFFF...FFFFFF 平坦模式,所有的内存地址都属于同一个连续的地址空间,并且 没有明显的分段。程序的所有数据和代码都可以直接访问这块地址空间中的任意位置
ELF+加载器可以让我们找到这个文件各个区域的起始和结束地址,还有main函数的入口地址
先创建PCB,再加载到内存中
mm_struct结构体对象,有code_start,code_end,等的成员变量,初始值从可执行程序来
这里通过可执行程序的头部区地址填充结构体的变量
进程创建阶段,初始化地址空间,让cpu知道main函数入口地址,代码加载到内存中,每一行代码和数据就有了物理地址,自己的虚拟地址自己也知道,就可以构建映射了

库只要加载到内存,就有了物理地址了,要用它,就得把它映射到地址空间
操作系统需要在当前地址空间堆栈之间申请一段虚拟地址,这两段地址填充到页表中完成映射
库函数的调用,其实是在地址空间内来回跳转