首先我们要达成的共识是,Linux下的文件名本质是:文件名是存储在目录磁盘内容中的一串字符串。
即文件名==字符串
首先硬链接语法:ln 目标文件名 硬链接文件名
如下:
# ln test.txt hardLink_test
软链接语法:ln -s 目标文件名 软连接文件名
如下:
# ln -s test.txt softLink_test
一般在Linux中软连接字体为蓝色:
删除软/硬链接
①unlink 软/硬链接文件名;
②rm 软/硬链接文件;
软硬链接的本质区别是:
软链接生成的链接文件有独立的inode,而硬链接的inode与原文件相同。
软连接的本质:软连接文件有着独立的inode,软连接文件在磁盘中存储着他所指向文件的文件名
软链接类似windows上的快捷方式。
硬链接的本质:在指定路径下,一个新增文件名与inode的映射关系。
换句话说,硬链接就是一个文件的“别名”。
如下图:硬链接文件hardLink_test实际上上与源文件test.txt共用一个inode;而软连接文件sortLink_test有着单独的inode,在磁盘上有着单独的date块(软连接文件的inode中date存储的是目标文件的文件名。)
什么是硬链接数 在inode中有一个变量count用于记录与该inode有着映射关系的硬链接数。 如下图,在没有创建硬链接前,普通文件的硬链接数是1。
当创建硬链接后,硬链接数变为2
什么时候一个文件才算真正被删除呢? 当一个文件的count硬链接数变成0的时候,这个文件才算被删除。 也就是说,即使删除了文件test.txt或者hardLink_test中任意一个文件,在磁盘上存储的数据并不会消失!只有将test.txt和hardLink_test全部删除,保证硬链接数为空,该文件才会被操作系统删掉。
普通文件和目录文件初始硬链接数说明
软连接的作用,以及为什么Linux不允许普通用户为目录创建硬链接
程序在编译链接时,把库的代码复制链接到可执行文件中。程序运行的时候不再需要静态库。
程序在运行的时候才去链接动态库的的代码,多个程序共享使用动态库的代码。
gcc/g++默认使用动态链接
gcc test.c -o test//生成的可执行文件默认使用动态库
①确保系统中有静态库,若无,可安装标准C/C++静态库,如下:
# 安装 C 标准库静态版(glibc)
sudo yum install glibc-static
# 安装 C++ 标准库静态版
sudo yum install libstdc++-static
②在生成可执行文件时添加"-static"
gcc test.c -o test -static
可以观察到:在文件大小上,使用静态库生成的test_s相较于使用动态库生成的test_d大上好几倍。
以下通过自制简易静态库深入了解库的原理。
首先要明确的是,库的本质就是.o文件的集合,通过不同的打包方式分成的动态库和静态库。
从源文件到可执行文件经过了如下几个步骤:
①预处理:头文件展开,宏定义替换,去注释,条件编译;.c--->.i
②编译:将源文件编译为汇编语言;.i--->.s
③汇编:将②中汇编语言进一步翻译为二进制机器语言;.s--->.o
④链接:将③中的二进制代码与库中代码链接最后形成可执行程序。.o--->可执行文件
库的名称一般由三部分构成,其中第一和第三部分是固定的,如lib… .a或者lib… .so,中间省略号部分自定义。
首先将源文件编译为.o文件,再通过ar -rc指令打包。如下:
gcc -c test.c -o test.o
ar -rc libmymath.a my_add.o my_sub.o
2)打包库 实际通过创建不同的目录用以,分别存储库文件和所依赖的头文件。 头文件放在include目录,库文件一般放在lib目录。
假如我将上述mylib文件夹打包,供他人使用。可以通过在gcc编译时指明所依赖的头文件和库文件形成可执行文件。
$ gcc -o test test.c -I ./mylib/include/ -L ./mylib/lib/ -lmymath
说明:
需要为编译器指明需要的头文件路径:-I 后跟路径;
需要为编译器指明需要的静态库路径:-L 后跟路径;
需要为编译器指明库名:-L 后跟库名,注意:这个库名是去掉lib前缀和.a后缀的。
我们知道安装的本质其实就是,将文件拷贝到相应的目录下。
所以我们可以通过,将自制的库文件和头文件拷贝到系统目录下,如:
值得注意的是,个人自制的头文件和库一般没有经过检验,容易存储漏洞或者bug,一般不推荐使用该方法。
细节
为什么在编译C或者C++时没有指明库名称也可以编译成功? 编译器会默认识别C或者C++的标准库,所以可以不用添加库名称。但使用自制或第三方库时就必须指明库名称。 gcc在编译时,若头文件在当前路径或者系统路径下,则无需指明头文件所在的路径。
自制动态库与自制静态库大体相似,下面仅指出不同处:
1)在编译成.o文件时,需要加上-fPIC(位置无关码)
gcc -c -fPIC my_add.c -o my_add.o
2)不再使用ar -rc打包,而是使用gcc打包:加入-shared,如gcc -shared -o 库名 依赖文件
gcc -shared -o libmymath.so my_add.o my_sub.o
3)除了在编译时需要指明库,在加载可执行文件时也需要告诉OS使用的哪个库。
有以下方法告诉OS:
①添加到LD_LIBRARY_PATH变量中,如:
export LD_LIBRARY_PATH=$LDLD_LIBRARY_PATH:后跟库所在路径
export LD_LIBRARY_PATH=$LDLD_LIBRARY_PATH:
注意:上述为添加到LD_LIBRARY_PATH=$LDLD_LIBRARY_PATH变量中,而非覆盖;
由于环境变量每次登录都会重置,故该方案只能是一种临时方案。
②在路径 /etc/ld.so.conf.d/下创建一个文件,然后打开该文件,将自制库的路径添加到该文件中,保存退出后,在执行ldconfig指令。(一些步骤需要sudo)
该方案为永久方案。
③软链接到执行目录下
使用软连接将目标文件链接到可执行程序的同一个目录下。推荐使用该方案。
④软连接到系统lib64路径下
如:
sudo ln -s 目标库路径 /lib64/libmymath.so
我们知道程序的编译过程分别是:预处理、编译、汇编、链接 四个阶段,而链接时又因加载的库不同而形成的可执行文件不同(虽然他们都能运行)。
下面主要讨论动/静态库在最后一步“链接”时的加载过程。
使用静态库链接,在链接时,会将库中的相关函数的实现代码直接拷贝到可执行程序的代码区中。
(如平时调用的printf等函数,这些函数的具体实现在标准C库当中。
程序在编译时,就已经按照虚拟地址空间进行编址,只是缺少堆栈。)
当链接完成后形成的可执行文件中,库函数与自定义函数没有本质区别。当程序运行时,都可以通过页表的映射在内存中找到库函数或者自定义函数的物理地址。
在链接时,将库函数中相关函数的实现代码的地址写入到程序中。也就是说在形成的可执行文件中,使用的库函数没有相关的代码实现。
在实际执行过程中,OS除了会将可执行程序读入内存,将相关库文件也会被读取到内存中,然后将库文件的物理地址通过页表映射到共享区存储(共享区在堆栈之间)。当执行到某个库函数时,OS会到共享区中查找该库函数的地址,在通过这个地址去找到相关的实现代码。
换句话说,动态库(可能是部分动态库)是与可执行文件一起被加载进内存中。
相较于静态库的直接拷贝,动态库则灵活许多。
本文先从介绍软硬链接开始,再到介绍动/静态库的概念,然后通过自制静态库进一步介绍动静态库的区别,最后介绍了动/静态库的加载以及区别。