Linux中pkg-config的使用
1. pkg-config簡單介紹
pkg-config在編譯應(yīng)用程序和庫的時候作為一個工具來使用。例如你在命令行通過如下命令編譯程序時:
# gcc -o test test.c `pkg-config --libs --cflags glib-2.0`pkg-config可以幫助你插入正確的編譯選項,而不需要你通過硬編碼的方式來找到glib(或其他庫)。
--cflags一般用于指定頭文件,--libs一般用于指定庫文件。
大家應(yīng)該都知道一般用第三方庫的時候,就少不了要使用到第三方的頭文件和庫文件。我們在編譯、鏈接的時候,必須要指定這些頭文件和庫文件的位置。對于一個比較大的第三方庫,其頭文件和庫文件的數(shù)量是比較多的,如果我們一個個手動地寫,那將是相當(dāng)?shù)穆闊┑摹R虼?#xff0c;pkg-config就應(yīng)運而生了。pkg-config能夠把這些頭文件和庫文件的位置指出來,給編譯器使用。pkg-config主要提供了下面幾個功能:
- 檢查庫的版本號。 如果所需要的庫的版本不滿足要求,它會打印出錯誤信息,避免鏈接錯誤版本的庫文件
- 獲得編譯預(yù)處理參數(shù),如宏定義、頭文件的位置
- 獲得鏈接參數(shù),如庫及依賴的其他庫的位置,文件名及其他一些鏈接參數(shù)
- 自動加入所依賴的其他庫的設(shè)置
pkg-config命令的基本用法如下:
# pkg-config <options> <library-name>例如,我們可以通過如下命令來查看當(dāng)前安裝了哪些庫:
[root@localhost pkgconfig]# pkg-config --list-all zlib zlib - zlib compression library gio-unix-2.0 GIO unix specific APIs - unix specific headers for glib I/O library inputproto InputProto - Input extension headers cairo-xcb cairo-xcb - XCB surface backend for cairo graphics library gio-2.0 GIO - glib I/O library //后續(xù)省略2. 配置環(huán)境變量
事實上,pkg-config只是一個工具,所以不是你安裝了一個第三方庫,pkg-config就能知道第三方庫的頭文件和庫文件的位置的。為了讓pkg-config可以得到一個庫的信息,就要求庫的提供者提供一個.pc文件。默認(rèn)情況下,比如執(zhí)行如下命令:
# pkg-config --libs --cflags glib-2.0pkg-config會到/usr/lib/pkconfig/目錄下去尋找glib-2.0.pc文件。也就是說在此目錄下的.pc文件,pkg-config是可以自動找到的。然而假如我們安裝了一個庫,其生成的.pc文件并不在這個默認(rèn)目錄中的話,pkg-config就找不到了。此時我們需要通過PKG_CONFIG_PATH環(huán)境變量來指定pkg-config還應(yīng)該在哪些地方去尋找.pc文件。
我們可以通過如下命令來設(shè)置PKG_CONFIG_PATH環(huán)境變量:
# export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig/這樣pkg-config就會在/usr/local/lib/pkgconfig/目錄下尋找.pc文件了。我們在文章開頭提到的找不到Leptonica 1.74.4的原因也正是因為其找不到lept.pc,因此我們只需要將對應(yīng)的目錄設(shè)置到PKG_CONFIG_PATH環(huán)境變量中即可。
另外還需要注意的是,上述環(huán)境變量的設(shè)置只對當(dāng)前的終端窗口有效。為了讓其永久生效,我們可以將上述命令寫入到/etc/bash.bashrc等文件中,以方便后續(xù)使用。
3. pkg-config與LD_LIBRARY_PATH
pkg-config與LD_LIBRARY_PATH在使用時有些類似,都可以幫助找到對應(yīng)的庫(靜態(tài)庫和共享庫)。這里我們重點介紹一下它們兩者的區(qū)別。我們知道一個程序從源代碼,然后編譯連接,最后再執(zhí)行這一基本過程。這里我們列出pkg-config與LD_LIBRARY_PATH的主要工作階段:
- pkg-config: 編譯時、 鏈接時
- LD_LIBRARY_PATH: 鏈接時、 運行時
pkg-config主要是在編譯時會用到其來查找對應(yīng)的頭文件、鏈接庫等;而LD_LIBRARY_PATH環(huán)境變量則在 鏈接時 和 運行時 會用到。程序編譯出來之后,在程序加載執(zhí)行時也會通過LD_LIBRARY_PATH環(huán)境變量來查詢所需要的庫文件。
下面我們來講述一下LD_LIBRARY_PATH及l(fā)dconfig命令:
庫文件在鏈接(靜態(tài)庫和共享庫)和運行(僅限于使用共享庫的程序)時被使用,其搜索路徑是在系統(tǒng)中進(jìn)行設(shè)置的。一般Linux系統(tǒng)把/lib和/usr/lib這兩個目錄作為默認(rèn)的庫搜索路徑,所以使用這兩個目錄中的庫時不需要進(jìn)行設(shè)置搜索路徑即可直接使用。對于處于默認(rèn)庫搜索路徑之外的庫,需要將庫的位置添加到庫的搜索路徑之中。設(shè)置庫文件的搜索路徑有下列兩種方式,可任選其中一種使用:
- 在環(huán)境變量LD_LIBRARY_PATH中指明庫的搜索路徑
- 在/etc/ld.so.conf文件中添加庫的搜索路徑
將自己可能存放庫文件的路徑都加入到/etc/ld.so.conf中是明智的選擇。添加方法也及其簡單,將庫文件的絕對路徑直接寫進(jìn)去就OK了,一行一個。比如:
/usr/X11R6/lib /usr/local/lib /opt/lib需要注意的是:第二種搜索路徑的設(shè)置方式對于程序鏈接時的庫(包括共享庫和靜態(tài)庫)的定位已經(jīng)足夠了。但是對于使用了共享庫的程序的執(zhí)行還是不夠的,這是因為為了加快程序執(zhí)行時對共享庫的定位速度,避免使用搜索路徑查找共享庫的低效率,所以是直接讀取庫列表文件/etc/ld.so.cache的方式從中進(jìn)行搜索。/etc/ld.so.cache是一個非文本的數(shù)據(jù)文件,不能直接編輯,它是根據(jù)/etc/ld.so.conf中設(shè)置的搜索路徑由/sbin/ldconfig命令將這些搜索路徑下的共享庫文件集中在一起而生成的(ldconfig命令要以root權(quán)限執(zhí)行)。因此為了保證程序執(zhí)行時對庫的定位,在/etc/ld.so.conf中進(jìn)行了庫搜索路徑的設(shè)置之后,還必須要運行/sbin/ldconfig命令更新/etc/ld.so.cache文件之后才可以。
ldconfig,簡單的說,它的作用就是將/etc/ld.so.conf列出的路徑下的庫文件緩存到/etc/ld.so.cache以供使用。因此當(dāng)安裝完一些庫文件(例如剛安裝好glib),或者修改ld.so.conf增加新的庫路徑之后,需要運行一下/sbin/ldconfig使所有的庫文件都被緩存到ld.so.cache中。如果沒有這樣做,即使庫文件明明就在/usr/lib下的,也是不會被使用的,結(jié)果在編譯過程中報錯。
在程序鏈接時,對于庫文件(靜態(tài)庫和共享庫)的搜索路徑,除了上面的設(shè)置方式之外,還可以通過-L參數(shù)顯示指定。因為用-L設(shè)置的路徑將被優(yōu)先搜索,所以在鏈接的時候通常都會以這種方式直接指定要鏈接的庫的路徑。
前面已經(jīng)說明過了,庫搜索路徑的設(shè)置有兩種方式:在環(huán)境變量 LD_LIBRARY_PATH 中設(shè)置以及在 /etc/ld.so.conf 文件中設(shè)置。其中,第二種設(shè)置方式需要 root 權(quán)限,以改變 /etc/ld.so.conf 文件并執(zhí)行 /sbin/ldconfig 命令。而且,當(dāng)系統(tǒng)重新啟動后,所有的基于 GTK2 的程序在運行時都將使用新安裝的 GTK+ 庫。不幸的是,由于 GTK+ 版本的改變,這有時會給應(yīng)用程序帶來兼容性的問題,造成某些程序運行不正常。為了避免出現(xiàn)上面的這些情況,在 GTK+ 及其依賴庫的安裝過程中對于庫的搜索路徑的設(shè)置將采用第一種方式進(jìn)行。這種設(shè)置方式不需要 root 權(quán)限,設(shè)置也簡單:
# export LD_LIBRARY_PATH=/opt/gtk/lib:$LD_LIBRARY_PATH# echo $LD_LIBRARY_PATH4. pc文件書寫規(guī)范
這里我們首先來看一個例子:
[root@localhost pkgconfig]# cat libevent.pc #libevent pkg-config source fileprefix=/usr/local exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/includeName: libevent Description: libevent is an asynchronous notification event loop library Version: 2.0.22-stable Requires: Conflicts: Libs: -L${libdir} -levent Libs.private: Cflags: -I${includedir}這是libevent庫的一個真實的例子。下面我們簡單描述一下pc文件中的用到的一些關(guān)鍵詞:
- Name: 一個針對library或package的便于人閱讀的名稱。這個名稱可以是任意的,它并不會影響到pkg-config的使用,pkg-config是采用pc文件名的方式來工作的。
- Description: 對package的簡短描述
- URL: 人們可以通過該URL地址來獲取package的更多信息或者package的下載地址
- Version: 指定package版本號的字符串
- Requires: 本庫所依賴的其他庫文件。所依賴的庫文件的版本號可以通過使用如下比較操作符指定:=,<,>,<=,>=
- Requires.private: 本庫所依賴的一些私有庫文件,但是這些私有庫文件并不需要暴露給應(yīng)用程序。這些私有庫文件的版本指定方式與Requires中描述的類似。
- Conflicts: 是一個可選字段,其主要用于描述與本package所沖突的其他package。版本號的描述也與Requires中的描述類似。本字段也可以取值為同一個package的多個不同版本實例。例如: Conflicts: bar < 1.2.3, bar >= 1.3.0
- Cflags: 編譯器編譯本package時所指定的編譯選項,和其他并不支持pkg-config的library的一些編譯選項值。假如所需要的library支持pkg-config,則它們應(yīng)該被添加到Requires或者Requires.private中
- Libs: 鏈接本庫時所需要的一些鏈接選項,和其他一些并不支持pkg-config的library的鏈接選項值。與Cflags類似
- Libs.private: 本庫所需要的一些私有庫的鏈接選項。
5. 示例
如下我們給出一個使用pkg-config的程序例子(test_event.cpp):
#include <iostream> #include <event.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <string.h> #include <fcntl.h> using namespace std; struct event_base* main_base; static const char MESSAGE[] ="Hello, World!\n"; void accept_handle(const int sfd, const short event, void *arg) { cout<<"accept handle"<<endl; struct sockaddr_in addr; socklen_t addrlen = sizeof(addr); int fd = accept(sfd, (struct sockaddr *) &addr, &addrlen); //處理連接 struct bufferevent* buf_ev; buf_ev = bufferevent_new(fd, NULL, NULL, NULL, NULL); buf_ev->wm_read.high = 4096; cout<<"event write"<<endl; bufferevent_write(buf_ev, MESSAGE, strlen(MESSAGE)); } int main() { cout<<"hello man!"<<endl; // 1. 初始化EVENT main_base = event_init(); if(main_base) cout<<"init event ok!"<<endl; // 2. 初始化SOCKET int sListen; // Create listening socket sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Bind struct sockaddr_in server_addr; bzero(&server_addr,sizeof(struct sockaddr_in)); server_addr.sin_family=AF_INET; server_addr.sin_addr.s_addr=htonl(INADDR_ANY); int portnumber = 8080; server_addr.sin_port = htons(portnumber); /* 捆綁sockfd描述符 */ if(bind(sListen,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1) { cout<<"error!"<<endl; return -1; } // Listen ::listen(sListen, 3); cout<<"Server is listening!\n"<<endl; /*將描述符設(shè)置為非阻塞*/ int flags = ::fcntl(sListen, F_GETFL); flags |= O_NONBLOCK; fcntl(sListen, F_SETFL, flags); // 3. 創(chuàng)建EVENT 事件 struct event ev; event_set(&ev, sListen, EV_READ | EV_PERSIST, accept_handle, (void *)&ev); // 4. 事件添加與刪除 event_add(&ev, NULL); // 5. 進(jìn)入事件循環(huán) event_base_loop(main_base, 0); cout<<"over!"<<endl; }執(zhí)行如下命令編譯程序:
# gcc -o test_event test_event.cpp -lstdc++ `pkg-config --cflags --libs libevent`運行程序:
[root@localhost test-src]# ./test_event hello man! init event ok! Server is listening!開啟另外一個終端,采用nc命令連接test_event服務(wù)端程序:
[root@localhost ~]# nc 127.0.0.1 8080 Hello, World!可以看到運行成功。
6. Linux下鏈接庫的路徑順序
6.1 運行時鏈接庫的搜索順序
Linux程序在運行時對動態(tài)鏈接庫的搜索順序如下:
1) 在編譯目標(biāo)代碼時所傳遞的動態(tài)庫搜索路徑(注意,這里指的是通過-Wl,rpath=<path1>:<path2>或-R選項傳遞的運行時動態(tài)庫搜索路徑,而不是通過-L選項傳遞的)
例如:
# gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/lib test.c或者 # gcc -Wl,-rpath=/home/arc/test:/lib/:/usr/lib/:/usr/local/lib test.c2) 環(huán)境變量LD_LIBRARY_PATH指定的動態(tài)庫搜索路徑;
3) 配置文件/etc/ld.so.conf中所指定的動態(tài)庫搜索路徑(更改/etc/ld.so.conf之后,一定要執(zhí)行命令ldconfig,該命令會將/etc/ld.so.conf文件中所有路徑下的庫載入內(nèi)存);
4) 默認(rèn)的動態(tài)庫搜索路徑/lib;
5) 默認(rèn)的動態(tài)庫搜索路徑/usr/lib;
6.2 編譯時與運行時動態(tài)庫查找的比較
下面是對編譯時庫的查找與運行時庫的查找做一個簡單的比較:
1) 編譯時查找的是靜態(tài)庫或動態(tài)庫, 而運行時,查找的是動態(tài)庫;
2) 編譯時可以用-L指定查找路徑,或者用環(huán)境變量LIBRARY_PATH, 而運行時可以用-Wl,rpath或者-R選項,或者修改/etc/ld.so.conf,或者設(shè)置環(huán)境變量LD_LIBRARY_PATH;
3) 編譯時用的鏈接器是ld,而運行時用的鏈接器是/lib/ld-linux.so.2
4) 編譯時與運行時都會查找默認(rèn)路徑/lib、/usr/lib
5) 編譯時還有一個默認(rèn)路徑/usr/local/lib,而運行時不會默認(rèn)查找該路徑;
說明: -Wl,rpath選項雖然是在編譯時傳遞的,但是其實是工作在運行時。其本身其實也不算是gcc的一個選項,而是ld的選項,gcc只不過是一個包裝器而已。我們可以執(zhí)行man ld來進(jìn)一步了解相關(guān)信息6.3 補充:gcc使用-Wl,-rpath
1)?-Wl,-rpath
加上-Wl,-rpath選項的作用就是指定程序運行時的庫搜索目錄,是一個鏈接選項,生效于設(shè)置的環(huán)境變量之前(LD_LIBRARY_PATH)。下面我們通過一個例子來說明:
// add.h int add(int i, int j);// add.c #include "add.h"int add(int i, int j) {return i + j; }// main.c #include <stdio.h> #include <stdlib.h> #include "add.h"int main(int argc, char *argv[]) {printf("1 + 2 = %d\n", add(1, 2));return 0; }add.h和add.c用于生成一個so庫,實現(xiàn)了一個簡單的加法,main.c中引用共享庫計算1 + 2:
# 編譯共享庫 gcc add.c -fPIC -shared -o libadd.so # 編譯主程序 gcc main.o -L. -ladd -o app編譯好后運行依賴庫:
# ldd app linux-vdso.so.1 (0x00007ffeb23ab000) libadd.so => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007febb7dd0000) /lib64/ld-linux-x86-64.so.2 (0x00007febb83d0000 # ./app ./app: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory可以看到,?libadd.so這個庫沒有找到,程序也無法運行,要運行它必須要把當(dāng)前目錄添加到環(huán)境變量或者搜索路徑中去。但是如果在鏈接時加上-Wl,rpath選項之后:
# gcc -c -o main.o main.c # gcc -Wl,-rpath=`pwd` main.o -L. -ladd -o app # ldd app linux-vdso.so.1 (0x00007fff8f4e3000) libadd.so => /data/code/c/1-sys/solib/libadd.so (0x00007faef8428000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007faef8030000) /lib64/ld-linux-x86-64.so.2 (0x00007faef8838000) # ./app 1 + 2 = 3依賴庫的查找路徑就找到了,程序能正常運行。
下面我們再來看一下生成的可執(zhí)行文件app,執(zhí)行如下命令:
# readelf app -dDynamic section at offset 0xe08 contains 26 entries:Tag Type Name/Value0x0000000000000001 (NEEDED) Shared library: [libadd.so]0x0000000000000001 (NEEDED) Shared library: [libc.so.6]0x000000000000000f (RPATH) Library rpath: [/root/test]0x000000000000000c (INIT) 0x4005780x000000000000000d (FINI) 0x4007840x0000000000000019 (INIT_ARRAY) 0x600df00x000000000000001b (INIT_ARRAYSZ) 8 (bytes)0x000000000000001a (FINI_ARRAY) 0x600df80x000000000000001c (FINI_ARRAYSZ) 8 (bytes)0x000000006ffffef5 (GNU_HASH) 0x4002980x0000000000000005 (STRTAB) 0x4004080x0000000000000006 (SYMTAB) 0x4002d00x000000000000000a (STRSZ) 189 (bytes)0x000000000000000b (SYMENT) 24 (bytes)0x0000000000000015 (DEBUG) 0x00x0000000000000003 (PLTGOT) 0x6010000x0000000000000002 (PLTRELSZ) 96 (bytes)0x0000000000000014 (PLTREL) RELA0x0000000000000017 (JMPREL) 0x4005180x0000000000000007 (RELA) 0x4005000x0000000000000008 (RELASZ) 24 (bytes)0x0000000000000009 (RELAENT) 24 (bytes)0x000000006ffffffe (VERNEED) 0x4004e00x000000006fffffff (VERNEEDNUM) 10x000000006ffffff0 (VERSYM) 0x4004c60x0000000000000000 (NULL) 0x0可以看到是在編譯后的程序中包含了庫的搜索路徑。
2)?-Wl,rpath-link
-Wl,rpath-link是設(shè)置編譯鏈接時候的順序,例如app運行依賴libadd.so,但是libadd.so又依賴libadd_ex.so,rpath-link就是指定libadd_ex.so的路徑。和-Wl,rpath相比工作的時間不同,一個在鏈接期間,一個在運行期間。
7. gcc編譯頭文件查找路徑
關(guān)于gcc編譯時的查找路徑,GCC The C Preprocessor: Search Path描述的比較清楚,我在這里將其復(fù)制出來:
By default, the preprocessor looks for header files included by the quote form of the directive #include “file” first relative to the directory of the current file, and then in a preconfigured list of standard system directories. For example, if /usr/include/sys/stat.h contains #include “types.h”, GCC looks for types.h first in /usr/include/sys, then in its usual search path.
For the angle-bracket form #include?, the preprocessor’s default behavior is to look only in the standard system directories. The exact search directory list depends on the target system, how GCC is configured, and where it is installed. You can find the default search directory list for your version of CPP by invoking it with the -v option. For example,
cpp -v /dev/null -o /dev/null There are a number of command-line options you can use to add additional directories to the search path. The most commonly-used option is -Idir, which causes dir to be searched after the current directory (for the quote form of the directive) and ahead of the standard system directories. You can specify multiple -I options on the command line, in which case the directories are searched in left-to-right order.
If you need separate control over the search paths for the quote and angle-bracket forms of the ‘#include’ directive, you can use the -iquote and/or -isystem options instead of -I. See Invocation, for a detailed description of these options, as well as others that are less generally useful.
If you specify other options on the command line, such as -I, that affect where the preprocessor searches for header files, the directory list printed by the -v option reflects the actual search path used by the preprocessor.
Note that you can also prevent the preprocessor from searching any of the default system header directories with the -nostdinc option. This is useful when you are compiling an operating system kernel or some other program that does not use the standard C library facilities, or the standard C library itself.
除此之外,我們還可以通過相應(yīng)的環(huán)境變量來指定頭文件的搜索路徑:
export C_INCLUDE_PATH=XXXX:$C_INCLUDE_PATH export CPLUS_INCLUDE_PATH=XXX:$CPLUS_INCLUDE_PATH可以將以上代碼添加到/etc/profile末尾。
?
[參看]:
pkg-config官網(wǎng)
ldconfig命令
PKG_CONFIG_PATH變量 與 ld.so.conf 文件
Linux下運行時鏈接庫的路徑順序
gcc使用-Wl,-rpath解決so庫版本沖突
Linux 中C/C++ search path(頭文件搜索路徑)
總結(jié)
以上是生活随笔為你收集整理的Linux中pkg-config的使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux监听火狐浏览器关闭,火狐浏览器
- 下一篇: linux DDos病毒查杀过程记录