ホームページ >システムチュートリアル >Linux >Linuxにおけるダイナミックリンクとスタティックリンクの本来の意味は何でしょうか?
古いルールです。最初にいくつか質問してください:
動的リンクの出現は、静的リンクのいくつかの欠点を解決することです:
Program1 と Program2 には、それぞれ Program1.o と Program2.o という 2 つのモジュールが含まれており、どちらも Lib.o モジュールを必要とします。静的リンクの場合、両方のターゲット ファイルが Lib.o モジュールを使用するため、リンクによって同時に出力される実行可能ファイル Program1 と Program2 にコピーが存在します。同時に実行すると、Lib.o には 2 つのコピーがあります。ディスク上とメモリ上にコピーがあり、システム内でターゲット ファイルを共有する Lib.o に似た複数のプログラムが多数ある場合、多くのスペースが無駄になります。
モジュールが 20 個のモジュールに依存している場合、20 個のモジュールの 1 つを更新する必要がある場合、更新が成功する前にすべてのモジュールが検索され、実行可能プログラムに再コンパイルされる必要があります。モジュールが更新されるたびにモジュールの場合、ユーザーは非常に大きなプログラムを再取得する必要があります。プログラムが静的リンクを使用している場合、ネットワーク経由でプログラムを更新するのは非常に不便です。プログラムのどこかに小さな変更があると、全体がプログラムが再ダウンロードされます。
静的リンクの欠点を解決するために、動的リンクが導入され、動的リンクのメモリ配分は図のようになります。
複数のプログラムが同じ共有オブジェクト ファイルに依存します。この共有オブジェクト ファイルのコピーはディスク上とメモリ上に 1 つだけ存在し、コピーは生成されません。簡単に言うと、静的リンクとは異なります。プログラムを構成するオブジェクト ファイルへのリンクを実行し、プログラムが実行されるまで待機し、プログラムが実行されるまでリンク プロセスを延期します。ダイナミック リンク方式により、開発プロセス中の各モジュールの独立性が高まり、結合が少なくなるため、さまざまな開発者や開発組織が独立して開発およびテストすることが容易になります。
リーリー
コンパイルと実行のプロセスは次のとおりです:リーリー
-fPIC および -shared を使用してダイナミック リンク ライブラリを生成し、それを実行可能プログラムにリンクして通常どおり実行できます。readelf コマンドを使用して、ダイナミック リンク ライブラリのセグメント情報を表示できます。
リーリー
ダイナミック リンク モジュールのロード アドレスが 0 から始まることがわかります。0 は無効なアドレスです。ロード アドレスはプログラムの実行時に決定され、コンパイル時には不確かです。
プログラムを変更します: リーリー
実行してマップ情報を読み取ります:
~/test$ ./test & [1] 126 ~/test$ func 1 cat /proc/126/maps 7ff2c59f0000-7ff2c5bd7000 r-xp 00000000 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so 7ff2c5bd7000-7ff2c5be0000 ---p 001e7000 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so 7ff2c5be0000-7ff2c5dd7000 ---p 000001f0 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so 7ff2c5dd7000-7ff2c5ddb000 r--p 001e7000 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so 7ff2c5ddb000-7ff2c5ddd000 rw-p 001eb000 00:00 516391 /lib/x86_64-linux-gnu/libc-2.27.so 7ff2c5ddd000-7ff2c5de1000 rw-p 00000000 00:00 0 7ff2c5df0000-7ff2c5df1000 r-xp 00000000 00:00 189022 /mnt/d/wzq/wzq/util/test/lib.so 7ff2c5df1000-7ff2c5df2000 ---p 00001000 00:00 189022 /mnt/d/wzq/wzq/util/test/lib.so 7ff2c5df2000-7ff2c5ff0000 ---p 00000002 00:00 189022 /mnt/d/wzq/wzq/util/test/lib.so 7ff2c5ff0000-7ff2c5ff1000 r--p 00000000 00:00 189022 /mnt/d/wzq/wzq/util/test/lib.so 7ff2c5ff1000-7ff2c5ff2000 rw-p 00001000 00:00 189022 /mnt/d/wzq/wzq/util/test/lib.so 7ff2c6000000-7ff2c6026000 r-xp 00000000 00:00 516353 /lib/x86_64-linux-gnu/ld-2.27.so 7ff2c6026000-7ff2c6027000 r-xp 00026000 00:00 516353 /lib/x86_64-linux-gnu/ld-2.27.so 7ff2c6227000-7ff2c6228000 r--p 00027000 00:00 516353 /lib/x86_64-linux-gnu/ld-2.27.so 7ff2c6228000-7ff2c6229000 rw-p 00028000 00:00 516353 /lib/x86_64-linux-gnu/ld-2.27.so 7ff2c6229000-7ff2c622a000 rw-p 00000000 00:00 0 7ff2c62e0000-7ff2c62e3000 rw-p 00000000 00:00 0 7ff2c62f0000-7ff2c62f2000 rw-p 00000000 00:00 0 7ff2c6400000-7ff2c6401000 r-xp 00000000 00:00 189023 /mnt/d/wzq/wzq/util/test/test 7ff2c6600000-7ff2c6601000 r--p 00000000 00:00 189023 /mnt/d/wzq/wzq/util/test/test 7ff2c6601000-7ff2c6602000 rw-p 00001000 00:00 189023 /mnt/d/wzq/wzq/util/test/test 7fffee96f000-7fffee990000 rw-p 00000000 00:00 0 [heap] 7ffff6417000-7ffff6c17000 rw-p 00000000 00:00 0 [stack] 7ffff729d000-7ffff729e000 r-xp 00000000 00:00 0 [vdso]
可以看到,整个进程虚拟地址空间中,多出了几个文件的映射,lib.so和test一样,它们都是被操作系统用同样的方法映射到进程的虚拟地址空间,只是它们占据的虚拟地址和长度不同.
从maps里可以看见里面还有libc-2.27.so,这是C语言运行库,还有一个ld-2.27.so,这是Linux下的动态链接器,动态链接器和普通共享对象一样被映射到进程的地址空间,在系统开始运行test前,会先把控制权交给动态链接器,动态链接器完成所有的动态链接工作后会把控制权交给test,然后执行test程序。
当链接器将Program.o链接成可执行文件时,这时候链接器必须确定目标文件中所引用的func函数的性质,如果是一个定义于其它静态目标文件中的函数,那么链接器将会按照静态链接的规则,将Program.o的func函数地址进行重定位,如果func是一个定义在某个动态链接共享对象中的函数,那么链接器将会将这个符号的引用标记为一个动态链接的符号,不对它进行地址重定位,将这个过程留在装载时再进行。
动态链接有两种方式:装载时重定位和地址无关代码技术。
装载时重定位:
在链接时对所有绝对地址的引用不作重定位,而把这一步推迟到装载时完成,也叫基址重置,每个指令和数据相当于模块装载地址是固定的,系统会分配足够大的空间给装载模块,当装载地址确定后,那指令和数据地址自然也就确定了。
然而动态链接模块被装载映射到虚拟空间,指令被重定位后对于每个进程来讲是不同的,没有办法做到同一份指令被多个进程共享,所以指令对不同的进程来说有不同的副本,还是空间浪费,怎么解决这个问题?使用fPIC方法。
地址无关代码:
指令部分无法在多个进程之间共享,不能节省内存,所以引入了地址无关代码的技术。我们平时编程过程中可能都见过-fPIC的编译选项,这个就代表使用了地址无关代码技术来实现真正的动态链接。
基本思想就是使用GOT(全局偏移表),这是一个指向变量或函数地址的指针数组,当指令要访问变量或者调用函数时,会去GOT中找到相应的地址进行间接跳转访问,每个变量或函数都对应一个地址,链接器在装载模块的时候会查找每个变量和函数的地址,然后填充GOT中的各个项,确保每个指针指向的地址正确。GOT放在数据段,所以它可以在模块装载时被修改,并且每个进程都可以有独立的副本,相互不受影响。
tips
-fpic和-fPIC的区别:它们都是地址无关代码技术,-fpic产生的代码相对较小较快,但是在某些平台会有些限制,所以大多数情况下都是用-fPIC来产生地址无关代码。
-fPIC和-fPIE的区别:一个作用于共享对象,一个作用于可执行文件,一个以地址无关方式编译的可执行文件被称作地址无关可执行文件。
-fpie和-fPIE的区别:类似于-fpic和-fPIC的区别
在程序刚启动时动态链接器会寻找并装载所需要的共享对象,然后进行符号地址寻址重定位等工作,这些工作会减慢程序的启动速度,如果解决?
使用PLT延迟绑定技术,这里会单独有一个叫.PLT的段,ELF将 GOT拆分成两个表.GOT和.GOT.PLT,其中.GOT用来保存全局变量的引用地址,.GOT.PLT用来保存外部函数的地址,每个外部函数在PLT中都有一个对应项,在初始化时不会绑定,而是在函数第一次被用到时才进行绑定,将函数真实地址与对应表项进行绑定,之后就可以进行间接跳转。
支持动态链接的系统往往都支持显式运行时链接,也叫运行时加载,让程序自己在运行时控制加载的模块,在需要时加载需要的模块,在不需要时将其卸载。这种运行时加载方式使得程序的模块组织变得很灵活,可以用来实现一些诸如插件、驱动等功能。
通过这四个API可以进行显式运行时链接:
dlopen():打开动态链接库 dlsym():查找符号 dlerror():错误处理 dlclose():关闭动态链接库
参考这段使用代码:
#include #include int main() { void *handle; void (*f)(int); char *error; handle = dlopen("./lib.so", RTLD_NOW); if (handle == NULL) { printf("handle null \n"); return -1; } f = dlsym(handle, "func"); do { if ((error = dlerror()) != NULL) { printf("error\n"); break; } f(100); } while (0); dlclose(handle); return 0; }
编译运行:
$ gcc -o test program.c -ldl $ ./test func 100
为什么要进行动态链接?
为了解决静态链接浪费空间和更新困难的缺点。
動的リンクするにはどうすればよいですか?
ロード時の再配置とアドレスに依存しないコード テクノロジ。
アドレス非依存コード技術の原則?
GOTセグメントを介した間接ジャンプを実現します。
遅延読み込みテクノロジの原則?
外部関数シンボルの遅延バインディングと PLT セグメントを介した間接ジャンプを実装します。
明示的なランタイム リンクを行うにはどうすればよいですか?
ヘッダー ファイル内の 4 つの関数を使用すると、コードは上記のようになります。
以上がLinuxにおけるダイナミックリンクとスタティックリンクの本来の意味は何でしょうか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。