• 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生成的依赖性文件,从预定义目录树的一套模块中自动载入相关模块。 |

  • 相关阅读:
    跟老婆学习软件工程一:瀑布模型
    我的手机需求
    我的BB8820手机
    WordPress中query_posts函数和WPpagenavi插件冲突的解决方法
    HomeZZ注册推介码
    分享:世界机场代码(ICAO)[带经纬度]
    Silverlight学习笔记十七BingMap(三)之地图的地区标识
    从真正的项目中学习Silverlight(MSN Entertainment项目源码下载)
    Silverlight的十几种图片展示炫酷效果(附源码)
    Silverlight水的微波凌步效果
  • 原文地址:https://www.cnblogs.com/ddk3000/p/5051115.html
Copyright © 2020-2023  润新知