基础IO的介绍(四)

1.软硬链接

下面来谈一下软硬链接,ln建立链接,-s代表soft软的意思:

看到有个soft-link文件指向file.txt,这是我们对应的软链接。看下图:

file.txt和soft-link文件的inode不 一样,说明它们是不同的文件,所以软链接是一个独立的文件,具有独立的inode。再touch test.txt,然后ln test.txt hard-link:

ln是建立链接的命令,带s表示建立软链接,由后者指向前者,不带s指的是硬链接。ll后发现hard-link有了,但test.txt和hard-link前面数字变成2了,也就是这个数字本质叫做硬链接数。再看下图:

再把inode调出来,发现hard-link和test.txt对应的inode是一样的,因此硬链接不是一个独立的文件,因为它没有独立的inode。

那么该如何理解软链接?该如何理解硬链接?首先来谈如何理解硬连接:根据我们目前的认识,hard-link和test.txt的inode一样,刚开始创建test.txt时这个inode就有了。因为hard-link和test.txt是同一个inode,所以它们对应的属性绝对是一样的,也侧面证明了文件名不在inode中保存,否则ls列出的文件名一样。所谓的建立硬链接,本质其实就是在特定目录的数据块中新增文件名和指向的文件的inoed编号的映射关系。看下图:

现在rm test.txt,此时再ls -li看到1311048这个文件还在,链接数由2变成了1。所以经过上述操作,成功做了取别名。任意一个文件,无论是目录还是普通文件,都有inode,其中每一个inode内部,都有一个叫做引用计数的计数器。目录里面保存的是文件名和inode编号的映射关系,可以在一个目录里面存在多个不同的文件名映射同一个inode,所以我们可以间接地认为每个文件最终可指向同一个inode,这个有点类似于指针,所以引用技术表明有多少个文件名指向我。所以删一个文件是把目录中的文件名的映射关系去了,然后找到inode把引用计数减减,减到0时文件才被删除,所以前面没被删除。

下面来谈谈如何理解软链接:软链接是一个独立的inode,意味着它有独立的inode属性及数据块。我们把软链接可想象成一个独立文件,数据块里放的是所指向文件的路径。比如向file.txt里写一串a:

cat soft-link时能访问到文件内容,因为soft-link中保存的是对应文件路径。所以如果把file.txt删了:

此时软链接荒神了,因为指向的文件不存在了。这个软链接特别像Windows中的快捷方式:

这是快捷方式,里面有个目标属性,记录了它所指向的系统中的可执行程序,所以快捷方式删了没什么用,可执行程序仍然在系统中。删除链接文件要么rm,要么用unlink:

那为什么要用软硬链接?先说说为什么有软链接,我是一个程序员:

我给客户写了一个可执行程序:

我的可执行程序未来放到了一个目录下,用户执行的时候就要带路径去执行:

为了方便来个软链接,往后运行就方便了:

为什么有硬链接?当我们touch一个file文件时:

这个文件的硬链接数是1。当我们mkdir dir:

为啥此时建立的目录硬链接数是2呢?任何一个目录在目录内部都会存在一个点和两个点,一个点叫当前目录,两个点叫上级目录。现在就能理解为啥一个点表示当前目录,因为它们的inode一样,dir目录中的一个点是dir目录的硬链接。两个点是上级路径,当前路径在dir里,上级路径是lesson12:

看到inode一样。所以cd..时为啥进入到了lesson12呢?因为..数据块里是上级目录。现在在dir里创建一个dir1,此时再ll:

为啥变成了3?因为dir1里面有个点点:

点点指向1311047,就是dir。再看下图:

看到根目录引用计数是18,说明根目录中有18-2=16个有效目录(点和自己)。所以硬链接通常用来进行路径定位,采用硬链接,可以进行目录间切换。再看下图:

系统中不允许对目录建立硬链接。为什么呢:

找文件时可能会形成环路问题,所以不允许。写文件就要找inode,系统可以自己建点和点点,系统做搜索时不会对点和点点这两个隐藏目录做搜索。

把打开文件和文件系统的文件产生关联:在OS当中,我们给一个进程创建PCB,创建它所对应的文件描述符表,要有对应的structfile。OS层面上structfile要能帮我们找到文件对应的struct inode文件属性。OS层面struct file也给该文件提供了文件缓冲区。在应用层写入时是把应用层缓冲区的数据想办法写到文件缓冲区,再把属性一改,最后把属性刷新到磁盘,缓冲区内容刷新到磁盘,此时完成了数据的更新:

数据从c语言写到磁盘经历了几次拷贝?3次。普通数据拿到c语言缓冲区里是第一次,再到文件缓冲区是第二次,文件缓冲区里把数据落实到磁盘上是第三次。

2.动静态库

下面来谈一下动静态库,分为三个阶段:1.回顾,站在库的制作者角度。2.站在库的使用者角度3.动静态库是怎么被加载的。下面开始第一部分:曾经说过我们的库分为静态库和动态库,linux中静态库叫libxxx.a,动态库叫libxxx.so,与静态库相对的有静态链接,与动态库相对的有动态链接。静态链接是把库中的代码拷贝到我最初给的可执行程序里,动态链接是把调用库函数在库中的地址填进来就可以了。下面我们自己设计一个静态库:(touch mymath.h,touch mymath.c,也就是我将来想设计一套方法,声明放头文件,实现放源文件,整体用以上形成一个简单的库文件):

这样写完了方法声明及实现。现在想把我们提供的方法给别人用:1.我把源文件直接给他,包好头文件后把我和他的.c编译链接就行了。但我不想给它源代码,因此2.把我们的源代码想办法打包成库,最后提供库+.h。那可以不给头文件吗?不可以,因为可以发现任何库它的头文件必须要公开,因为头文件本质是这个库里面所提供方法的一个说明书,要不然怎么知道有哪些函数。现在我想给他一个库怎么办呢(touch Makefile)?我想形成的库名字叫libmymath.a,$要形成lib,依赖的原文件.o文件:

我们对应的静态库非常简单粗暴:

要先把源文件编译成.o文件。今天我有很多源文件:a.c,b.c,c.c,d.c,还有个main. c。前面4个是库的源代码,main.c是我自己写的,我自己要调用库里面的方法。让别人用我方法可以把源代码给他,相当于:

这些源文件直接编译,就要把它们都变成.o,然后把5个.o链接就形成可执行了。静态库就是反正最终把自己源文件.o和方法源文件的.o链接,现在我自己把前4个先编为a.o,b.o,c.o,d.o。我再把这些.o文件打个包叫libxxx.a,然后把libxxx.a交给main.c(本质libxxx.a里是所有方法源代码.o文件的集合)。未来编译时候把.c变为.o,然后和.a一结合,最终形成.exe:

所以现在要形成一个库要把mymath.c编译成.o,直接gcc -c $^什么也不加:

gcc命令默认在编译的时候,$^是mymath.c,直接把源文件形成同名的.o文件。.o有了后要把.o打包一下,ar是一个生成静态库的命令,把所有的.o打包形成点.a,-r是replace的意思,-c 是create的意思。ar -rc libxxx.a xxx.o xxx.o意思是把两个点o放到目标文件里,目标文件里有存在就替换,没存在就创建:

make后看到mymath.o形成了,然后把mymath.o打包形成 libmymath.a,至此就有静态库了。清理也没有问题:

现在把库做出来了,然后尝试把库发布一次,让别人能直接用起来:

想形成一个库make一下,想把库发布出来make output:

看到当前目录多了个lib,tree一下发现include里包含的是我要给他的头文件,mymathlib包含的是.a 文件。未来别人用我的库,我直接把lib文件夹给他就行了。下面我是一个用户要用一下这个库(进入新路径下):

别人先拿到了我对应的文件,然后创建一个main.c打开:

编译时发现编不过,因为编译代码时首先要预处理进行头文件展开,展开的第一件事是要把对应的头文件找到。gcc在找mymath.h时会在默认路径下找,ls/usr/include,我们知道系统中不存在mymath.h头文件。gcc搜索先会去系统默认搜索路径下搜,再去当前目录搜索,但mtmath目前既不在系统里,也不在当前目录,即使gcc可带-I选项,意思是告诉gcc除了在当前目录和系统目录下找,若不找不到去我的指定目录下找:

虽然仍有报错,但不是头文件找不到了。现在报错说找不到add方法,这里是链接报错:

因为gcc -c没问题,说明前三步正确。报错原因是找不到add方法实现,也就是找不到静态库。那找不到库是什么原因?gcc默认只会去/lib64/libc.a系统路径下找默认库或当前找,所以编译时还要告诉gcc,用-L,找不到去指定路径下寻找:

报错仍然存在,因为只说明了库在哪里,并没有说明要链接哪一个库,所以也要说明链接的是哪一个库(为啥-I不用说找哪个头文件?因为源代码已经告诉了gcc include了哪个头文件,所以只用说明路径),所以带-l,表示要链接哪一个库:

还是不行,库真实名字是去掉前缀,去掉后缀,剩下的叫库名。一般建议l后紧跟库名称:

此时编译通过。不想带中间一大堆选项怎么办?1.把lib里的头文件拷贝到系统的include里,库文件拷贝到系统的lib64目录下,编译时只需指明-l就可通过。2.建立软链接。

之前写c或c++用gcc编译时从来没有指明过-l等这些选项,因为c或c++这样的语言库本身是一种默认的库,gcc或g++本来就认识。上面我们写的libmymath.a我们链接时必须指明这个库的名称,然后编译器才知道链哪个。对我们来说往后在链接库这件事情上,除了系统默认的系统调用(fork、wait等)、语言级别提供的库,除此之外提供的这些库gcc一律都不认识,把这样的库叫做第三方库,用第三方库时必须带-l选项指明库名称。现在解决个问题:

故意除了一下0:

我们是做过相应判定的,返回-1且把库中变量设为1。下面运行:

看到是-1,那比如我显示的是result=-1,我怎么知道这个-1是正确还是错误?库是可以给我们提供一个全局变量的,调库函数出错可通过全局变量来得知为什么出错。打印结果上errno是0,因为c 语言形参实例化是从右向左的,所以刚开始myerrno行参实例化是0,然后才调的是div。可这样:

现在就理解了,c语言默认提供了一个全局变量叫errno,返回值失败时我们最想知道为什么失败,所以定义errno这样的全局变量可查看它对应的出错码。今天我们自己写的lib库里,也包含了我们自己对应的全局变量。它被头文件声明,源文见定义,最终在整个库只有一份,所以每次调的时候一旦出错就被设置了(上述说明了:1.第三方库,往后使用的时候,必须是用gcc -l。2.深刻理解errno的本质)。

以前说过ldd可查看程序的链接方式:

发现没看到mymath这样的库,怎么理解?gcc链接程序时默认是动态链接的,但gcc时-lmymath只提供了mymath,运行结果能打出errno,说明我自己的可执行程序里包含了我这个库。相当于我们在进行链接时,gcc对特定的一个库链接时,默认肯定是动态链接。但如果没有提供动态库,只提供静态库,gcc只能把这个库按静态链接的方式,所以看不到。如果今天系统中没动态库全是静态库,此时gcc即使不带-static它也只能静态链接,所以-static其实是一种建议形选项,动静态库都提供时gcc肯定默认是动态链接。想静态带-static, 若只提供静态的那只能静态链接。如果系统中只提供静态链接,gcc则只能对该库进行静态链接。不带static,有动优先连动,没有连静,带static 只能连静。如果系统中需要链接多个库,则gcc可以链接多个库。现在每次gcc都要-I、-L弄这么一大堆,有没有什么办法不要指明-I、-L了,直接链接库:(系统路径下拷,权限提升)我们要把对应的头文件和库文件拷贝到系统默认路径下,就不用再带-L或- I了:

现在把库的头文件和库文件放到了系统中:

发现可以跑了,没带太多选项。看下图:

我们把别人的库下到了Linux上,这是sudo拷贝(前两个动作)是在做库的安装。只要把库下下来这个库必须要提供头文件和库文件,所谓安装是把头文件拷贝到系统路径下,把库文件扔到系统路径里,这就是安装。卸载是把系统中的头文件和库文件从特定路径下删了就行:

但自己写的尽量别放系统路径下。有没有一种方式能不让我们拷贝库和头文件也能把我们程序运行起来?进行软链接:

此时系统头文件下建立了软链接,这个链接文件也可指向头文件。但这种用法不对,因为它的头文件多不可能给每个头文件都建立软链接,从此往后这样用:

再到系统中建立软链接:

解决了头文件和库文件搜索问题,但仍是指明用哪个库,此时就可以正常运行了。上述只想说明我们使用库可以在系统默认搜索路径下建立对应的软链接,这样凡是在系统中自己安装的就不需要拷贝到系统路径下。

下面来谈一谈动态库:(touch myprint.h myprint.c mylog.h mylog.c):

这里我将来想make一下,一次既形成一个动态库,又形成一个静态库,而且把动静态库各自分离开。看下图:

现在想把log和print一对打包形成动态库。首先把源文件编译成.o,和静态库刚开始思路一样,唯一区别是形成动态库编译的.o要额外加一些选项(选项作用后面说)。要带选项-fPIC,意思是产生位置无关码,-c不指明目标文件默认形成同名.o。先命令行方式看看:

看到形成了两个.o。下面把.o文件打包形成一个库,仍然用gcc命令形成动态库(gcc从形成.o到最后形成库,不用借助其他工具,gcc默认把动态库那一套内置了):

此时看到形成了动态库,为啥动态库有x权限,静态库没可执行?因为静态库将来是提供源代码的,它的作用是将来形成可执行时把静态库中要的东西拷过去,静态库是不会加载到内存的。动态库要和可执行产生关联,若访问动态库内容要跳转到动态库执行,这样注定了动态库必须要被加载,只有可执行程序这样的库文件才能帮我们快速加载,像可执行程序一样被系统加载到内存里面。也就是说怎么理解可执行权限?其实是这个文件是否会以可执行程序形式加载到内存里。(rm *.o *.so)现在想Makefile一键形成:新增要形成的叫libmethod.so

这样动静态库都有了。下面make一下:

有了动静态库,再make output:

形成一个mylib,再tree:

看到了提供的头文件和库。此时就能把mylib去给别人用了,我这里有log、math、print方法,这还有两个不同的库。现在拿给别人用:

相当于别人下了一个库叫mylib,里面包含了头文件和对应的动静态库,然后别人可在他的项目里用了。看下图:

静态库已经能成功编译了。改一下代码(只用动态库):

直接gcc还是会有头文件找不到和链接错误:

正确形成了a.out,和静态库相似。再把静态库屏蔽取消,再次编译:

完成动静态库都编过。

下面继续屏蔽动态的只看静态:

运行的时候出现了一个问题,如何解决:

报错说加载动态库时,打不开动态库,因为这个库找不到,和ldd时看到的not found是相吻合的。但我在编译时不是已经告诉编译器库在哪里,为啥运行的时候还是说找不到呢?因为我们只告诉了编译器这个库在哪里,但加载时并没告诉系统(加载器)动态库在哪里。我们以前写c 语言用c语言库程序加载时为啥可以找到呢?不仅编译,加载时也要有路径,系统加载时会默认去某些路径下进行搜索我们动态库,这些路径都是系统中内置好的。看下图:

我们当前动态库在当前路径下的mylib里的lib下,OS做不到直接找,我们要想办法让OS找到。其中一种方法是把我们的动态库.so拷贝到系统lib64下,这样就可以找到动态库了。还有种方法是建立软链接:

ldd看到程序找到了动态库,也能运行。看下图:

删除后看到又找不到了。通过上面有两个解决加载找不到动态库的方法:1.拷贝到系统默认的库路径/lib64(常用)/usr/lib64。2.在系统默认的库路径/64或/usr/lib64下建立软链接。还有一种方式:系统中存在一个环境变量叫 LD_LIBRARY_PATH:

可能没有,这个环境变量默认是没有帮我们在系统里建立的。这个环境变量是专门用来给用户提供搜索用户自定义的库路径的。所以我们要让别人使用起来动态库,可想办法将我们库的路径添加到环境变量里就可以找到了。添加时别把原始路径覆盖了:

echo后看到存在了我们的搜索路径。在看下图:

ldd后看到libmymeth.so可以找到对应的库,而且可以运行。因此3.将自己的库所在的路径,添加到系统的环境变量LD_LIBRARAY_PATH中(理解到环境变量是系统级别的全局变量,它可支持编译器、加载器等工具搜索它所要的文件等)。如果想让我们动态库和系统一样随随便便可让别人运行起来,可使用etc下的ld.so.conf.d目录下建立一个文件就可以了:

这个目录下可以存在很多我们对应的配置文件,这个配置文件里放的都是路径。只要这个目录下建立.conf的配置文件,然后把我们动态库路径放进去,系统自然就找到了:(cd /etc/ld.so.conf.d)(touch yxx_test.conf)vim:

有了后ldconfig把所有配置文件重新加载一下,然后ldd后就看到了:

清空后再加载一下:

看到又找不到了。所以4./etc/ld.so.conf.d目录下建立自己的动态库路径的配置文件,然后重新ldconfi(关闭xshell再打开也一直)(编译时告诉过编译器链接哪个库,库名系统知道,所以添加路径时不用指定库名)。实际情况,我们用的库都是别人的成熟的库,都采用直接安装到系统的方式(方法1)。

现在知道动态库在进程运行的时候,是要被加载的,静态库没有(因为把代码拷到可执行程序了,加载过程和静态库没关系)。常见的动态库被所有的可执行程序(动态链接的)都要使用,所以一般把动态库也叫作共享库。所以动态库在系统中加载之后,会被所有进程共享。比如你用libc,他也用libc,系统会因为你用和他用把libc加载两次吗?不会,加载后直接共享就行,这些重复代码就少了。那怎么做到被共享的?下面来谈谈动态库是怎么被加载的:每个进程都有自己的PCB、地址空间、页表。每个进程把自己代码、数据、堆等映射到物理内存。磁盘上有对应的可执行,可执行程序有自己的代码和数据分别加载到物理内存,当前进程通过页表访问代码和数据:

后来又新启了一个进程,还会有独立的一套:

如果1.exe和2.exe执行的是OS代码呢?OS也能当一个可执行程序,两个进程互不影响,其实这是内核级虚拟机的原理。动态库也是文件,最初在磁盘中,当进程执行1.exe的代码和数据,代码中要使用printf库中代码:

但当前代码里没有printf的实现。程序最初在编译链接时已知道printf代码在库里面,但库还没加载到内存,所以要把库加载到内存:

目前进程看不到对应的库,用printf时还不能调库里面的方法,此时把当前动态库经过页表映射到地址空间对应的共享区中:

想调printf就跳转到共享区里去执行,执行完后从共享区返回到正文部分,此时能实现在你地址空间上对库的访问了:

建立映射,从此往后我们执行的任何代码,都是在我们的进程地址空间中进行执行。也就是只要把动态库映射到共享区,整个进程在CPU级别去调度的时候都是在我自己对应的地址空间内进行各种运行的,无非是在我的地址空间上进行函数远距离跳转罢了。和我自己在自己代码内跳转没区别,相当于把两份代码组合起来。有个事实:比如一个进程可能打开多个动态库,所以系统在运行中,一定会存在多个动态库。OS本身要把所有加载的动态库管理起来------先描述再组织。也因此整个系统中,所有库的加载情况,操作系统非常清楚。后来又有个进程要执行2.exe,也想用3.so。此时操作系统要介入了,看索要的库有没有被加载。现在知道要的库已被加载了,就把动态库经过页表映射到共享区:

用的时候还是跳到共享区执行完后返回来就行:

所以系统中只需存在一份共享库就可被所有进程全部共享使用了,所以这个库既叫动态库也叫共享库,通过将库映射到地址空间完成共享。有个问题:我们说过共享库中提供一个errno全局变量,那现在A进程运行使用c标准接口出错errno被设置,B进程也出错被设置了,那我们共享同一个库对同一个errno做修改岂不是会让进程间互相干扰?不会,库是在堆栈之间的,这属于用户空间,写入的时候会发生写时拷贝。

3.地址的问题

上面宏观了解后还是有很多关于地址的问题,下面关于地址来谈谈:1.程序没有加载前的地址。2.程序加载后变成进程了的地址。3.动态库的地址。先来谈1,当我们编译形成一个可执行程序时还没运行,这个程序内部有地址吗?我们平时vs上写代码时调试过,进入汇编发现每行代码都是有地址的,所以可执行程序编完后内部有地址的概念。对我们来讲我们对应的可执行程序编译时就有地址了,如图:

代码编译时已经天然的给代码和数据编址了,调用时不用函数名转而转成地址。现在我们可执行程序在编译链接形成可执行程序的时候,内部的编译已经变成了平坦模式:相当于整个执行程序在编址的时候代码区和数据区在哪已经遵照地址空间的方式来编译代码的,也就是我们在磁盘上看到的可执行程序地址顺序排布问题和地址空间规则基本一致。所以我们可执行程序内部已经有地址了,编译器在编译程序时每个程序在赋予地址的时候已经考虑将来加载的问题了,所以编译器也要考虑操作系统。编译器编译对应的可执行程序最终是要被我操作系统加载的,编译器要照顾可执行程序将来在内存中加载的问题。看下图:

我们可执行程序编址时形成.code(代码段) .rdonly(只读段) .data(已初始化数据段) .bss(未初始化),把相应内容放相应区域,编址时从上往下顺序编址。这上面的地址其实已经是虚拟地址了,也就是我们对应的可执行程序还没加载到内存的时候已经按虚拟地址方式加载好了。但是为了能更好区分,可执行程序这不能叫虚拟地址,这种地址我们把它称为逻辑地址。

下面来谈2:有个物理内存,还有磁盘,磁盘上有对应的可执行程序(假设是上面一堆)。接下来考虑把可执行程序变成进程就要加载到物理内存:

指令都有它对应的地址,那么指令或可执行程序加载到内存内部用的地址其实是虚拟地址。程序在物理内存随便一个位置加载,那一条条指令要不要占据物理内存空间呢?要,当把可执行程序加载到内存之后,每条指令天然就具备了物理地址。如图:

call 4访问的是内部地址,对应这条指令有自己的物理地址。当程序加载到内存时,重要的指令都有两套地址,一个是自己内部用的加载之前的逻辑地址,以及加载到物理内存时所具备的物理地址。每个进程有自己的PCB,进程空间,页表。CPU要执行对应的代码和数据:

现在程序已经加载到内存了,进程数据结构也创建好了,那么该如何执行第一条指令呢?我们形成的可执行程序里,它形成时里面包含了entry入口地址,代表它的可执行程序在编译形成的时候,已经在可执行程序头部提前把整个程序入口地址写好了。这个入口地址是逻辑地址(可执行程序没加载到内存)。CPU怎么知道当前该执行哪条指令?CPU内有个寄存器叫EIP(下一条要执行的指令的虚拟地址),进程要记录自己当前的cwd工作目录,还有exe可以找到自己的可执行程序。(先形成内核数据结构)我们可执行程序要运行时,先把入口地址加载到CPU的寄存器中,因为这个地址天然编址时是个虚拟地址,所以CPU开始执行了,首先跑到正文段去执行。然后要去读页表,此时触发缺页中断,然后程序被加载进来,被加载进来后程序天然具备了物理地质,然后页表中建立好映射关系,就能找到对应的代码了。然后开始按顺序去执行,读一条指令这条指令长度我们知道,让EIP里面加长度就能让指令按顺序去执行了。看下图:

CPU读到执行函数的指令了,这样情况说明CPU内读取到的指令,内部可能有数据,可能也有地址(如call 4),那这个指令内部用的地址是什么地址?虚拟地址(加载到内存时没有逻辑地址的概念了),访问4时在地址空间中找到虚拟地址再转成物理地址去执行,有可能页表中找到4时并不存在,发生缺页中断就可以了。从此我们会发现,我们整个过程从读取程序中的地址,到CPU分析处理,到重新通过页表访问它:

读到的指令的地址全部都是虚拟地址。

下面谈3:我们编址时从全0到全f范围把它们编好了,我们要衡量一个位置比如有个位置叫0x11223344,这个地址是对整个范围来讲的:

我们把这样的地址称为绝对地址。下面说个例子来理解:比如张三在0~100米的跑道上跑步,小明问张三跑哪了,张三说30米处。这里说的30米是相较于0~100这个范围内说的,这数字是唯一的,称为绝对地址。100米的跑道40米处有个树,问张三在哪张三说在大树边10米的地方,这里的10以树作为参照定出来的,这样地址可称为相对地址或逻辑地址:

假如这棵树在最开始:

此时逻辑地址偏移是等于绝对地址。我们前面说过一个进程启动时有对应的地址空间,PCB,页表,磁盘中有对应的可执行程序,CPU通过EIP访问,若有缺页中断会建立起物理到虚拟的映射:

当执行某些库函数方法时,比如我的1.exe可执行程序里有printf,程序编译后printf会变成对应的地址:

这个地址可帮我们找到它在动态库中的位置。今天CPU在执行代码时要执行printf了,就转去执行共享区中的代码(若共享库没有就缺页中断)。当执行代码要访问printf的时侯,共享库加载到物理内存随便放,我还要把动态库映射到共享区里:

现在问题是:当前共享区是非常大的,进程之前可能还用了liba.so libd.so等,我们这的printf是被硬编码到可执行程序的,我将来的可执行程序调printf只能访问0x112,那么共享库这段内容必须被加载到0x112上。但一个进程在运行时,库先加载还是后加载说不准,怎么保证每个库都必须加载到固定位置呢?发现动态库被加载到固定地址空间中的位置是不可能的(若先用了liba.so等可能还会占用固定位置),我们需要想办法让库可在虚拟内存中任意位置加载。库在形成的时候让自己内部函数不要采用绝对地址编制,只表示每个函数在库中偏移量即可。此时0x1122是个偏移量,当我们把库加载到内存,然后在地址空间里共享区随便放,放好后建立好页表中库的映射,只需要记录好库在虚拟地址中的起始位置,因为操作系统要对库做管理,一个库被加载到地址空间的什么地址操作系统也是要知道的。正文调用时识别到调0x1122,识别到是哪个库中的方法,此时让起始地址加偏移量就找到库中的方法。前面说gcc在形成目录文件时加fPIC产生与位置无关码,意思是默认形成.o的时候采用绝对地址,现在直接用偏移量进行对库中函数进行编址。那么静态库为什么不谈加载,不谈与位置有关?静态库过会把程序拷贝到可执行程序里,谈不上加载问题。静态库直接拷贝到程序里,相当于库中的方法是在我的方法,直接按绝对编址编就可以了,所以不谈位置。

相关推荐
网硕互联的小客服2 小时前
Linux root用户密码输入错误锁定策略,使用旧密码失败如何处理?
linux·服务器·网络·centos·自动化
石小千2 小时前
部署Nextcloud与Onlyoffice(一)安装Nextcloud
linux·运维
倔强的石头1062 小时前
【Linux指南】基础IO系列(五):重定向原理与 dup2 系统调用 —— 改变 IO 流向的魔法
linux·运维·服务器
吴烦恼的博客2 小时前
RK3588-kernel BringUp记录(二)
linux·kernel
-ONLY-¥2 小时前
HAProxy+Nginx高可用集群实战指南
linux
花间相见2 小时前
【AI私人家庭医生day01】—— 项目介绍
大数据·linux·人工智能·python·flask·conda·ai编程
Cyber4K2 小时前
【Nginx专项】基础入门篇-访问限制及访问控制
linux·运维·服务器·nginx
涛声依旧393162 小时前
创建新的虚拟主机
linux·服务器·网络
高斯的手稿08013 小时前
树莓派上更换镜像源的方法
linux·运维·windows