【Linux】基础IO --- 软硬链接、acm时间、动静态库制作、动静态链接、动静态库加载原理…

我用执着烧死了所有的幼稚和任性,那片荒野慢慢长出了理智冷漠和清醒。

在这里插入图片描述

文章目录



一、软硬链接

linux文件类型 说明
b 块设备文件一般指硬盘、软盘等存储设备。
c 字符设备,是一些串行端口的接口设备,例如键盘、鼠标、打印机、tty终端。
d 目录文件,类似于Windows的文件夹。
l 链接文件,类似于Windows的快捷方式。
s 套接字文件(socket),主要用于通信,特别是在网络上。
p 管道文件(pipe),主要用于进程间通信
- 文件,分纯文本文件(ASCII)和二进制文件(binary)。

1.软硬链接的区别(是否具有独立的inode)

1.下面分别是软链接和硬链接的作用结果。

[wyn@VM-8-2-centos lesson21]$ ln -s myfile.txt soft_file.link ---软链接

在这里插入图片描述

[wyn@VM-8-2-centos lesson21]$ ln myfile.txt hard_file.link ---硬链接

在这里插入图片描述
在这里插入图片描述
2.
软链接文件soft_file.link有自己独立的inode,可以被当作独立文件看待
而硬链接文件没有自己独立的inode,无论改变myfile.txt什么内容,hard_file.link都会随着一起改变,所以建立硬链接,实际上根本没有创建新文件,因为没有给硬链接分配独立的inode

3.
既然硬链接没有创建新文件,那么硬链接一定没有属于自己的属性集合和数据集合,用的也一定是其他文件的inode和数据块,所以硬链接本质就是在指定路径下,增加了文件名和inode的映射关系,inode被多个文件所指向

在这里插入图片描述

4.
所以791188显示的引用计数(也叫硬链接数)就是2,因为有两个文件指向791188这个inode。

在这里插入图片描述

在这里插入图片描述

5.
删除myfile.txt文件后,自然hard_file.link文件的硬链接数就变为1,所以当一个文件的硬链接数变为0的时候,这个文件就真正的被删除了,例如myfile.txt的硬链接数已经变为0

在这里插入图片描述

2.软硬链接的作用

2.1 软链接作用(建立快捷方式)

1.
删除软链接的目标文件myfile.txt后,软链接实际上还是存在的,因为它的inode还在,但是当cat打印软链接文件时却显示文件不存在。所以软链接soft_file.link没有用目标文件的inode来标识目标文件,因为源文件的inode实际上还存在,hard_file.link硬链接用的不正就是源文件的inode吗?由此可见软链接标识源文件用的是源文件名

2.
软链接的数据块保存的是它所指向的目标文件的路径,所以源文件一删,软链接直接就失效了,因为它找不到目标文件了。

在这里插入图片描述

3.
删除软链接并不影响源文件,所以软链接相当于windows下的快捷方式。
edge属性中有一个目标,这个目标实际上就相当于软链接中指向的目标文件,我们能够双击快捷方式打开Microsoft Edge的原因就是,目标文件实际上就是Microsoft Edge的可执行程序
如果每次运行一个程序,我们都要找这个程序的下载位置在哪个盘的具体哪个路径,然后再双击这个可执行程序,以便把它运行起来,这样可能所有的使用者都疯掉了,太难用,太恶心了简直,所以就出现了快捷方式这种东西,和linux的软链接很相似

[wyn@VM-8-2-centos lesson21]$ unlink soft_file.link ---删除软链接文件

在这里插入图片描述

在这里插入图片描述

4.
下面图片展示了软链接的作用,即将一个很深目录下的可执行程序在指定的某个目录下建立软链接,然后就可以在指定目录下快速的运行这个可执行程序了。

在这里插入图片描述

2.2 硬链接作用(防止误删重要文件,路径的快速查找和切换(. 和. .))

1.
硬链接表面看来,像是对源文件作了一个重命名,就和源文件的分身一样,实际硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。
假设myfile.txt是一个很重要的文件,我们将myfile.txt建立硬链接形成了hard_file.txt文件,所以就算我们误删了myfile.txt文件,也不用担心,因为hard_file.txt中的数据和myfile.txt一模一样,相当于作了一个重要文件的数据备份。

在这里插入图片描述

2.
file.txt文件只有本身它自己指向自己的inode,所以它的硬链接数是1,empty目录文件不仅自己指向自己的inode,目录下的隐藏文件.也指向empty目录文件的inode,所以empty的硬链接数是2,因为有两个文件指向empty的inode。
如果在empty里面再创建一个目录,则empty的硬链接数会变成3,因为dir中的隐藏文件…也指向empty的inode
所以empty目录下的隐藏文件 . 和empty目录下的dir目录中的隐藏文件. . 其实就相当于empty目录文件的硬链接。

在这里插入图片描述
在这里插入图片描述

3.
补充知识:目录文件的大小都是4096字节的倍数,最小是4096,因为4096是IO块,磁盘读写的最小单位是8个扇区,正好是4096字节的大小。操作系统知道你肯定会在目录文件下面读写文件,所以直接给你开一个IO块大小的空间

下面文章中有一些关于单位的错误,需要大家辩证的看待。
Linux下为什么目录的大小总是4096(转载自博客园博主猿少有为的文章)

4.
从下面可以看到,Linux不允许普通用户给目录建立硬链接,但Linux自己可以给目录建立硬链接(隐藏文件),只许州官放火,不许百姓点灯。

linux为什么不能硬链接目录?(转载自知乎博主醉卧沙场博主的文章,讲解的非常非常非常详细

为什么一般情况下ln不能硬链接目录 (转载自博客园博主Kproxy的文章)

在这里插入图片描述
5.
但软链接目录是被允许的,如果要取消软链接目录,可以利用unlink,但需注意的是,文件末尾不能加/,因为软链接文件不是一个目录而是一个文件

在这里插入图片描述
在这里插入图片描述

分享一篇写的不错的文章,感兴趣伙伴的可以看看。
Linux软连接和硬链接(转载自知乎博主Heropoo后端攻城狮的文章)

二、stat命令下的acm时间

在这里插入图片描述
1.
Access是指文件被访问的时间
Change是指文件属性被修改的时间
Modify是指文件内容被修改的时间

2.
如果修改文件内容,文件属性中文件大小也会随之改变,所以我们经常会看到,修改文件内容后,Change会随着Modify时间一起被修改

3.
早些年的linux操作系统中,只要你访问了文件,文件的Access时间就会被立马更改,但等到后面发现,文件操作中更改属性和内容的比率远远大于访问文件的操作,所以如果每次都要刷新文件访问的时间,那么频繁的IO数据是会影响操作系统的效率的,因为磁盘是外设。
所以linux更改了原来的策略,比如访问次数达到某一个固定的数时,linux才会统一刷新文件的访问时间,所以Access时间不是实时更新的

三、动静态库的区别(链接阶段,链接结果,链接方式上的区别)

1.
静态库以.a为后缀,程序在编译链接阶段,将库的代码链接到可执行文件当中。等到程序运行起来加载到内存变为进程的时候,将不再需要静态库。
动态库以.so为后缀,程序运行加载到内存变为进程的时候,才会去链接动态库的代码,如果内存中有多个进程都需要动态库,则多个进程共享使用动态库的代码。

2.
采用动态链接的可执行文件仅仅包含它所用到的函数的入口地址的一个表,并非包含库文件中外部函数的整个机器码。

3.
在可执行文件加载到内存变为进程后,外部函数的机器码会被操作系统从磁盘上的库文件复制到内存中,这个过程就是动态链接(dynamic linking)。

4.
动态库可以在多个进程间共享,所以动态链接的可执行文件体积更小,节省磁盘空间。操作系统采用的虚拟内存机制允许物理内存中的一份动态库被多个进程共享,节省内存空间。

四、库的本质是什么?(.o文件的集合)

下面写了一些简单的代码,可以帮助我们了解什么是库,和库的作用。
在这里插入图片描述

在这里插入图片描述
1.
两种方式生成可执行程序mymath实际上是一样的,一个将编译链接过程整合到一起,一个将编译链接过程分开,先将每个源文件编译生成可重定位目标二进制文件,然后再将多个.o文件链接起来,也就是符号表的合并,链接的方式可细分为动态链接和静态链接。

1.gcc -o mymath main.c my_sub.c my_add.c

2.gcc -c main.c 
gcc -c my_sub.c
gcc -c my_add.c
gcc -o mymath main.o my_sub.o my_add.o

2.
如果我们不想给对方源代码,可以给对方提供.o可重定位目标二进制文件和.h头文件,让对方直接进行链接工作,这样的方式也可以生成可执行程序。
左侧相当于用库的人,右侧相当于写库的人。
给对方提供.o(方法的实现)和.h(都有哪些方法)的文件集合,这样的思想就是库的思想。

在这里插入图片描述

3.
一旦需要编译的源文件过多,为了方便使用,可以将所有的.o文件打一个包,而包含一堆.o文件的这个包,实际就是库文件,而根据打包工具和打包方式的不同又可以划分为动态库和静态库,库的本质就是.o文件的集合

五、静态库和静态链接(ar指令,将.o文件进行归档)

1.制作静态库(打包压缩.h文件和.o文件,形成头文件和库文件集合)

在这里插入图片描述
tar命令详解,打包、压缩、解包
1.
ar命令实际就是archive单词归档的前两个字母,静态库就是归档文件。
rc代表replace and create,如果被打包文件不存在则创建文件,如果存在就将文件替换到归档文件中。
生成的libmymath.a文件此时就是一个归档文件。

在这里插入图片描述

  1 libmymath.a:my_add.o my_sub.o
  2     ar -rc $@ $^    ---生成静态库libmymath.a
  3 my_add.o:my_add.c
  4     gcc -c my_add.c
  5 my_sub.o:my_sub.c
  6     gcc -c my_sub.c
  7 
  8 .PHONY:clean
  9 clean:
 10     rm -f *.o libmymath.a      

在这里插入图片描述

2.
光有libmymath.a(我们自己写的库文件)还不够,因为C语言会给使用者提供库文件libc.a和头文件stdio.h,所以我们还缺头文件。
将库给对方的实际就是,把库文件(.a/.so)和与之匹配的头文件都给对方。

[wyn@VM-8-2-centos lesson22]$ ls /usr/include/stdio.h
/usr/include/stdio.h
[wyn@VM-8-2-centos lesson22]$ ls /lib64/libc.a
/lib64/libc.a

3.
make output执行后,就可以生成一个库,库名称叫做mylib,其中包含库文件和头文件,mylib就可以作为一个库文件来交给对方使用。
如果库文件体积较大,也可以采取压缩形成压缩包的方式交付给使用者。

  1 libmymath.a:my_add.o my_sub.o
  2     ar -rc $@ $^    ---生成静态库libmymath.a
  3 my_add.o:my_add.c
  4     gcc -c my_add.c
  5 my_sub.o:my_sub.c
  6     gcc -c my_sub.c
  7 
  8 .PHONY:output
  9 output:
 10     mkdir -p mylib/include 
 11     mkdir -p mylib/lib
 12     cp -f *.a mylib/lib 
 13     cp -f *.h mylib/include                                                                                                           
 14 .PHONY:clean
 15 clean:
 16     rm -f *.o libmymath.a

在这里插入图片描述

将mylib库压缩为mylib.tgz压缩包,交付库时,可以将这个压缩包交给对方。

[wyn@VM-8-2-centos lesson22]$ tar -czvf mylib.tgz mylib

在这里插入图片描述

删除多个文件时,我们可以利用通配符 * 来进行高效率的删除。

在这里插入图片描述

左边的使用库的人将压缩包解压后,便可以得到库mylib。

在这里插入图片描述

4.
如果使用库的人想要进行库的安装,则只需要将对应的文件拷贝到系统目录下即可,所以,安装的本质就是拷贝。

[wyn@VM-8-2-centos test]$ cp mylib/include/*.h /usr/include/
[wyn@VM-8-2-centos test]$ cp mylib/lib/libmymath.a /lib64/

2.使用者拿到库后,编译链接时遇到的问题

2.1 gcc找不到头文件

1.
使用者直接gcc编译链接,会发生报错,显示找不到头文件。
gcc编译器在搜索头文件时,有两种搜索策略,一种是在当前路径(和源代码同级路径)下搜索,一种是在系统默认指定路径下搜索,当前路径下gcc确实找不到mylib库里面的头文件。

在这里插入图片描述
2.
所以我们需要利用-I选项,来指定gcc的头文件搜索路径,

[wyn@VM-8-2-centos test]$ gcc -o mymath main.c -I ./mylib/include/

2.2 链接错误:函数的未定义引用(库文件找不到,库搜索路径)

1.
指令执行后产生了链接错误,也就是说预处理、编译、汇编阶段已经没有问题了。
库文件如果在系统路径下(/usr/lib64或者/usr/lib路径),链接器肯定可以找到对应的库文件,但是当前路径下的库文件,链接器确实找不到。

在这里插入图片描述
在这里插入图片描述

2.
所以需要利用-L选项,来指定链接器的搜索路径。但除此之外,还需要指定库名称。
因为如果要链接第三方库,就必须明确指定库的名称

[wyn@VM-8-2-centos test]$ gcc -o mymath main.c -I ./mylib/include/ -L ./mylib/lib/

仅仅指明库文件路径,系统依旧报链接错误。
在这里插入图片描述

3.
而头文件不需要指定头文件的名称,只需要头文件所在路径即可,那是因为源代码main.c告诉了编译器要包含什么头文件,gcc会去指定路径下找特定的头文件。
但没人告诉链接器要链接哪一个库文件,所以我们必须指定库文件的路径和名称。

4.
但以前写代码的时候,我们从来没有指明过库名称,那是因为当时我们没有使用过第三方库,使用的都是C或C++语言提供的标准库,或者是操作系统提供的系统级别的接口,所以gcc或g++默认就可以确定代码需要链接的是哪一个库文件,但今天我们连接的库并不是标准库,而是第三方库。

第一方库:系统的
第二方库:自己的
第三方库:其他人写的

5.
利用-l选项指定库名称时,要去掉库文件的前缀lib和后缀.a或.so,中间剩下的才是库文件的名称。

在这里插入图片描述
选项和选项后面的内容之间,有没有空格都是可以的,不带空格自动补齐的功能就会没有了,但不带空格看起来会更加紧凑一些。

[wyn@VM-8-2-centos test]$ gcc -o mymath main.c -I./mylib/include/ -L./mylib/lib/ -lmymath

去掉前缀和后缀之后,就能正常使用库mylib了。
在这里插入图片描述

2.3 具体的链接方式取决于什么?(取决于提供的库 以及 编译时带的选项)

1.
但通过ldd列出共享库和file产看mymath文件的具体信息,我们又会发现许多猫腻。
gcc默认是动态链接的,但如果我们就不提供动态库,只给gcc静态库呢?而且我们知道形成一个可执行程序,可能不仅仅只依赖一个库,那如果链接100个库,70个静态库,30个动态库,gcc又该怎么链接呢?

在这里插入图片描述

Linux 命令(61)—— ldd 命令(转载自csdn博主恋喵大鲤鱼的文章)

2.
所以gcc默认的动态链接只是一个建议选项,而究竟是动态链接还是静态链接,取决于提供的库是动态库还是静态库
如果只提供动态库,你没带选项,那正好就是动态链接。但如果编译带上-static选项,此时编译链接是不成功的,会发生报错,无法进行编译链接!
如果只提供静态库,你没带选项,那gcc也只能静态链接。当然如果你带上-static选项,那是更标准的做法。
如果动静态库都给gcc,此时你编译带-static选项,那就是静态链接。如果你没带,那就是动态链接。

3.
而链接的库中只要有一个库是动态库,gcc最后呈现的链接方式就是动态链接的。
可执行程序mymath中不仅链接了我们自己写的静态库libmymath.a,还链接了C语言的动态库libc.so.6,所以最后呈现的链接方式是动态链接。

2.4 将库路径拷贝到系统默认路径下(安装的本质就是拷贝)

1.
将库的路径拷贝到系统默认路径下,其本质就是安装。拷贝=安装

在这里插入图片描述
2.
即使我们已经将库拷贝到系统默认路径下了,但在编译时,如果不指明链接库文件的名称,还是会报相同的连接错误,函数的未定义引用,原因我们上面说过,头文件有源代码告诉链接具体的什么头文件,但库文件没人告诉,并且我们链接的还不是标准库,而是第三方库,所以在编译时,至少也要加上-l选项

在这里插入图片描述

3.
将系统默认路径下的库路径删除,这其实就是卸载。
我们自己写的测试代码,还是不建议拷贝到系统默认路径下的,系统默认路径下的库都是经过严格测试的,发布版本这样的系统流程的,我们自己写的测试代码安全性和实用性不够好,所以还是不要拷贝到系统默认路径下了。

在这里插入图片描述

六、动态库和动态链接(gcc -shared 生成动态库)

1.产生位置无关码 + 归档.o文件形成动态库(gcc -fPIC -c *.c和gcc -shared -o libxxx.so *.o)

gcc -fPIC -c *.c  ---生成.o文件
gcc -shared -o libmymath.so *.o  ---.o文件进行归档形成动态库

shared: 表示生成共享库格式
fPIC:产生位置无关码(position independent code)
库名规则:libxxx.so

在这里插入图片描述

2.程序运行期间,加载动态库时,OS和shell找不到库文件(四种解决办法)

1.
然后我们将库文件和头文件进行打包,放到mylib目录文件下,然后如果你想的话, 可以将这个目录压缩之后交给库的使用者,使用者进行下载、解压缩之后就可以获得库文件和头文件了。

在这里插入图片描述
2.
动态库的使用方法和静态库很相似,在编译时带上相应的选项就可以生成可执行程序mymath了。
但是当我们运行这个程序时,就出现了问题,我们的mymath程序确实是动态链接的,但是系统找不到我们的动态库libmymath.so文件。

在这里插入图片描述
在这里插入图片描述

3.
在编译时,gcc知道了库文件的路径和名称,但是在程序运行时就和gcc没关系了,动态库是在程序运行期间进行加载的,而在运行期间,OS和shell不知道我们的库在哪里,因为我们的库不在系统路径下,所以OS无法找到

2.1 将库路径添加到环境变量LD_LIBRARY_PATH(并非永久,只是暂时)

1.
在程序运行期间,shell不仅仅只去系统默认路径下查找库,还会去环境变量LD_LIBRARY_PATH下查找,所以只要将动态库文件路径添加到环境变量中,就可以解决问题。

2.
ldd显示的内容中可以看到,OS成功找到库文件路径。

在这里插入图片描述

2.2 在/etc/ld.so.conf.d/目录下增加配置文件,并手动调用ldconfig更新一下

1.
但是当我们下一次登录xshell时,环境变量中我们刚刚添加的路径会默认自动消失,所以在下一次登录时,mymath就又无法正常运行了,还会报找不到库文件的错误,如果想让路径永久生效,就需要改环境变量的配置文件,这个配置文件改起来非常的麻烦,所以环境变量这样的解决方式,就比较适用于平常的测试,暂时在本次登录下有效。

2.
首先进入/etc/ld.so.conf.d/中,可以看到目录中有许多的配置文件,这些目录中都存着动态库的路径,如果我们将自己的动态库路径写到一个文件,并将这个文件放到/etc/ld.so.conf.d/目录下,OS和shell也就可以找到库文件了。

在这里插入图片描述

在这里插入图片描述
3.
在新增配置文件过后,依然可以看到可执行程序的动态库文件依旧找不到,其实这里还差一个步骤,我们需要手动调用ldconfig一下,因为我们安装了一个新的动态链接库,所以需要告知系统一下,也就是刷新一下,刷新过后就可以正常运行程序了,程序运行期间也就可以正常链接动态库了

在这里插入图片描述

Linux :ldconfig的使用介绍(转载自csdn博主技术探索者的文章)

2.3 在系统或当前路径下,建立动态库文件的软链接

1.
在程序运行时,系统会在当前路径下查找需要链接的动态库文件,那么我们可以通过软链接的方式来建立动态库文件的快捷方式,让系统能够在运行期间通过快捷方式找到对应的动态库文件。

在这里插入图片描述
2.
除了在当前路径下建立软链接,我们也可以在系统路径下建立软链接,这样OS也可以在程序运行期间找到动态库文件

在这里插入图片描述

2.4 将动态库文件路径拷贝到系统默认路径下(说白了就是将动态库进行安装)

这个解决方式就不细说了,比较简单,只需要cp一下即可,执行命令时带上sudo选项,但不推荐这么做,因为我们的动态库和人家系统的库比起来,比较拉,所以不要乱安装动态库。

3.安装其他第三方库ncurses

[wyn@VM-8-2-centos Use_libraries]$ sudo yum install -y ncurses-devel

1.
安装好ncurses库之后,可以在系统默认头文件和库文件路径下,查找到下载的ncurses库的头文件和库文件。

在这里插入图片描述
2.
下面是使用ncurses库的demo代码,大家也可以在vim上玩一下,在编译代码的时候,要告诉gcc库的名称,否则会报连接错误:函数的未定义引用。

//test.c
 
#include <string.h>
#include <ncurses.h>
 
int main()
{
    
    
    initscr();
    raw();
    noecho();
    curs_set(0);
 
    const char* c = "Hello, World!";
 
    mvprintw(LINES/2,(COLS-strlen(c))/2,c);
    refresh();
 
    getch();
    endwin();
 
    return 0;
}

下面是demo代码的运行结果
在这里插入图片描述

curses ncurses库 介绍安装(转载自csdn博主whatday的文章)

七、动静态库加载过程的深度理解(绝对编址、相对编址:fPIC产生与位置无关码)

1.
静态库不需要加载,在加载程序也就是编译链接时,系统就会将静态库的代码拷贝到可执行程序的代码段里面,因为可执行程序中没有栈和堆段,只有代码段、数据段(可以细分为.data和.rodata段)和BSS段
所以物理内存中,必定有静态库的代码,因为静态库的代码会作为可执行程序的一部分,加载到内存的虚拟地址空间中,然后通过页表映射到物理内存上,那么物理内存上就有静态库代码的地址,这样的加载方案就是绝对编址的方案

在这里插入图片描述

程序或-内存区域分配(五个段)–终于搞明白了(转载自csdn博主helmsgao的文章)

2.
和静态链接不同的是,动态库只将可执行程序用到的库函数的偏移地址拷贝到可执行程序里面,动态库中所有库函数的编址方案都采用start:偏移地址的方式来进行相对编址
在CPU执行代码的时候,发现物理内存中有外部地址,这个外部地址就是编译链接阶段动态库中函数的偏移地址,此时OS就暂且不执行我们的代码,而是先将外部地址对应的动态库加载到物理内存中(加载动态库时应该是需要什么加载什么),然后OS会通过页表将物理内存中动态库的位置映射到虚拟地址空间中的共享区,一旦动态库被映射到共享区,那么这个库的起始地址也就立马被确定了,完成映射之后,虚拟地址空间中不是有库函数的偏移量吗?那就直接在虚拟地址空间的上下文中进行跳转,跳转到共享区中,而现在已经拥有了库的起始地址和具体库函数的偏移量,所以在共享区中就可以很轻松的找到库函数的二进制代码并且将其执行,执行完毕之后,在跳转到代码段中,继续向后执行剩余的代码。

3.
这也就能解释,在动态生成.o文件时,gcc编译我们要加 -fPIC 选项,这就是为了让动态库中的函数采用相对地址的方案来进行编址,以便完成后续程序运行时的动态链接过程

4.
打包.o文件时,gcc采用 -shared的选项来形成动态库特定格式,方便操作系统后期以库的方式来将动态库加载到内存。然后再通过页表映射到共享区,库的起始地址被确定,代码段跳转到共享区,拿着库函数偏移量和起始地址,执行对应的库函数二进制码,随后再跳转回代码段执行剩余二进制码。

5.
假设有100个程序用了静态库,进程轮换时包含的100个进程都拥有自己的静态库代码,而不是进程间共享的。
而如果100个程序用了动态库,那么物理内存中只需要有一份动态库代码即可,进程轮换时包含的100个进程只需要共享一份物理内存上的动态库即可。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/erridjsis/article/details/128797445