1. 程式人生 > >Linux:32/64位程式(應用程式、共享庫、核心模組)

Linux:32/64位程式(應用程式、共享庫、核心模組)

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

1. 應用程式

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

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

在64位系統上,gcc預設編譯成64位程式,但可以編譯32位程式,需要安裝32位庫。

安裝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.c

2. 共享庫(so)

要點:
1. 可以使用gcc的-m32和-m64選擇編譯32位或64位共享庫(so)。
2. 64位應用程式只能呼叫64位共享庫,32位應用程式只能呼叫32位共享庫。

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 ]

選項 說明
-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位共享庫。

解決方法:

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

3 核心模組(ko)

要點:
1. 編譯核心模組時,根據使用的核心標頭檔案,決定生成的是32位或64位核心模組ko。
2. 核心模組ko的執行不僅要求對應的Linux核心位數正確,而且要求Linux核心版本與編譯核心模組時使用的核心標頭檔案版本一致。

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位由下面決定:1、使用的核心原始碼目錄是32位或64位;2、編譯時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生成的依賴性檔案,從預定義目錄樹的一套模組中自動載入相關模組。