Linux:32/64位程序(應用程序、共享庫、內核模塊)

摘要:
Linux系統區分32/64位,相應地,應用程序、共享庫和內核模塊也區分32/64位。
本文以Ubuntu系統爲例,介紹如何編譯和使用32/64位的應用程序、共享庫和內核模塊。html


1. 應用程序

要點:linux

  1. 使用gcc編譯器的-m32和-m64選項指定編譯成32位或64位應用程序,編譯時須要使用32/64位庫,所以編譯前須要安裝對應的庫。
  2. 在64位系統上,能夠執行64位和32位應用程序。在32位系統上,只能執行32位應用程序,不能執行64位應用程序。

1.1 64位系統上編譯應用程序

在64位系統上,gcc默認編譯成64位程序,但能夠編譯32位程序,須要安裝32位庫。shell

安裝32位庫 :sudo apt-get install lib32readline-gplv2-dev
編譯32位程序:gcc -m32 t1.c編程

1.2 32位系統上編譯應用程序

在32位系統上,gcc默認編譯32位程序,但能夠編譯64位程序,須要安裝64位庫。數據結構

安裝64位庫:sudo apt-get install lib64readline-gplv2-dev
編譯64位程序:gcc -m64 t1.capp


2. 共享庫(so)

要點:函數

  1. 可使用gcc的-m32和-m64選擇編譯32位或64位共享庫(so)。
  2. 64位應用程序只能調用64位共享庫,32位應用程序只能調用32位共享庫。

參考:http://blog.csdn.net/wsl888444/article/details/8289056ui

2.1 編寫共享庫

// test.h  共享庫接口文件
#ifndef _TEST_H_  
#define _TEST_H_ 
void test( int x );
#endif
// test.c  共享庫實現文件
#include <stdio.h>  
void test( int x ) 
{
    printf( "hello, I'm libtest.so %d\n", x );
    return;
}

2.2 編譯共享庫

gcc test.c -fPIC -shared -o libtest.so [ -m32 | -m64 ].net

選項 說明
-fPIC 表示編譯爲位置獨立的代碼。若是不用此選項,編譯後的代碼是位置相關的,則動態載入時是經過代碼拷貝的方式來知足不一樣進程的須要,不能達到代碼段共享的目的。
-shared 表示編譯成共享庫
-m32 或 -m64 選擇編譯32位或64位共享庫

2.3 使用共享庫

2.3.1 編譯應用程序時使用共享庫

// t1.c  應用程序
#include "test.h"  
int main( ) 
{
    test( 99 );
    return 0;
}

編譯程序:
gcc t1.c -L . -l test -o t1 [ -m32 | -m64 ]調試

選項 說明
-L 指明共享庫所在的目錄
-l 指明共享庫的名稱,該名稱是處在頭lib 和後綴.so 中的名稱。例如上面共享庫libtest.so,則參數爲 -l test 。



查看應用程序依賴的共享庫: ldd t1
查看目標文件中定義的符號: nm t1

執行程序:

  1. 建立共享庫文件的軟連接:進入/usr/lib目錄,建立到共享庫的軟連接。
    例如共享庫是/home/test/libtest.so,則命令是 ln -s /home/test/libtest.so libtest.so
  2. 執行應用程序: ./t1

2.3.2 動態加載方式使用共享庫

// t2.c  應用程序
int main( )
{  
    void  *handle = NULL; 
    void (*test)( int x ); 

    handle = dlopen( "./libtest.so", RTLD_LAZY ); 
    if ( handle == NULL )
    {
        printf( "dll loading error.\n" );
        return 0;
    }

    test =  ( void(*)( int ) )dlsym( handle, "test" );
    if ( test == NULL )
    {
        printf( "%s: dlsym: '%s'\n", "test", dlerror() );
        return 0;
    }
    
    test( 99 );  
    
    return 0;
}

編譯程序:
gcc -ldl test1.c –o test [ -m32 | -m64 ]

選項 說明
-ldl 使用共享庫相關函數需使用該參數



程序說明:
使用C++時,so的頭文件中聲明前要加 extern "C",才能正確獲取函數地址。不然,在dlsym可能產生錯誤:找不到函數(undefined symbol)。 test.h 中聲明如: extern "C" void test( int x );

2.4 混合使用32/64位應用程序和共享庫

問題: 32/64位應用程序是否能夠調用64/32位共享庫?

回答: 64位應用程序只能調用64位共享庫,32位應用程序只能調用32位共享庫。

參考:
http://cboard.cprogramming.com/linux-programming/113856-loading-32-bit-library-into-64-bit-linux-program.html
http://stackoverflow.com/questions/10039401/use-32bit-shared-library-from-64bit-application

解決方法:

  1. 方法一:編譯使應用程序和共享庫的位數相同
  2. 方法二:作一個與共享庫位數相同的中間程序,用於調用共享庫;應用程序與中間程序通訊,間接調用共享庫。參考個人另外一篇文章:《Linux:使用rpcgen實現64位程序調用32位庫函數》

3 內核模塊(ko)

要點:

  1. 編譯內核模塊時,根據使用的內核頭文件,決定生成的是32位或64位內核模塊ko。
  2. 內核模塊ko的運行不只要求對應的Linux內核位數正確,並且要求Linux內核版本與編譯內核模塊時使用的內核頭文件版本一致。

參考:http://blog.csdn.net/gavin_dinggengjia/article/details/6307080

3.1 什麼是內核模塊?

內核模塊的全稱是 Loadable Kernel Module(LKM, 動態可加載內核模塊),它是Linux內核向外部提供的一個插口。

內核模塊是具備獨立功能的程序,一般由一組函數和數據結構組成,用來實現一種文件系統、一個驅動程序,或其它內核上層的功能。

內核模塊能夠被單獨編譯,運行時被連接到內核,做爲內核的一部分在內核空間運行,這與運行在用戶空間的進程不一樣。下表比較了應用程序與內核模塊的差異。

項目 C語言應用程序 內核模塊
使用函數 libc庫 內核函數
運行空間 用戶空間 內核空間
入口函數 main() module_init()
出口函數 exit() module_exit()
編譯 gcc -c Makefile
鏈接 gcc insmod
運行 直接運行 insmod
調試 gdb kdbug、kdb、kgdb



從表中能夠看出,內核模塊不能調用 libc 庫函數,它運行在內核空間,只有超級用戶能夠對其運行。
另外,內核模塊程序必須經過 module_init() 和 module_exit() 函數來告訴內核「我來了」和「我走了」。

3.2 編寫一個簡單的內核模塊

內核模塊和內核都在內核空間運行,內核模塊編程在必定意義上說就是內核編程。
由於內核版本的每次變化,其中的某些函數名也會相應地發生變化,所以內核模塊編程與內核版本密切相關。

1. 內核模塊代碼

// hello.c  內核模塊代碼
#include "linux/init.h"       // 包含宏_init和_exit
#include "linux/kernel.h"     // 包含經常使用的內核函數
#include "linux/module.h"     // 全部模塊都要用到

static int __init hello_init( void )
{  
    printk( KERN_ALERT "Hello world!\n" );  
    return 0;  
}  

static void __exit hello_exit( void )
{ 
    printk(KERN_ALERT "Goodbye!\n");  
}  

module_init( hello_init );  
module_exit( hello_exit );  

MODULE_LICENSE( "GPL" );  
MODULE_AUTHOR( "ddk" );  
MODULE_DESCRIPTION( "hello" );
函數 說明
module_init 內核模塊初始化的入口點
module_exit 註銷由內核模塊提供的全部功能
hello_init 內核模塊初始化函數
hello_exit 內核模塊的退出清理函數,可作終止該內核模塊相關的清理工做。
printk 內核定義的函數,功能與printf相似,printk把要打印的信息輸出到終端或系統日誌。


2. Makefile

# Makefile
obj-m:=hello.o  
KERNELBUILD:=/lib/modules/$(shell uname -r)/build 
default:
    make -C $(KERNELBUILD) M=$(shell pwd) modules
clean:
    rm -rf *.o *.ko *.mod.c .*.cmd *.markers *.order *.symvers .tmp_versions
選項 說明
obj-m 決定過了內核模塊的名稱和生成的ko文件名
KERNELBUILD 編譯內核模塊須要的內核源代碼文件目錄
M 內核模塊代碼目錄
make -C ...... 編譯內核模塊。-C 將工做目錄轉到KERNELBUILD,調用該目錄下的Makefile,並向這個Makefile傳遞參數M的值是$(shell pwd) modules。



關於KERNELBUILD的說明:
(1)若是爲Linux發行版(例如Ubuntu、CentOS等)編譯內核模塊,則能夠直接從發行版目錄 /usr/src 中獲取這個目錄,通常是 /usr/src/linux-headers-* 。這個目錄區分32位和64位。
(2)若是爲Linux標準內核(從 https://www.kernel.org/pub/linux/kernel/ 獲取)編譯內核模塊,則須要先編譯Linux標準內核,而後使用 /usr/src 中的編譯後對應目錄。Linux標準內核源代碼不區分32位和64位,但編譯時區分編譯爲32位或64位。編譯和安裝Linux標準內核,請參考:http://blog.csdn.net/ddk3001/article/details/50276347
(3)編譯出的內核模塊ko是32位或64位由下面決定:一、使用的內核源代碼目錄是32位或64位;二、編譯時make命令中指定 ARCH=i386 或 ARCH=x86_64 。
(4)cat hello.ko能夠查看內核模塊要在哪一個Linux內核版本中運行。

3. 編譯和使用內核模塊

功能 命令 說明
編譯模塊 make 執行第一個目標default,生成hello.ko,這個就是咱們須要的內核模塊。
編譯清理 make clean 清理編譯產生的文件,hello.ko 也會清理掉。
插入模塊 sudo insmod ./hello.ko 用dmesg就能夠看到產生的內核信息,Hello world! 內核消息也會輸出到日誌文件/var/log/kern.log中。
卸載模塊 sudo rmmod hello 用dmesg能夠看到Goodbye!

3.3 modutils軟件包

modutils是管理內核模塊的一個軟件包,安裝後在/sbin目錄下就會有insomod、rmmod、lsmod等實用程序。一般在加載Linux內核時,modutils已經被載入。

命令 說明
insmod 調用insmod程序把須要插入的模塊以目標代碼的形式插入到內核中。在插入的時候,insmod自動調用module_init()函數運行。
rmmod 調用rmmod程序將已經插入內核的模塊從內核中移出。rmmod會自動運行module_exit()函數。
lsmod 調用lsmod程序將顯示當前系統中正在使用的模塊信息。實際上這個程序的功能就是讀取/proc/modules中的信息。
modinfo 顯示一個模塊的相關信息。
depmod 生成可載入模塊的依賴性文件,供modprobe在安裝模塊時使用。
modprobe modprobe 和 insmod 都是載入內核模塊,差異是 modprobe 可以自動處理模塊載入的依賴問題。modprobe 使用depmod生成的依賴性文件,從預約義目錄樹的一套模塊中自動載入相關模塊。
相關文章
相關標籤/搜索