zoukankan      html  css  js  c++  java
  • 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位共享库。

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

    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
    ", 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.
    " );
    		return 0;
        }
    
        test =  ( void(*)( int ) )dlsym( handle, "test" );
        if ( test == NULL )
        {
            printf( "%s: dlsym: '%s'
    ", "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!
    " );  
        return 0;  
    }  
    
    static void __exit hello_exit( void )
    { 
        printk(KERN_ALERT "Goodbye!
    ");  
    }  
    
    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生成的依赖性文件,从预定义目录树的一套模块中自动载入相关模块。 |

  • 相关阅读:
    poj 3243 Clever Y(BabyStep GiantStep)
    poj 2417 Discrete Logging
    poj 3481 Double Queue
    hdu 4046 Panda
    hdu 2896 病毒侵袭
    poj 1442 Black Box
    hdu 2815 Mod Tree
    hdu 3065 病毒侵袭持续中
    hdu 1576 A/B
    所有控件
  • 原文地址:https://www.cnblogs.com/ddk3000/p/5051115.html
Copyright © 2011-2022 走看看