摘要:
Linux系统区分32/64位,相应地,应用程序、共享库和内核模块也区分32/64位。
本文以Ubuntu系统为例,介绍如何编译和使用32/64位的应用程序、共享库和内核模块。html
要点:linux
在64位系统上,gcc默认编译成64位程序,但能够编译32位程序,须要安装32位库。shell
安装32位库 :sudo apt-get install lib32readline-gplv2-dev
编译32位程序:gcc -m32 t1.c编程
在32位系统上,gcc默认编译32位程序,但能够编译64位程序,须要安装64位库。数据结构
安装64位库:sudo apt-get install lib64readline-gplv2-dev
编译64位程序:gcc -m64 t1.capp
要点:函数
参考:http://blog.csdn.net/wsl888444/article/details/8289056ui
// 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; }
gcc test.c -fPIC -shared -o libtest.so [ -m32 | -m64 ].net
选项 | 说明 |
---|---|
-fPIC | 表示编译为位置独立的代码。若是不用此选项,编译后的代码是位置相关的,则动态载入时是经过代码拷贝的方式来知足不一样进程的须要,不能达到代码段共享的目的。 |
-shared | 表示编译成共享库 |
-m32 或 -m64 | 选择编译32位或64位共享库 |
// 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
执行程序:
// 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 );
问题: 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
解决方法:
要点:
参考:http://blog.csdn.net/gavin_dinggengjia/article/details/6307080
内核模块的全称是 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() 函数来告诉内核“我来了”和“我走了”。
内核模块和内核都在内核空间运行,内核模块编程在必定意义上说就是内核编程。
由于内核版本的每次变化,其中的某些函数名也会相应地发生变化,所以内核模块编程与内核版本密切相关。
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! |
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生成的依赖性文件,从预约义目录树的一套模块中自动载入相关模块。 |