1. 程式人生 > >Linux 靜態庫與共享庫的使用

Linux 靜態庫與共享庫的使用

申明: 正如題如示,本篇講的是Linux下是靜態庫與共享庫,而Window下的動態連結庫詳細情況可見這篇文章:windows動態連結庫 DLL 淺析。雖然原理,思想差不多,但是細節卻各有不同。

一、靜態庫

1、概念:靜態庫指將所有相關的目標檔案打包成為一個單獨的檔案-即靜態庫檔案,以.a結尾。靜態庫可作為連結器的輸入,連結器會將程式中使用的到函式的程式碼從庫檔案中拷貝到應用程式中。一旦連結完成,在執行程式的時候就不需要靜態庫了 注1:由於每個使用靜態庫的應用程式都需要拷貝所用函式的程式碼,所以靜態連結的檔案會比較大。 注2:在Unix系統中,靜態庫以一種稱為存檔(archive)的特殊檔案格式存放在磁碟中。存檔檔案
是一組連線起來的可重定位目標檔案的集合,有一個頭部用來描述每個成員目標檔案的大小和位置(存檔檔名由字尾.a標識)。 2、建立與應用 假設我們想在一個叫做libvector.a的靜態庫中提供以下向量函式:
 // addvec.c
void addvec(int* x, int* y, int*z, int n)
{
     int i=0;
     for(; i< n;++i)
          z[i] = x[i] + y[i];
}

// multvec.c
void multvec(int*x, int* y, int*  z, int n)
{
     int i = 0;
     for(; i < n; ++i)
          z[i] = x[i] * y[i];
}


使用AR工具建立靜態庫檔案:
為了使用這個庫,編寫一個應用(其呼叫addvec庫中的函式):
/* main2.c */
#include <stdio.h>

int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2]={0};

int main()
{
    addvec(x, y, z, 2);
    printf("z = [%d %d]\n", z[0], z[1]);
    return 0;
}


編譯-連結-執行程式:
注1-static 引數告訴編譯器驅動程式,連結器應該構建一個完全的可執行目標檔案,它可以載入到儲存器並執行,在載入時無需進一步的連結 -即一次性靜態連結完畢,不允許存在動態連結
注2:當連結器執行時,它判定addvec.o定義的addvec符號是被main2.o引用的,所以它拷貝addvec.o到可執行檔案。因為程式中沒有引用任何由multvec.o定義的符號,所以連結器就不會拷貝這個模組到可執行檔案。同時,連結器還會拷貝libc.a中的pirintf.o模組,以及許多C執行時系統中的其他模組。連結器完整的行為可如下圖所示:
二、共享庫 1、概念:共享庫是一個目標模組(以.so字尾表示),在執行時,可以載入到任意的儲存器地址,並和一個在儲存器中的程式連結起來,這個過程稱為動態連結,是由一個叫做動態連結器的程式來執行的。 2、分類根據載入和連結共享庫的時機又可分為:A)應用程式自身載入時動態連結和載入共享庫;B)應用程式執行過程中動態連結和載入共享庫兩種情況。 2-A:應用程式自身載入時動態連結和載入共享庫 2-A.1 基本思路是:當建立可執行檔案時,靜態執行一些連結(共享庫的重定位和符號表資訊,而非程式碼和資料),然後在應用程式載入時,動態完成連結過程。 2-A.2 建立與應用 建立類似於靜態庫的建立,假設我們現在想在一個叫做libvector.so的共享庫庫中提供以下addvec和multvec函式: 下面使用-shared選項來指示連結器建立一個共享的目標檔案(即共享庫),連結並執行程式:
注1-fPIC選項指示編譯器生成與位置無關的程式碼 其動態連結過程可如下圖所示:
注2:在可執行檔案p2中沒有拷貝任何libvector.so真正的程式碼和資料節,而是由連結器拷貝了一些重定位和符號表資訊,它們使得執行時動態連結器可以解析libvector.so中程式碼和資料的引用,重定位完成連結任務。其中需要重定位的有:
  • 1)重定位libc.so的文字和資料到某個儲存器段;
  • 2)重定位libvector.so的文字和資料到另一個儲存器段;
  • 3)重定位p2中所有對libc.so和libvector.so定義的符號的引用。
最後連結器將控制傳遞給應用程式。從這個時刻開始,共享庫的位置就固定了,並在在程式的執行過程中都不會再改變2-B:應用程式執行過程中動態連結和載入共享庫 2-B.1 概念:與A情況不同,此情況下:應用程式在執行過程中要求動態連結器載入和連結任意共享庫,而無需編譯時連結那些庫到應用中。 2-B.2 應用例項 Linux系統為應用程式在執行過程中載入和連結共享庫提供了一組API:
#include<dlfcn.h>

/* 載入和連結共享庫 filename
	filename:共享庫的名字
	flag有:RTLD_LAZY, RTLD_NOW,二者均可以和RTLD_GLOBAL表示取或
*/
void *dlopen(const char *filename, int flag); // 若成功則返回執行控制代碼的指標,否則返回NULL

/*根據共享庫操作控制代碼與符號,返回符號對應的地址
	handle:共享庫操作控制代碼
	symbol:需要引用的符號名字
*/
void *dlsym(void *handle, char *symbol); // 若成功則返回執行符號的指標(即地址),若出錯則返回NULL

/* 如果沒有程式正在使用這個共享庫,解除安裝該共享庫 */
int dlclose(void *handle); // 若解除安裝成功,則返回0,否則返回-1

/* 捕捉最近發生的錯誤 */
const char *dlerror(void); // 若前面對dlopen,dlsym或dlclose呼叫失敗,則返回錯誤訊息,否則返回NULL


例子

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2] ={0};

int main()
{
    void *handle;
    void (*addvec)(int *, int *, int *,int);
    char *error;

    handle = dlopen("./libvector.so", RTLD_LAZY);
    if(!handle){
        fprintf(stderr, "%s\n", dlerror());
        exit(1);
    }

    addvec = dlsym(handle, "addvec");
    if((error = dlerror()) != NULL){
        fprintf(stderr, "%s\n", dlerror());
        exit(1);
    }

    addvec(x, y, z, 2);
    printf("z = [%d %d]\n", z[0], z[1]);

    if(dlclose(handle) < 0){
        fprintf(stderr, "%s\n", dlerror());
        exit(1);
    }

    return 0;
}

執行結果:-ldl引數:表示生成的物件模組需要用到共享庫


Referebces:

1.《深入理解計算機系統》第7章:連結 P448-P479