ln -s 被软链接文件 软链接文件
我们在观察下面的情况,就是软链接文件,是有自己独立的inode编号的,说明他是一个独立的文件。
软链接本质上是一个独立的文件,软链接文件内容里面放的是链接文件的路径。
类似于Windows下的快捷方式。
ln 被硬链接文件 硬链接文件
我们观察下面,是可以发现hello.txt 和link.hard 其实上inode是一致的,这就说明link.hard其实上本质不是一个独立的文件
既然没有新建文件,那么就是一个新的文件名而已,在目录中插入了一段新的映射关系。
那么这样inode中一定有一个引用计数的变量用于记录这个inode编号有多少段映射关系。
那么硬链接的本质就是 在目录中插入了一段新的映射关系,并且让inode结构体中的引用计数++。
我们可以看到,这个查看文件信息是的这个文字我们从来没有提到过。 这个其实上就是文件的硬链接数:表明这个文件有多少个硬链接
我们回到上机目录,我们发现我们刚刚所处的目录居然有两个硬链接,但是,我们并没有给他创建硬链接啊,我们仔细对比了inode编号 目录的编号是于目录内的隐藏文件./是一致的。
所以我们得出结论,我们经常使用的 .. 和 . 其实就是目录的硬链接。
主要是因为,这样我们在查找文件的时候会陷入环路问题。
注:硬链接无法跨分区,因为只有在分区里面inode才唯一。
我们在编写代码的时候都使用过库。
动态库:libXXX.so 静态库:libYYY.a(库的真是名字其实是XXX和YYY那部分)
在Linux环境下,gcc默认链接的都是动态库,云服务器上其实连静态库都没有安装。
如果,要编译链接静态库,需要在后面加上 -static
动静态库的本质其实上就是一大堆的可执行程序。
将这些经过编译的二进制文件打包,这样就形成了库。
库的意义:这样隐藏了源代码,又提高了生产效率
ar [options] archive-file object-files
指令解析:
-c:创建库文件,如果库已存在,则会被覆盖。
-r:向库文件中添加.o文件,如果.o文件已在库中存在,则会被替换。
-t:列出库文件中包含的.o文件列表。
-v:在执行过程中显示详细的信息。
第一步:编译形成 .o 文件。
第二步:使用ar命令,将所有.o文件进行打包,形成静态库文件。
第三步:将库进行标准化。
libmyc.a:my_add.o my_sub.o //第二步:使用ar命令,将所有.o文件进行打包,形成静态库文件
ar -rc $@ $^
%.o:%.c //第一步:编译形成 .o 文件
gcc -c $<
.PHONY:clean
clean:
rm -rf *.a *.o mylib mylib.tgz
.PHONY:output //第三步:将库进行标准化
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp -rf *.h mylib/include/
cp -rf *.a mylib/lib/
tar czf mylib.tgz mylib
1.-I(i的大写):用来指定编译器搜索头文件的额外路径。 当编译器在编译过程中遇到#include指令时,它先会在标准的位置(当前目录或系统默认的头文件路径)来查找指定的头文件,如果查找不到,编译器就会使用-I指定的路径进行搜索。
2.-L:用来指定链接器搜索库文件的额外路径。 当链接器在链接中需要找到某个库文件(.so、.a),它先会在标准的位置(系统默认的库路径)中查找,如果查找不到,链接器就会使用-L指定的路径进行搜索。
3.-l(L的小写):用来指定链接器在链接过程中要链接的库。 补充:头文件的搜索路径:当前目录、系统默认的头文件路径(/usr/include、/usr/local/include)、gcc内置的标准头文件路径、命令行中通过-l选项指定的头文件路径。 库文件的搜索路径:系统默认的库文件路径(/usr/lib、/usr/local/lib)、gcc内置的库文件路径、命令行中通过-L选项指定的库文件路径、环境变量LIBARY_PATH中指定的路径。
这些选项分别用于控制编译和链接过程中的头文件、库文件的搜索路径和库文件的选择。
动态库制作的过程中只需要使用gcc就行了
第一步:使用-fPIC选项,编译形成 .o 文件。
fPIC:产生位置无关码(position independent code)
第二步:使用-shared,将所有.o文件进行打包,形成动态库文件。
第三步:将库进行标准化
libmyc.so:my_add.o my_sub.o //第二步:使用-shared,将所有.o文件进行打包,形成动态库文件
gcc -shared -o $@ $^
%.o:%.c //第一步:使用-fPIC选项,编译形成 .o 文件
gcc -c -fPIC $<
.PHONY:clean
clean:
rm -rf *.so *.o mylib mylib.tgz
.PHONY:output //第三步:将库进行标准化
output:
mkdir -p mylib/include
mkdir -p mylib/lib
cp -rf *.h mylib/include/
cp -rf *.so mylib/lib/
tar czf mylib.tgz mylib
动态库,再被使用的时候,是要在任何时候都要保证能被链接到的。 这个情况,就是在编译的时候,指定了路径去链接数据库。 但是,在执行程序的时候,由于没指明数据库,数据库也不再默认路径下,所以,就找不到。
拷贝进去之后,这个文件立马就能运行了。
你也发现了,我一运行完就删除了,就是为了防止这种不成熟的库,污染我的库。
ldd Filename 可以查看文件所链接的库
LD_LIBRARY_PATH:是一个环境变量,在linux中,为动态链接器指定额外的库搜索路径
一、动态库概念
动态库:也称为共享库,是一种包含代码和数据,可以在多个程序之间共享的文件,存放在磁盘上。
与静态库不同,静态库在程序编译时会被完全复制到可执行文件中,而共享库则在程序运行时被加载到内存中,如果多个程序使用同一个共享库,OS会让这些进程共享内存中的同一份库代码和数据,即:动态库的代码和数据在内存中只存在一份。
管理:系统中可以同时存在多个已经被加载的库,OS需要管理它们,先描述(包含了加载地址等信息)、再组织。
二、动态库加载的过程
检查依赖:程序启动时,动态链接器会检查该程序依赖的所有动态库。
搜索路径:动态链接器会在预设的库搜索路径中查找所需的动态库文件。
加载与映射:第一次加载、后续加载。
第一次加载:如果动态库尚未被加载到内存中,动态链接器会将该库加载到内存中,并映射到进程地址空间的共享区中。
后续加载:如果其他进程也需要共享这个库,动态链接器会检查内存中是否已存在该库;如果已存在,只需修改地址空间中共享区的映射关系,指向已存在的库副本;如果不存在,则重复第一次加载的过程。
优点:节省内存、易于更新、提高了程序的性能和安全性。
其实上就是谈谈可执行程序的问题。
第一个问题:一开始我们的程序在没有加载进入内存的时候有没有地址? --- 有了
其实,我们的程序在没有加载进入内存的时候,根据类别(比如权限,访问属性等),把可执行程序划分成了几个区域 。
我们之前提到的进程地址空间,其实上本质就是一个结构体(mm_struct),我们结构体里面记载了各个区域的开始地址和结束地址(就是两个指针变量),利用这种双指针的记载方式,我们达成了分区的效果。
那么这就有一个问题?这个结构体(即进程地址空间结构体)是由谁来初始化的?每个结构体的代码大小都不同,那么正文段的大小也应该不同,结构体的数据怎么来? ---- 里面的数据很多都是从可执行程序中来的 (和操作系统,编译器都有关系)
编址:在编译和链接阶段,为程序和库中的符号(变量、函数)分配地址的过程,主要有绝对编址、相对编址两种方式。
可编址的范围:32位平台,[0, 2^32] -> [0, 4GB] ,64位平台,[0, 2^64] -> [0, 16GB]。
绝对编址:在编译和链接过程中,符号的地址是固定的,即:已经确定了符号的实际的物理内存地址。这种方式要求程序运行时,必须加载到特定的物理地址处,否则无法正确的运行。 绝对编址中的地址 == 实际的物理内存地址。
相对编制:也成为逻辑地址、虚拟地址。在编译和链接过程中,符号的地址是不固定的,而是相对于某个基地址的偏移量。这种方式允许程序在加载时动态确定实际地址,从而实现位置无关代码。 符号地址 = 基地址 + 偏移量。基地址在编译链接阶段是未知的,通常是由OS在程序加载时分配的虚拟地址,是在地址空间内的一个起始地址,如:0x400000。
回答前面提到的问题:地址空间、页表中的数据来自哪里?
所以这里也提出一个观点:虚拟地址空间不仅仅OS系统要遵守,编译器也要遵守。
目前,使用的都是平坦模式下编址,为了兼容前面的相对编址,可以将全部代码看做一大块空间,那么,绝对编址下的代码的地址就是:0 + 偏移量。
每个可执行程序大小不同,说明了每个程序中各个区域虚拟地址范围也会不同。相应地,当这些程序被加载到内存变为进程时,则每个进程地址空间中各个区域的虚拟地址的范围也是不同的。
静态库,本身就是拷贝进入代码的,就按照普通代码,进行编址,然后加载到内存中,把编译出来的绝对地址,当做虚拟地址,进行页表映射。
一.先加载形成PCB和mm_struct,再对他们进行初始化
二.再将整个可执行程序加载进入内存当中,在构建页表
代码也是数据,我们将代码加载进入内存的时候,那此时,这条代码既有了在代码编译时形成逻辑地址,也有了在内存上的物理地址。那么此时也就有了虚拟地址和物理地址的相互对应,这样就可以构建页表了
问:那么CPU如何找到程序的入口地址,然后运行程序的过程
用可执行程序初始化mm_struct的时候,就会顺便把头文件里的main函数的虚拟地址加载进入CPU的PC指令当中,再利用MMU和页表配合,就可以完成虚拟到物理的转化,这样就找到了程序入口。
然后,将指令读取到CPU中的指令寄存器中,指令:
因为要调用函数,所以再将0x100000进行虚拟到物理的转化,找到函数,再执行函数
所以我们在这里输出一个结论:进程地址空间是一个由操作系统(创建PCB和mm_struct)+ 编译器(编译代码和形成逻辑地址)+ 计算机体系结构(虚拟地址到物理地址的转化)三者合作形成的概念。
问题:CPU如何对各式各样的指令,来做出正确的反应呢?
CPU其实就只能识别二进制,我们将所有的代码翻译成二进制文件的时候,CPU内部有一个指令集,里面记载了对于各式各样的二进制指令,CPU该如何去进行对应的动作。所以,将我们传入的指令与指令集相对应,然后做出反应
库函数和代码都加载进入内存的时候,调用库函数的方法的逻辑地址都是一致的 在编译器编译代码内部的动态库方法,并不会赋予它一个新的逻辑地址,而是会沿用动态 库内部这个方法的逻辑地址
我们知道,动态库的代码在虚拟地址映射的时候是映射到共享区的,所以在正文代码在执行的时候,执行到了动态库中的方法,此时,就要跳转到共享区,利用虚拟地址( start+偏移量)找到该方法,在共享区的位置,然后进行虚拟地址到物理地址的转化,找到该方法在内存上的位置,读取到CPU内部,开始执行代码