找回密码
 注册
搜索
热搜: 回贴
微赢网络技术论坛 门户 服务器 Linux/BSD 查看内容

解决制作根文件系统遇到的共享库查找问题

2009-12-20 13:34| 发布者: admin| 查看: 26| 评论: 0|原作者: 夙瑶

在探讨Linux挂载根文件系统之后的流程时,制作了一个小的根文件系统作为分析的示例,结果因为共享库的问题,好长时间没有解决。从网上搜索了些资料,初步理解下,后续有时间再深入。


1、理解什么是目标代码,什么可执行代码,什么是库。

编译之后生成的是二进制代码就是目标代码,它是不可以直接执行的。经过链接之后生成可执行代码,也就是可执行代码实际上是目标代码,操作系统的启动代码,库代码三者的总和。当然,不是简单的融合在一起,这个过程是比较复杂的,也就是链接器的作用了。

由于目标代码,可执行代码都是操作系统相关的文件格式,所以是不兼容的。比如,Linux下不支持windows的exe可执行文件格式,windows也不支持Linux下的ELF可执行文件格式。


库其实本质上也是可执行代码的二进制格式。库有三种使用形式:静态库、共享库、动态库。静态库供链接器使用,在链接时加载;共享库在链接时定位,在运行时
加载;动态库是共享库的另一种变化形式,也是在运行时加载,不过并非在程序运行开始时加载,而是在程序中的语句需要使用该函数时才载入。动态库可以在程序
运行期间释放动态库所占用的内存。


Linux下的库文件分为共享库和静态库两大类。它们之间的差别就在于加载时刻不同,如上所述。区分库类型的方法就是看文件后缀,通常共享库
以.so(shared object)结尾,而静态库通常以.a结尾(archive)。在终端缺省情况下,共享库通常为绿色,而静态库为黑色。

2、库的命名规则

GNU的库的使用必须遵守Library GNU Public License(LGPL许可协议)。该协议和GNU许可协议略有不同,开发人员可以免费使用GNU库进行软件开发,但必须保证向用户提供所用的库函数的源代码。

系统中可用的库默认存在在/usr/lib和/lib目录中。库文件名由前缀lib和库名以及后缀组成。根据库的类型不同,后缀名也不一样。共享库的后缀名由.so和版本号组成,静态库的后缀名为.a。采用旧的a.out格式的共享库的后缀名为.sa.

libname.so.major.minor
libname.a

这里的name可以是任何字符串,用来唯一标识某个库。该字符串可以是一个单字、几个字符,甚至是一个字母。数学共享库的库名为libm.so.5,这里的标识字符为m,版本号为5.libm.a则是静态数学库。

3、自己的一些理解

初步理解了链接器和加载器的原理,可以明白下面的规则:

静态链接 --> 静态库 --> 链接时加载
动态链接 --> 共享库 --> 链接时定位,运行时加载


所以,很明显,如果是静态链接,则自动去寻找静态库。假定你指定了静态链接,而库路径下仅仅有相应的共享库文件,则依然提示找不到。对于静态链接程序而
言,所有目标文件都集中在一起而成为可执行文件,它不需要其他的支持就可以到兼容的系统中运行。正是因为所有的都集中,所以,静态编译的文件特别大,占用
空间就大。而嵌入式系统对空间要求比较苛刻,所以最好还是使用共享库。


共享库在链接时只是定位,知道库的位置,在运行时才会载入库。这样,问题就来了,它怎么载入库呢?是通过共享库加载器来完成的。对于ELF文件,使用的共
享库加载器为ld-linux.so.2。所以,要想使用共享库,首先应该有ld-linux.so.2作为共享库加载器。那么,又产生的问题是:在运行
时如何找到共享库的位置呢?无论何时载入程序打算运行时,共享库都应该位于以下位置:

(1)环境变量LD_LIBRARY_PATH列出的所有用分号分隔的位置
(2)文件/etc/ld.so.cache中找到的库的列表,由工具ldconfig维护
(3)目录/lib
(4)目录/usr/lib

如果我想要验证最小的根文件系统,那么最合适的方案就是:/bin /dev/ /lib
/lost found,bin下只有一个sh程序,dev下只有console,lib下存放sh的依赖共享库。只是实现了一个功能,就是打开一个
shell终端程序,只是不能做任何工作。如果采用静态链接制作sh,那么lib文件夹也可以不需要。这个制作没有实际意义,但是可以理解初始的流程。

分析一下这个过程。大体的思路是,加载根文件系统,然后打开console,寻找第一个执行程序init,找到后就开始执行。具体看init/main.c中的init函数,基本初始化,完成命令行的解析后:

if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) 0)
printk(KERN_WARNING "Warning: unable to open an initial console.\n");

所以,应该在/dev下包含console设备文件,否则的话,会出现无法打开初始化终端的提示。

/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);

这里的execute_command是一个全局变量,在解析传递进内核的命令行参数时确定的。

static int __init init_setup(char *str)
{
unsigned int i;
execute_command = str;
/*
* In case LILO is going to boot us with default command line,
* it prepends "auto" before the whole cmdline which makes
* the shell think it should execute a script with such name.
* So we ignore all arguments entered _before_ init=... [MJ]
*/
for (i = 1; i MAX_INIT_ARGS; i )
argv_init = NULL;
return 1;
}
__setup("init=", init_setup);

这里的__setup是一个宏定义,比较复杂,定义在include/linux/init.h中。

#define __setup(str, fn) \
__setup_param(str, fn, fn, 0)

#define __setup_param(str, unique_id, fn, early) \
static char __setup_str_##unique_id[] __initdata = str; \
static struct obs_kernel_param __setup_##unique_id \
__attribute_used__ \
__attribute__((__section__(".init.setup"))) \
__attribute__((aligned((sizeof(long))))) \
= { __setup_str_##unique_id, fn, early }

这个利用了gcc的一些扩展用法,暂时忽略,来关注流程。就是如果你指定了init=filename,那么首先按照指定文件执行。如果没有指定,则要按照下列顺序依次执行:

run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel.");

可见,如果这四个位置都没有找到,或者找到文件,但是不可执行,那么就会产生kernel panic。而这个kernel panic是最为常见的一个错误提示了。

分析到这里,也就清晰为什么最小的根文件系统会是这样了。具体制作步骤:

(1)制作一个ramdisk映象

dd if=/dev/zero of=if.img bs=1k count=15360
mke2fs -F -v -m0 if.img

其中,of指定生成的映象的名字,bs是块的大小,count是ramdisk的大小,单位是KB。mke2fs是初始化ramdisk中的文件系统,完成后挂载后会出现lost found文件夹。

(2)添加内容

|-- new
| |-- bin
| | |-- bash
| | `-- sh -> bash
| |-- dev
| | `-- console
| |-- lib
| | |-- ld-2.1.3.so
| | |-- ld-linux.so.2 -> ld-2.1.3.so
| | |-- libc-2.1.3.so
| | |-- libc.so.6 -> libc-2.1.3.so
| | |-- libtermcap.so.2 -> libtermcap.so.2.0.8
| | `-- libtermcap.so.2.0.8
| `-- lost found


这是制作的根文件系统的最小部分。其中,bash和lib里面的共享库是从开发板的ramdisk中摘出来的。至于bash是用了那些共享库,可以用
arm-linux-readelf查找,不过需要注意的是,前面工具没有列出ld-linux.so.2,而这个共享库加载器是必需的。在host下,
可以使用ldd查看可执行文件依赖的共享库,比较全面。原来的时候,ld-linux.so.2出现了问题,结果不管怎么修改,都会出现上面提到的那个
kernel
panic,后来重新用了一个ld-linux.so.2,问题才消除。也说明,/etc/ld.so.conf和/etc/ld.so.cache并非
是必须的,在嵌入式系统中,你完全可以把需要用到的共享库放到/lib或者/usr/lib下面,这样就不需要设定LD_LIBRARY_PATH,也不
需要这两种/etc/下的配置文件了。不过应用多的情况下,为了区别,也可以使用上述两种方式添加库的查找路径。

(3)制作完成,并加载

[root@lqm fs]# umount new
[root@lqm fs]# gzip -c -v9 ramdisk > ramdisk.gz

然后加载ramdisk.gz,最终效果:

RAMDISK: Compressed image found at block 0
VFS: Mounted root (ext2 filesystem).
Freeing init memory: 84K
bash#

可见挂载根文件系统是正确的,可以识别ext2文件系统。然后打开了console,并且执行了/bin/sh。

ramdisk映象备份如下,通过这个制作和分析过程,就比较清晰加载根文件系统后的初始过程。后面的完善功能定制,就完全依赖于shell脚本的配置和执行了。需要对这个过程进行更深入的分析和了解。

screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='Click here to open new window\nCTRL Mouse wheel to zoom in/out';}" onmouseover="if(this.width>screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.style.cursor='hand'; this.alt='Click here to open new window\nCTRL Mouse wheel to zoom in/out';}" onclick="if(!this.resized) {return true;} else {window.open('http://fileicon/rar.gif');}" onmousewheel="return imgzoom(this);" alt="" />
文件:
ramdisk.rar
大小:
546KB
下载:
下载







最新评论

QQ|小黑屋|最新主题|手机版|微赢网络技术论坛 ( 苏ICP备08020429号 )

GMT+8, 2024-9-30 13:17 , Processed in 0.174479 second(s), 12 queries , Gzip On, MemCache On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

返回顶部