linux的靜態鏈接和動態鏈接程序有什么區別?
恰好之前我寫過一篇文章討論這個問題,下面摘錄一部分。
動態鏈接庫和靜態鏈接庫使用我們按照之前幾節配置好的 vim 輸入以下代碼:
// 文件名
t.c
#include <stdio.h>
int main()
{
printf("hello embedTime ");
return 0;
}
這段代碼包含了 stdio 頭文件,調用了庫函數 printf,所以編譯它肯定會使用鏈接庫。linux 系統有兩種鏈接庫,一種常常被稱為“靜態鏈接庫(static library)”,還有一種常被稱作“動態鏈接庫(shared library)”。
動態鏈接是應用非常廣泛的方式。動態鏈接庫的英文字面意思可以翻譯為“共享的庫”,的確如此,使用動態鏈接庫的程序在加載時,linux 內核會檢查程序用到的庫是否已經在內存中,如果在,則 linux 內核不再重新加載庫,直接就執行程序了。所以,多個程序可以共享一個庫,這實際上可以節約資源。
對于靜態鏈接庫來說,程序鏈接時會將其作為程序的一部分,因此最終生成的可執行程序相比于動態鏈接方式,會更大一點。
編譯上面的程序:
# gcc t.c -o shared.out這條編譯語句使用的是動態鏈接方式。為 gcc 命令附加 -static 命令,可以以靜態鏈接方式編譯程序:
# gcc t.c -static -o static.out現在我們查看一下這兩種鏈接方式生成的可執行程序大小對比:
# ls -ahltotal 888Kdrwxr-xr-x 3 root root 4.0K Dec 17 22:40 .drwxr-xr-x 8 root root 4.0K Dec 11 10:28 ..drwxr-xr-x 2 root root 4.0K Dec 17 22:39 his-rwxr-xr-x 1 root root 8.4K Dec 17 22:40 shared.out-rwxr-xr-x 1 root root 857K Dec 17 22:40 static.out-rw-r--r-- 1 root root 76 Dec 17 21:37 t.c
很容易看出,使用靜態鏈接方式生成的可執行程序,要比使用動態鏈接方式生成的可執行程序大 100 多倍。雖然幾百 KB 對于大多數 linux 主機來說不算什么,但是嵌入式系統資源一般都非常緊缺,這時再輕易使用靜態鏈接就非常奢侈了。
使用靜態鏈接也是有好處的,生成的可執行程序能夠脫離庫獨立運行,而使用動態鏈接的可執行程序則不能脫離庫獨立運行。靜態鏈接和動態鏈接的可執行程序,執行過程有哪些不同
上面討論了 linux 中程序鏈接的兩種方式,既然可執行程序體積相差這么多,那它們的執行過程也應該有所差異了?的確如此,現在我們一起來分析下。在linux中分析程序的執行過程,可以使用 strace 命令。
先分析 shared.out,我們輸入 strace ./shared.out,會發現有一大堆輸出信息:
# strace ./shared.outexecve("./shared.out", ["./shared.out"], [/* 22 vars */]) = 0brk(0) = 0x1a66000access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3fstat(3, {st_mode=S_IFREG|0644, st_size=33518, ...}) = 0mmap(NULL, 33518, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe241ff2000close(3) = 0access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3read(3, "ELF>P "..., 832) = 832fstat(3, {st_mode=S_IFREG|0755, st_size=1857312, ...}) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe241ff1000mmap(NULL, 3965632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe241a10000mprotect(0x7fe241bce000, 2097152, PROT_NONE) = 0mmap(0x7fe241dce000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1be000) = 0x7fe241dce000mmap(0x7fe241dd4000, 17088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe241dd4000close(3) = 0mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe241fef000arch_prctl(ARCH_SET_FS, 0x7fe241fef740) = 0mprotect(0x7fe241dce000, 16384, PROT_READ) = 0mprotect(0x600000, 4096, PROT_READ) = 0mprotect(0x7fe241ffb000, 4096, PROT_READ) = 0munmap(0x7fe241ff2000, 33518) = 0fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 2), ...}) = 0mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe241ffa000write(1, "hello embedTime ", 16hello embedTime) = 16exit_group(0) = ?+++ exited with 0 +++這些輸出信息即為 linux 執行程序的過程。每一個函數,都可以通過 man 命令查詢其手冊。幾個主要的過程如下:
就是加載庫到內存,再執行程序,最后調用系統調用 exit 結束程序。
現在再來看看靜態鏈接的程序 static.out,同樣使用 strace 命令查看:
# strace static.out可以看出,因為鏈接時,編譯器直接把靜態庫作為程序的一部分了,所以這里相比于動態鏈接的程序,少了很多將庫映射到內存的操作:
到這里,動態鏈接和靜態鏈接程序執行時的不同點,就清楚了。
歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。