一,硬链接
1-1,硬链接的认识
Linux下的硬链接本质是在指定的目录下,插入新的文件名和目标文件的眏射关系,并让inode的引用计数加一,这里我们通过 ls 指令可查看到文件详细信息种的硬链接数。这里可参考文件系统的文章:文件系统
Linux使用 ln [文件名] [硬链接名] 可创建对应文件的硬链接,具体如下图:
上面现象可发现,硬链接的inode编号与对应文件的inode编号相同,而若inode编号一样,说明文件相同,若是一个独立的inode,那么inode编号将不会与其它文件相同。
在文件系统的文章中所提到过这里inode对应的文件如同硬链接,删除文件时除非该文件的硬链接数是1时才能删除该文件的inode。也就是说硬链接如同文件重命名般,所有对应的操作都是在对应一个文件进行操作。若下图:
建立硬链接其实是在系统中什么都没有做,只是在对应的目录下新建一个和指定文件之间新的文件名来做眏射关系,也就是从该目录下的一对文件名和inode的眏射关系变成了两对或多对,即硬链接数从一开始不断加一,对应文件inode结构中的引用计数加一,这就如同一个inode对应多个文件名的情况。
1-2,目录的硬链接
文件的硬链接我们已明白,下面我们来说明有关目录的硬链接。
当我们创一个目录时,其硬链接数默认是2。目录自身的文件名与inode是一种眏射关系,但目录默认里面存在 ‘.’ 和 ‘..’ ,一个 ‘.’ 也是文件名(表示当前目录),对应的也是当前目录下的inode,即一个inode对应两个文件名。若是在当前新建目录中再新建一个目录,由于新建目录中存在 ‘..’ 目录(上一级目录),而 ‘..’ 目录与 ‘.’ 目录同理,对应的都是相应目录文件下的inode,所以这时的硬链接数就会变成3。这也就是为什么运用 ‘.’ 或 ‘..’ 时,表示的都是当前目录或上一级目录的原因。我们可利用目录的硬链接数,很容易的计算出一个目录下有多少个子目录,即:硬链接数 - 2。
注意:Linux中,用户不能给目录建立硬链接。若系统允许我们建立目录的硬链接,这里可假设一下我们在某个路径下建立起前缀目录的硬链接,当系统从根目录开始解析查找一个文件时,系统找到该硬链接时,将会又跑到上层的inode,出现环路问题。比如,当前路径是/home/who/day,当我们当day目录中建立根目录的硬链接dir且在dir里面创建文件并查找时,系统往上找到根目录往下解析后,当找到dir时,又将跑根目录逐层往下查找,出现闭合环路问题。系统自己拥有的目录硬链接会看作自己的一部分,能够将此区分开做特出处理。
1-3,硬链接的应用场景
文件多人共享:由于硬链接指向的是同一个物理文件(具有相同的inode号),因此多个用户或进程可以通过不同的文件名来访问和共享同一个文件。这有助于节省存储空间和提高文件访问效率。
文件备份:通过创建硬链接,可以在不占用额外存储空间的情况下生成文件的备份副本。这是因为硬链接和原始文件实际上是同一个文件的不同入口点。
系统文件管理:一些系统文件经常需要在不同位置进行引用。通过创建硬链接,可以简化这些文件的管理和维护工作。例如,在Linux系统中,许多系统文件和目录都通过硬链接来实现多路径访问。
目录结构的维护:硬链接在维护Linux的目录结构中起着重要作用。每个目录都包含至少两个硬链接(.和..),分别表示当前目录和上级目录。这种结构使得用户可以在目录间自由切换和访问文件。
二,软链接
2-1,软链接的认识
软链接本质就是一个独立文件,软链接内容里面放的是目标文件的路径,类似Windows上的快捷方式,执行软链接相当于执行对应的程序。Linux中使用指令 ln -s [文件名] [软链接名] 可建立相应文件所对应的软链接。如下:
2-2,软链接的应用
软链接的本质如同Windows上的快捷方式。通过建立软链接,可以在任意路径下快速找到相应的目标文件,并执行相应的程序。用户可以在任何需要的地方创建软链接,以便方便地访问远程位置的文件或目录。但若删除软链接对应的文件或程序后,软链接将无法使用,软链接只是提供了一种快捷方式,程序启动的根本在于源头。
除此方便用户操作之外,软链接还在动态库版本管理方面具有重要应用。
动态库版本管理:在软件开发中,动态库(如.so文件)的版本管理是一个重要问题。通过使用软链接,可以灵活地切换不同版本的动态库,以适应不同程序或开发阶段的需求。
三,动静态库
3-1,认识动静态库
库分为动态库(.so文件)和静态库(.a文件)。大多数高级语言都提供了创建和使用库的工具或方法,以C/C++为例,我们平常写程序时都会直接或间接使用到库。C语言可执行程序链接的时候会使用C语言中的 /lib64/libc.so.6库(使用指令:ldd “可执行程序” 可观察到可执行程序所依赖的动态库,因为静态库在链接阶段会被完全复制到可执行文件中,所以静态库的信息不会出现在 ldd
的输出中。),而此库在CentOS7版本下又链接(软链接)的是 libc-2.17.so的库,这个库属于动态库。C++可执行程序链接的是 libstdc++.so.6库。这些库主要提供一些基础方法,如字符串的切割、字符串的封装和函数功能等,我们写的代码只是个文本而已,运行的时候会将代码与库中的实现代码相结合,生成最终的可执行文件,然后库还会对一些系统调用做封装,屏蔽不同操作系统下底层语言实现的差别,保证语言的跨平台性。
大部分的操作系统默认安装的是动态库,以云服务器为例,它上面的静态库(如C标准库)是没有安装的,因此,当写完一个程序开始编译处理时,默认使用的是动态库,如果要使用静态库需要加选项 -static。这里可与系统处理程序的各个步骤联系起来:系统的编译处理过程 (有关C/C++的动静态库的使用和安装以及语言的处理过程里面已讲解,这里不再说明)。
Linux64位系统下的库都在 /lib64 目录下(不同的系统平台也可能在 /lib 目录下),我们在系统上安装的库也都是在此目录下。这里要说一下,库的名称其实是 “lib(前缀)” + “库名称” + “.so(动态库)”或“.a(静态库)”后缀 组成的,而库的真实名字是中间的 “库名称” ,如C语言的库 libc.so.6 真实名是 c ,表示C标准库;C++的库 libstdc++.so.6 真实名是 stdc++ ,表示C++标准库。所谓的库其实就是类似把源代码文件形成有关的.o文件(二进制文件),由于.o文件可能有很多个,所以通常需要再用特定的方式进行打包,形成一个库文件。这样一来,它即让开发者重用库中的代码,不用多次编写,提高开发效率,又能隐藏源代码。下面我们来模拟制作静态库,并模拟将其传给用户使用。
上面,我们先使用指令 gcc -c mymath.c 默认编译形成同名的.o二进制文件,然后使用指令 ar -rc libmyc.a mymath.o mystdio.o(ar指令可将其打包形成库文件),将 mymath.o 和 mystdio.o 打包形成静态库 libmyc.a,接下来我们只需将相关头文件(.h:方法的声明)和静态库libmyc.a(方法的实现)给予对方即可。用户拿到这两种文件后,只需链接即可实现程序,但问题是用户拿到后又该如何操作呢?我们先看以下情况。
首先,若对方拿到相关头文件和库后直接编译运行,会出现链接错误,因为默认情况下编译器链接的是 /lib64/ 目录里面的标准库,要想链接自己编写的库,在编译时需加上 -l 选项,告诉编译器要使用自己链接的库,其次,默认情况下,编译器链接时用到的库会自动往 /lib64(或 /lib)目录下寻找,这里需使用 -L 选项告诉编译器 要在具体哪个路径下,若不想使用 -L 选项可把自定义的库拷贝到 /lib64(或 /lib)系统库目录下,这样编译器在编译时就会默认往此目录下去寻找,最后,使用自定义库时不能直接用库的全名,如上面 gcc -o main.exe main.c -llibmyc.a -L . 是错误的,必须使用库的真实名,如上面 gcc -o main.exe main.c -lmyc -L . 。
下面我们再来模拟制作下动态库。这里要说一下,动态库的实现跟静态库的差别不大,这里需要在编译形成的.o文件时加用选项 -fPIC(产生位置无关码。后面会解释,这里先会用),如:gcc -c -fPIC mymath.c。打包时这里不需要其它工具,这里要使用gcc指令加用选项 -shared(表示生成共享库格式)和 -o选项 ,其中选项 -shared 要在选项 -o 前面,如:gcc -shared -o libmyc.so mymath.o mystdio.o。
由于指令的繁琐,这里可使用makefile文件。makefile文件和make的使用以往已说明,基础的用法这里不再说明,若不明白的可观看此文章:makefile和make的使用
这里说明一下makefile文件中 $^ 与 $< 的区别,两者虽都表示依赖文件列表,但它们所对应的指令操作却有所不同。 Makefile文件中 $^表示依赖文件列表的全部内容,它会一次性直接将其拿过来使用指令进行操作,$< 表示的是把上面的依赖文件一个一个的拿过来依次进行指令操作。如:上面 gcc -c -fPIC $< 表示依次将依赖文件列表中的 .c文件编译成 .o文件,gcc -shared -o $@ $^ 表示一次性拿到 mymath.o 和mystdio.o 生成共享库,即:gcc -shared -o $@ mymath.o mystdio.o。
动态库生成后,这里还不能直接使用。首先,它与静态库类似,需要使用 -l 选项说明要链接自定义的库,若是不把自定义库放入系统默认的搜索路径/lib64/下,还需使用 -L选项说明库的路径。其次,当编译源程序时,对于头文件,编译器会默认在两套路径下搜索,一个是当前目录,一个是系统默认的头文件目录(/usr/include/,此目录存放的是系统下对应的所有头文件)下,若源代码所包含的头文件都不在这两个目录下,那么程序编译时将会报错。这里可以使用选项 -I ,让编译器在查找源程序所包含的头文件时,除了会在两个默认的目录下查找外,还会在指定的路径下进行查找。如:gcc -o mytest main.c -I ./mylib/include -L ./mylib/lib -lmyc,编译时系统会搜索目录./mylib/include寻找头文件和搜索目录./mylib/lib -lmyc寻找库。同理,若不想使用 -L 选项和 -I选项,只需把对应的库文件和对应包含的头文件放入到系统默认搜索的目录中即可,需注意的是这里要使用root权限,不是root用户需使用sudo,如:sudo cp mylib/include/* /usr/include/ sudo cp ./mylib/lib/* /lib64/。之后,我们只需加一个 -l 选项告诉编译器还要使用自定义的库即可,如:gcc -o mytest main.c -lmyc。
使用 ldd [可执行程序] 可查看程序所依赖的动态库,通过查看后发现程序成功使用。
上面我们还看出,程序不仅依赖于自定义库libmyc.so,还依赖于C标准库libc.so.6。其实把所谓的库(或其它软件)安装到系统中,本质就是把对应的文件拷贝到系统默认指定的路径中,如:安装gcc软件时,C标准库自动安装在/lib64/目录下,相关的头文件安装在/usr/include/目录下。
下面要说明的是,当可执行程序生成时,它还不能正常运行,但模拟实现的静态库不会出现这种问题。因为静态库的本质是把库中的代码拷贝到使用的程序中,也就是说编译形成的可执行程序中存在有关静态库中的代码,只要形成了可执行程序后就与静态库无关了,而链接动态库的程序运行时,操作系统必须动态加载相关的库文件,如果系统找不到所需的动态库,程序可能会无法运行或运行出错。
注意,源代码的编译和编译后形成的可执行程序都需要相关动态库的加载。我们编译时链接的动态库只是相关工具的链接,如上面的演示中,在系统默认的路径下没有对应的动态库时,使用gcc及相关选项进行链接只是编译时工具gcc的链接,形成可执行程序后要想运行,必须还要操作系统层面的动态加载。解决系统层面的加载这里有四种方案:
1,直接暴力把自定义的动态库拷贝到 /lib64目录下。当运行程序时,操作系统默认动态加载 /lib64目录下相关的动态库。
2,