手把手教你制作根文件系统.docx
《手把手教你制作根文件系统.docx》由会员分享,可在线阅读,更多相关《手把手教你制作根文件系统.docx(16页珍藏版)》请在冰豆网上搜索。
手把手教你制作根文件系统
详解制作根文件系统
一、FHS(FilesystemHierarchyStandard)标准介绍
当我们在linux下输入ls /的时候,见到的目录结构以及这些目录下的内容都大同小异,这是因为所有的linux发行版在对根文件系统布局上都遵循FHS标准的建议规定。
该标准规定了根目录下各个子目录的名称及其存放的内容:
目录名
存放的内容
/bin
必备的用户命令,例如ls、cp等
/sbin
必备的系统管理员命令,例如ifconfig、reboot等
/dev
设备文件,例如mtdblock0、tty1等
/etc
系统配置文件,包括启动文件,例如inittab等
/lib
必要的链接库,例如C链接库、内核模块
/home
普通用户主目录
/root
root用户主目录
/usr/bin
非必备的用户程序,例如find、du等
/usr/sbin
非必备的管理员程序,例如chroot、inetd等
/usr/lib
库文件
/var
守护程序和工具程序所存放的可变,例如日志文件
/proc
用来提供内核与进程信息的虚拟文件系统,由内核自动生成目录下的内容
/sys
用来提供内核与设备信息的虚拟文件系统,由内核自动生成目录下的内容
/mnt
文件系统挂接点,用于临时安装文件系统
/tmp
临时性的文件,重启后将自动清除
制作根文件系统就是要建立以上的目录,并在其中建立完整目录内容。
其过程大体包括:
∙编译/安装busybox,生成/bin、/sbin、/usr/bin、/usr/sbin目录
∙利用交叉编译工具链,构建/lib目录
∙手工构建/etc目录
∙手工构建最简化的/dev目录
∙创建其它空目录
∙配置系统自动生成/proc目录
∙利用udev构建完整的/dev目录
∙制作根文件系统的jffs2映像文件
下面就来详细介绍这个过程。
二、编译/安装busybox,生成/bin、/sbin、/usr/bin、/usr/sbin目录
这些目录下存储的主要是常用命令的二进制文件。
如果要自己编写这几百个常用命令的源程序,mygod,这简直是一个噩梦!
好在我们有嵌入式Linux系统的瑞士军刀——busybox,事情就简单很多。
1、从下载busybox-1.7.0.tar.bz2
2、tarxjvfbusybox-1.7.0.tar.bz2解包
3、修改Makefile文件
175ARCH ?
=arm
176CROSS_COMPILE ?
=arm-linux-
4、makemenuconfig配置busybox
busybox配置主要分两部分。
第一部分是BusyboxSettings,主要编译和安装busybox的一些选项。
这里主要需要配置:
1)、BuildOptions--BuildBusyBoxasastaticbinary(nosharedlibs),表示编译busybox时,是否静态链接C库。
我们选择动态链接C库。
2)、InstallationOptions--Appletslinks(assoft-links)--(X)assoft-links,表示安装busybox时,将各个命令安装为指向busybox的软链接还是硬链接。
我们选择软链接。
3)、InstallationOptions--(/work/nfs_root/fs_mini3)BusyBoxinstallationprefix,表示busybox的安装位置。
我们选择/work/nfs_root/fs_mini3
4)BusyboxLibraryTuning。
保留Commandlineediting以支持命令行编辑;保留Historysize以支持记忆历史命令;选中Tabcompletion和Usernamecompletion以支持命令自动补全
第二部分是Applets,他将busybox的支持的几百个命令分门别类。
我们只要在各个门类下选择想要的命令即可。
这里我们基本保持默认设置。
1)选中NetworkingUtilities--httpd下的Enable-uoption,以启用http服务器的功能allowstheservertorunasaspecificuser
5、编译busybox
make
6、安装busybox
makeinstall
安装完成后,可以看到在/work/nfs_root/fs_mini3目录下生成了binsbinusr/binusr/sbin目录,其下包含了我们常用的命令,这些命令都是指向bin/busybox的软链接,而busybox本身的大小不到800K:
dennis@dennis-desktop:
/work/nfs_root/fs_mini3$ls
bin linuxrc sbin usr
dennis@dennis-desktop:
/work/nfs_root/fs_mini3$ ls-lbin
total740
lrwxrwxrwx1dennisdennis 72010-04-0323:
57addgroup->busybox
lrwxrwxrwx1dennisdennis 72010-04-0323:
57adduser->busybox
lrwxrwxrwx1dennisdennis 72010-04-0323:
57ash->busybox
-rwxr-xr-x1dennisdennis7496322010-04-0323:
57busybox
lrwxrwxrwx1dennisdennis 72010-04-0323:
57cat–>busybox
而普通PC机上的ls命令就有差不多80K的大小:
dennis@dennis-desktop:
/work/nfs_root/fs_mini3$ls-l/bin/ls
-rwxr-xr-x1rootroot780042007-09-2920:
51/bin/ls
busybox以它娇小的身躯容纳了数以百计的命令代码,实在是让人佩服不已,其不愧嵌入式系统瑞士军刀之美誉。
据说,busybox的作者身患绝症,这更让人钦佩GNU开源软件的作者们。
三、利用交叉编译工具链,构建/lib目录
光有应用程序(命令)是不够的,因为应用程序本身需要使用C库的库函数,因此还必需制作forARM的C库,并将其放置于/lib目录。
mygod,要自己写C库的源代码吗?
不用!
还记得交叉编译工具链的3个组成部分吗?
交叉编译器、forARM的C库和二进制工具。
哈哈,forARM的C库是现成的,我们只需要拷贝过来就可以了。
遗憾的是:
整个C库目录下的文件总大小有26M。
而我们根文件系统所在分区不过区区16M而已,根本放不下。
怎么办呢?
dennis@dennis-desktop:
/work/nfs_root/fs_mini3$du-s--si/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib
26M /work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib
需要C库目录下所有的文件吗?
no,absolutelyno!
让我们来分析一下glibc库目录下内容的组成。
该目录下的子目录和文件共分8类:
∙目标文件,如crtn.o,用于gcc链接可执行文件
∙libtool库文件(.la),在链接库文件时这些文件会被用到,比如他们列出了当前库文件所依赖的其它库文件,程序运行时无需这些文件
∙gconv目录,里面是各种链接脚本,在编译应用程序时,他们用于指定程序的运行地址,各段的位置等
∙静态库文件(.a),例如libm.a,libc.a
∙动态库文件(.so、.so.[0-9]*)
∙动态链接库加载器ld-2.3.6.so、ld-linux.so.2
∙其它目录及文件
很显然,第1、2、3、4、7类文件和目录是不需要拷贝的。
由于动态链接的应用程序本身并不含有它所调用的C库函数的代码,因此执行时需要动态链接库加载器来为它加载相应的C库文件,所以第6类文件是需要拷贝的。
除此之外,第5类文件当然要拷贝。
但第5类文件的大小也相当大。
dennis@dennis-desktop:
/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$du-c--si*.so*
7.2M total
需要全部拷贝吗?
非也,非也!
其实,需要哪些库完全取决于要运行的应用程序使用了哪些库函数。
如果我们只制作最简单的系统,那么我们只需要运行busybox这一个应用程序即可。
通过执行
dennis@dennis-desktop:
/work/nfs_root/fs_mini3$arm-linux-readelf-abin/busybox|grep'Shared'
0x00000001(NEEDED) Sharedlibrary:
[libcrypt.so.1]
0x00000001(NEEDED) Sharedlibrary:
[libm.so.6]
0x00000001(NEEDED) Sharedlibrary:
[libc.so.6]
可知:
busybox只用到了3个库:
通用C库(libc)、数学库(libm)、加密库(libcrypt),因此我们只需要拷贝这3个库的库文件即可。
但是每个库都有4个文件,4个文件都要拷贝吗?
当然不是。
dennis@dennis-desktop:
/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ls-llibcrypt[.-]*
-rwxr-xr-x1dennisdennis307002008-01-2205:
32libcrypt-2.3.6.so
-rw-r--r--1dennisdennis231182008-01-2205:
32libcrypt.a
lrwxrwxrwx1dennisdennis 132008-12-2215:
38libcrypt.so->libcrypt.so.1
lrwxrwxrwx1dennisdennis 172008-12-2215:
38libcrypt.so.1->libcrypt-2.3.6.so
dennis@dennis-desktop:
/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ls-llibm[.-]*
-rwxr-xr-x1dennisdennis 7790962008-01-2205:
31libm-2.3.6.so
-rw-r--r--1dennisdennis11342822008-01-2205:
32libm.a
lrwxrwxrwx1dennisdennis 92008-12-2215:
38libm.so->libm.so.6
lrwxrwxrwx1dennisdennis 132008-12-2215:
38libm.so.6->libm-2.3.6.so
dennis@dennis-desktop:
/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$ls-llibc[.-]*
-rwxr-xr-x1dennisdennis14356602008-01-2205:
48libc-2.3.6.so
-rw-r--r--1dennisdennis27682802008-01-2205:
31libc.a
-rw-r--r--1dennisdennis 1952008-01-2205:
34libc.so
lrwxrwxrwx1dennisdennis 132008-12-2215:
38libc.so.6->libc-2.3.6.so
4个文件中的.a文件是静态库文件,是不需要拷贝的。
另外3个文件是:
∙实际的共享链接库:
libLIBRARY_NAME-GLIBC_VERSION.so。
当然需要拷贝。
∙主修订版本的符号链接,指向实际的共享链接库:
libLIBRARY_NAME.so.MAJOR_REVISION_VERSION,程序一旦链接了特定的链接库,将会参用该符号链接。
程序启动时,加载器在加载程序前,会检索该文件。
所以需要拷贝。
∙与版本无关的符号链接,指向主修订版本的符号连接(libc.so是唯一的例外,他是一个链接命令行:
libLIBRARY_NAME.so,是为编译程序时提供一个通用条目)。
这些文件在程序被编译时会被用到,但在程序运行时不会被用到,所以不必拷贝它。
关于共享库的2个符号链接的作用的特别说明:
当我们使用gcc hello.c -o hello -lm编译程序时,gcc会根据-lm的指示,加头(lib)添尾(.so)得到libm.so,从而沿着与版本无关的符号链接(libm.so->libm.so.6)找到libm.so.6并记录在案(hello的ELF头中),表示hello需要使用libm.so.6这个库文件所代表的数学库中的库函数。
而当hello被执行的时候,动态链接库加载器会从hello的ELF头中找到libm.so.6这个记录,然后沿着主修订版本的符号链接(libm.so.6->libm-2.3.6.so)找到实际的共享链接库libm-2.3.6.so,从而将其与hello作动态链接。
可见,与版本无关的符号链接是供编译器使用的,主修订版本的符号链接是供动态链接库加载器使用的,而实际的共享链接库则是供应用程序使用的。
通过以上分析,我们只需要拷贝3个库(每个库各1个主修订版本的符号链接和1个实际的共享链接库)以及动态链接库加载器(1个符号链接和1个实体文件)。
步骤如下:
dennis@dennis-desktop:
/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$mkdir/work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:
/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$cp libcrypt-*/work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:
/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$cp-llibcrypt.so.*/work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:
/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$cp libm-*/work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:
/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$cp-llibm.so.*/work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:
/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$cp libc-*/work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:
/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$cp-llibc.so.*/work/nfs_root/fs_mini3/lib
dennis@dennis-desktop:
/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux/lib$cp-lld-*/work/nfs_root/fs_mini3/lib
四、手工构建/etc目录
/etc目录存放的是系统程序的主配置文件,因此需要哪些配置文件取决于要运行哪些系统程序。
即使最小的系统也一定会运行1号用户进程init,所以我们至少要手工编写init的主配置文件inittab。
busybox的inittab文件的语法、语义与传统的SYSV的inittab有所不同。
inittab文件中每个条目用来定义一个需要init启动的子进程,并确定它的启动方式,格式为:
:
:
。
例如:
ttySAC0:
:
askfirst:
-/bin/sh
∙表示子进程要使用的控制台,若省略则使用与init进程一样的控制台
∙表示运行级别,busyboxinit程序这个字段没有意义
∙表示init进程如何控制这个子进程
osysinit:
系统启动后最先执行,只执行一次,init进程等待它结束后才继续执行其它动作
owait:
系统执行完sysinit条目后执行,只执行一次,init进程等待它结束后才继续执行其它动作
oonce:
系统执行完wait条目后执行,只执行一次,init进程不等待它结束
orespawn:
启动完once进程后,init进程监测发现子进程退出时,重新启动它
oaskfirst:
启动完respawn进程后,与respawn类似,不过init进程先输出”PleasepressEntertoactivatethisconsole“,等用户输入回车后才启动子进程
oshutdown:
当系统关机时
orestart:
Busybox中配置了CONFIG_FEATURE_USE_INITAB,并且init进程接收到SIGUP信号时执行,先重新读取、解析/etc/inittab文件,再执行restart程序
octrlaltdel:
按下ctrl+alt+del键时执行,不过在串口控制台中无法输入它
∙表示进程对应的二进制文件。
如果前面有-号,表示该程序是“可以与用户进行交互的”
我们制作最简单的/etc/inittab文件,其内容如下:
:
:
sysinit:
/etc/init.d/rcS
:
:
askfirst:
-/bin/sh
:
:
ctrlaltdel:
/sbin/reboot
:
:
shutdown:
/bin/umount-a–r
制作最简单的脚本程序文件/etc/init.d/rcS,其内容如下:
#!
/bin/sh
ifconfigeth0192.168.2.17
修改shell脚本文件/etc/init.d/rcS的权限,以使其可被执行:
#chmoda+x/etc/init.d/rcS
五、手工构建最简化的/dev目录
在linux机器上,执行ls /dev可看到几百个设备文件,我需要手工创建它们吗?
maybe,我只需要手工创建几个设备文件!
我怎么知道我应该创建哪几个设备文件呢?
管它呢,先看看开发板上可爱的linux的反应再说。
启动Linux操作系统,显示:
VFS:
Mountedroot(nfsfilesystem).
Freeinginitmemory:
112K
Warning:
unabletoopenaninitialconsole.
这说明,内核已经成功挂载根文件系统,但却未能成功启动第1个用户进程init。
通过错误消息“unabletoopenaninitialconsole”搜索内核源代码,找到init/main.c文件。
748staticintnoinlineinit_post(void)
749{
750 free_initmem();
751 unlock_kernel();
752 mark_rodata_ro();
753 system_state=SYSTEM_RUNNING;
754 numa_default_policy();
755
756 if(sys_open((constchar__user*)"/dev/console",O_RDWR,0)<0)
757 printk(KERN_WARNING"Warning:
unabletoopenaninitialconsole.\n");
758
759 (void)sys_dup(0);
760 (void)sys_dup(0);
761
762 if(ramdisk_execute_command){
763 run_init_process(ramdisk_execute_command);
764 printk(KERN_WARNING"Failedtoexecute%s\n",
765 ramdisk_execute_command);
766 }
767
768 /*
769 *Wetryeachoftheseuntilonesucceeds.
770 *
771 *TheBourneshellcanbeusedinsteadofinitifweare
772 *tryingtorecoverareallybrokenmachine.
773 */
774 if(execute_command){
775 run_init_process(execute_command);
776 printk(KERN_WARNING"Failedtoexecute%s. Attempting"
777 "defaults...\n",execute_command);
778 }
779 run_init_proces