目录
一. 软硬链接
1.1 软硬链接的创建方法
1.2 软硬链接的特性
二. 动静态链接
2.1 动态链接和静态链接的概念
2.2 动态链接程序载入内存的方式
三. 动静态库的制作和使用
3.1 静态库的制作
3.2 静态库的使用
3.3 动态库的制作
3.4 动态库的使用
四. 总结
假设当前路径下有一个名为test.txt的文件,我们希望为它创建一个名为softlink的软连接和一个名为hardlink的硬链接,实现上述操作的指令为:
ln为创建软硬链接的指令,默认情况下为创建硬链接,如果希望创建软连接,那就需要-s选项。
语法:ln [选项] [链接源] [新建的链接文件]
unlink为取消链接的指令,用于软连接和硬链接均可,语法:unlink [链接名称]
注意:如果新建的软链接不在当前目录下,那么就需要指定被链接文件的绝对路径。
假设当前路径下有一个名为test.txt的普通文件和一个名为dir1的目录文件,希望在dir1中,创建test.txt的软连接,名为softlink1,如果直接使用指令ln -s test.txt dir1/softlink1来创建软链接,之后使用ll dir1,查看新建的软连接文件的属性信息,我们会发现,softlink1和test.txt全部变红,表示softlink找不到链接源test.txt,见图1.2。
这是因为在ln指定被链接文件test.txt时,没有给出绝对路径,从而软连接链接源softlink1既无法通过绝对路径从根目录开始找到test.txt,也无法从其所在的目录开始,通过相对路径找到test.txt。
这是就需要给出链接源test.txt的绝对路径,让软连接能够通过绝对路径找到链接源,这样就可以成功在非当前路径下创建软连接。
ls 指令有-i选项,可以显示出文件的inode码。
假设在当前路径下,有一个普通文件test.txt,还有这个文件的一个软连接softlink和一个硬链接hardlink,通过指令 ls -il,显示文件包括inode编码在内的详细属性信息,如图1.4所示。观察发现,软连接的inode码与其链接源不相同,而硬链接的inode码与其链接源相同,这样就可以得出两条结论:
如果软链接的链接源是一个可执行程序,那么,我们就可以在当前路径下,通过创建软连接,来运行在其他路径下的可执行程序。如图1.5所示,在文件dir1中有可执行程序mycmd.exe,运行结果为打印3行hello Linux,我们可以在当前路径下创建可执行程序mycmd.exe的软链接,用于运行代码。
结论:软链接相当于Windows下的(桌面)快捷方式。
硬链接没有独立的inode,因此不是一个独立的文件。观察图1.4,可以发现,硬链接不仅inode和链接源文件相同,而且权限、大小、创建修改时间等属性信息均与链接源文件相同,得出结论:硬链接就是文件的一个别名,与链接源文件具有完全相同的特性。
通过ll可以查看文件的详细属性信息,其中就包括文件的硬链接数。
如图1.7所示,在当前路径下创建普通文件test1.txt,并为其创建两个硬链接hardLink1和hardLink2,,此时ll查看文件属性信息,发现这三个文件的硬链接数都是3,rm hardLink2删除一个硬链接,并不会删除整个文件,即使他们具有相同的inode。
创建硬链接,就是在指定路径下,创建文件名和inode的映射关系。
每一个文件都有一个struct inode来存储文件的属性信息,其中struct inode中有一个引用计数器count的成员,就是用于记录当前的文件有多少硬链接(别名)。每次删除inode对应文件,引用计数器count会-1,当引用计数器变为0时,文件才会被真正删除。
如图1.8所示,清空当前路径下的所有文件,创建一个普通文件test.txt和一个目录文件dir1,ll查看属性,得出下面的结论:
目录文件的硬链接数为2,是因为其内部有一个名为 . 的私密文件夹,. 为目录文件本身,充当了一个硬链接。如果此时在新建的目录文件dir1内部创建一个新的目录文件dir2,此时dir1的硬链接数增加为3,因为dir2内有 .. 表示上级路径。
动态链接需要依赖动态库,静态链接需要依赖静态库。如果静态库丢失,那么采用静态链接的可执行程序由于库中的函数代码已经被拷贝到调用库函数的位置,因此即使静态库丢失采用静态链接的可执行程序依旧可以正常运行。但是,动态链接需要执行流跳转到库中执行对应的函数代码,因此动态库如果丢失采用动态链接的可执行程序化无法再正常运行。
无论是在Linux还是在Windows操作系统中,都可以通过后缀名来区分动静态库:
在使用gcc/g++编译代码时,如果动态库存在,默认采用动态链接,当然如果只存在静态库也会采用静态链接的方式链接。
这里还有两条指令,可以查看可执行程序的动静态链接信息:
动静态链接,有各自的优点和缺点:
动态库是一个独立的库文件,动态库与可执行程序可以分批加载到内存中。
在我之前的博文Linux系统编程:详解进程地址空间_【Shine】光芒的博客-CSDN博客中提到,每一个进程都拥有一份属于自身的地址空间(虚拟地址),通过页表映射,就可以根据虚拟地址,来找到实际运行的代码和数据在物理内存中的地址。
在运行动态链接的代码时,会先将可执行程序(除动态库以外)加载到内存中,这个进程有其自身的地址空间,并且操作系统为为其建立一张页表,将可执行程序中的代码和数据加载到物理内存中,通过页表建立虚拟地址和物理地址之间的映射关系。
上述讲到的可执行程序加载到内存完成之后,动态库才会载入到内存,动态库与可执行程序之间是分批加载的。OS会给动态库的代码和数据分配一块物理内存,并将物理内存的地址载入到页表,动态库用到的虚拟地址,是地址空间中共享区的地址。位于共享区的虚拟地址与动态库代码和数据在物理内存中的地址通过页表建立映射关系,就可以让程序的执行流跳转到动态库运行。
全局代码位于地址空间最下方的低地址处,当程序运行到调用动态库的位置时,会跳转到共享区中,找到动态库的虚拟地址,通过页表映射找到对应的物理地址执行动态库的代码,动态库代码运行完成后,在继续回去执行全局代码区的代码。
以源文件mymath.cpp和myprint.cpp,实现累加函数int addToTop(int from, int to)和字符串打印函数void Print(const char* str)来制作动静态库,源文件和头文件代码见代码3.1。
代码3.1:用于制作动静态库的文件(mymath.h、mymath.cpp、myprint.h、myprint.cpp)
// mymath.h 头文件
#pragma once
extern int addToTop(int from, int to);
// mymath.cpp 源文件
#include "mymath.h"
extern int addToTop(int from, int to)
{
int ans = 0;
for(int i = from; i <= to; ++i)
{
ans += i;
}
return ans;
}
// myprint.h 头文件
#pragma once
#include
extern void Print(const char* str);
// myprint.cpp 源文件
#include "myprint.h"
extern void Print(const char* str)
{
std::cout << str << std::endl;
}
静态库的制作步骤如下:
Step1:生成每个源文件的二进制(.o)目标文件 -- 如果直接给客户提供.o文件,也可以被使用,但是需要在编译时显示指定每个.o文件,用起来不便利。
Step2:对所有模板文件.o打包(ar指令)
Step3:发布库,要将头文件.h和目标二进制文件归档生成的静态库libXXX.a进行发布。一般会为待发布的静态库创建一个文件夹,这里暂时取名为Output,其内部创建两个子文件夹,分别取名为include和lib,其中include内部存放头文件,lib内部存储libXXX.a文件。
有两种方法,可以使用静态库:(1)将头文件*.h和库文件*.a复制默认的搜索路径下。(2)在编译程序时显示给出头文件和库.a文件的搜索路径。
方法1:头文件*.h和库文件*.a复制到默认的搜索路径下
注意:向/usr/include、/li64、/usr/lib64下 复制文件,需要管理员权限,如果普通用户要执行这样的操作,需要使用sudo指令来提权。
如图3.3所示,完成复制后,运行可执行程序./test.exe,会报错没有找到相应的函数,这是因为用户建立的库并不是系统库,需要显示的指出库的名称,-l选项,用于指定动静态库的名称。
语法:g++ ... -lName,-l后面直接跟库的名称,没有空格,且需要去除前缀和后置,如名称为libXXX.a的静态库,只需要声明为lXXX即可,前缀lib和后置.a均用略去。
注意:一般情况下,对系统路径下的内容进行修改,不是一个明智的选择。这里将刚才复制到/usr/include和/lib64的文件删除。使用显示给定*.h和*.a文件路径的方法使用静态库,会是一个更好的选择。
方法2:在编译程序时显示给出头文件和库.a文件的搜索路径
gcc/g++编译文件时,使用-I/-L选项即可指定头文件和库的搜索路径:
光指定头文件和库文件的搜索路径还不够,还需要指定库名称。
动态库的制作步骤如下:
Step1:生成与位置无关的二进制目标文件,语法:g++ -fPIC -c [源文件名] -o [目标文件名称]
Step2:将所有二进制目标文件打包为动态库,语法:g++ -shared [目标文件名] -o [动态库名称]
这里认识了g++/gcc的两个新选项:-fPIC和-shared。
除了将库文件*.so和头文件*.a复制到默认搜索路径下,还有4种方法可以使用动态库:
方法1:更改环境变化LD_LIBRARY_PATH的值
环境变量LD_LIBRARY_PATH的为系统查找动态库的默认搜索路径,可以通过export指令,将动态库.so的路径添加到环境变量LD_LIBRARY_PATH中,运行可执行程序./test.exe,运行成功。
注意:即使更改了LD_LIBRARY_PATH的值,也需要使用-I和-L显示告诉gcc/g++头文件和库的路径。因为-I/-L是告知编译器相关路径的,LD_LIBRARY_PATH才是作用与操作系统的,可执行程序运行起来后,与编译器不再有关。
但是,这种方法退出当前账号再次登录时,LD_LIBRARY_PATH修改后的值不会保持。
方法2:新增配置文件*.conf
在路径/etc/ld.so.conf.d/路径下使用管理员权限sudo创建.conf为后缀的文件,然后将库的路径作为内容写入到新建的.conf文件中。最后,还需要使用管理员权限执行指令ldconfig,来让配置文件生效,这样才能成功运行可执行程序。
删除在/etc/ld.so.conf.d/路径下新建的*.conf配置文件,同样需要管理员权限,删除文件之后,使用ldconfig指令让配置文件生效之后,./test.exe便不再能够成功运行。
方法3:在/lib64下建立软连接
这里必须使用绝对路径来创建软连接,这样就相当于在库的默认搜索路径下有了一个实际库的快捷方式,可以直接访问到库。注意:软连接的名称要与库本身的名称相同。
方法4:修改系统的默认配置文件
在用户的家目录~下有两个文件:.bashrc和.bash_profile,更改这两个文件的内容,可以改变登录时环境变量LD_LIBRARY_PATH的默认值。