根文件系统的构建与分析

来源:互联网 发布:软件测试行业怎么样 编辑:程序博客网 时间:2024/06/06 02:32

此系列,我想从初学者的角度来体验怎样构建根文件系统,显然,我也是个初学者。

根文件系统第一个进程init到启动用户应用程序的过程:

qt应用程序也做了,u-boot、内核也移植了,那启动内核后,怎样才能运行qt应用程序呢?内核启动后,操作系统做了些什么?

 U-Boot    目的  ――→  启动内核

       ↓

   内核      目的     ――→  启动第一个进程init(放在根文件系统上)     

       ↓

根文件系统

       ↓

用户应用程序

 

在内核初始化收尾阶段(/kernel-2.6.30.4/init/main.c)流程,start_kernel → rest_init → kernel_init → init_post。

下面的代码是内核启动最后阶段执行的init_post函数:

  1. static noinline int init_post(void)  
  2.     __releases(kernel_lock)  
  3. {  
  4.     /* need to finish all async __init code before freeing the memory */  
  5.     async_synchronize_full();  
  6.     free_initmem();  
  7.     unlock_kernel();  
  8.     mark_rodata_ro();  
  9.     system_state = SYSTEM_RUNNING;  
  10.     numa_default_policy();  
  11.   
  12.     if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)  
  13.         printk(KERN_WARNING "Warning: unable to open an initial console.\n");  
  14.   
  15.     (void) sys_dup(0);  
  16.     (void) sys_dup(0);  
  17.   
  18.     current->signal->flags |= SIGNAL_UNKILLABLE;  
  19.   
  20.     if (ramdisk_execute_command) {  
  21.         run_init_process(ramdisk_execute_command);  
  22.         printk(KERN_WARNING "Failed to execute %s\n",  
  23.                 ramdisk_execute_command);  
  24.     }  
  25.   
  26.     /* 
  27.      * We try each of these until one succeeds. 
  28.      * 
  29.      * The Bourne shell can be used instead of init if we are 
  30.      * trying to recover a really broken machine. 
  31.      */  
  32.     if (execute_command) {  
  33.         run_init_process(execute_command);  
  34.         printk(KERN_WARNING "Failed to execute %s.  Attempting "  
  35.                     "defaults...\n", execute_command);  
  36.     }  
  37.     run_init_process("/sbin/init");  
  38.     run_init_process("/etc/init");  
  39.     run_init_process("/bin/init");  
  40.     run_init_process("/bin/sh");  
  41.   
  42.     panic("No init found.  Try passing init= option to kernel.");  
  43. }  


        在启动第一个程序前,首先打开控制台设备sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0),这样init进程就拥有一个控制台,并可以从中读取输入信息在串口终端显示,也可以向其中写入信息。

        在bootloader里设置传给内核的参数init=/linuxrc,execute_command就等于/linuxrc,如果定义了,就会run_init_process(execute_command);执行这个应用程序,如果没有定义这个参数,就会往下走。执行

run_init_process("/sbin/init");

run_init_process("/etc/init");

run_init_process("/bin/init");

run_init_process("/bin/sh");

中的其中一个。另外要说明的是,run_init_process();一旦执行了init或者linuxrc,就不会返回,一直执行。init这个进程是所有进程的父进程,有了它,后面才能有shell、qt等进程。

Init进程的功能:
• 它是所有其他进程的父进程。
• init进程根据文件的内容运行一系列程序和脚本文件,完成系统的各项配置,最终达到启动用户应用程序的目的。

下面我们来追随init的运行。

==================================      i am a line        ========================================

       当我们进入根文件系统,发现有很多命令,如ls、cp、rm等,这些都是应用程序,但这些都是通过链接到busybox实现的功能。如:

/bin/ls -> busybox

/bin/cp -> busybox

/bin/rm -> busybox

就连sbin/init进程也是链接到busybox的,所以busybox是很多命令程序和工具的集合。所以要分析init怎么运行,就要分析busybox。

解压busybox源码,在/init/init.c。(这个就是编译busybox后就是被init链接的代码)

函数调用流程:

busybox -> init_main() -> parse_inittab() -> run_actions()

解析inittab文件函数parse_inittab();

在parse_inittab()函数里,首先打开/etc/inittab这个文件,接着解析inittab。inittab配置文件的格式说明和例子在busybox的源码里面有,搜索一下就能找到。

----------------------------------------------------------------------------------------------------------------------

inittab配置文件的格式:

id:runlevel:action:process
• 其中:
–Id:用来指定所启动进程的控制台,在嵌入式系统中一般不添;
–Runlevel:busybox完全忽略runlevel字段;
–Action:指出init程序在执行相应process时,对process所采取的动作
 <action>: Valid actions include: sysinit, respawn, askfirst, wait, once, restart, ctrlaltdel, and shutdown.

–Process:具体的执行程序或者脚本;

-----------------------------------------------------------------------------------------------------------------------

        回到源码,在parse_inittab()函数里,为inittab文件作一些添油加醋的修饰后,最终调用new_init_action(),下面进行分析。

  1. static void parse_inittab(void)  
  2. {  
  3. #if ENABLE_FEATURE_USE_INITTAB  
  4.     char *token[4];  
  5.     parser_t *parser = config_open2("/etc/inittab", fopen_for_read);  
  6.   
  7.     if (parser == NULL)  
  8. #endif  
  9.     {  
  10.         /* No inittab file -- set up some default behavior */  
  11.         /* Reboot on Ctrl-Alt-Del */  
  12.         new_init_action(CTRLALTDEL, "reboot""");  
  13.         /* Umount all filesystems on halt/reboot */  
  14.         new_init_action(SHUTDOWN, "umount -a -r""");  
  15.         /* Swapoff on halt/reboot */  
  16.         if (ENABLE_SWAPONOFF)  
  17.             new_init_action(SHUTDOWN, "swapoff -a""");  
  18.         /* Prepare to restart init when a QUIT is received */  
  19.         new_init_action(RESTART, "init""");  
  20.         /* Askfirst shell on tty1-4 */  
  21.         new_init_action(ASKFIRST, bb_default_login_shell, "");  
  22. //TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users  
  23.         new_init_action(ASKFIRST, bb_default_login_shell, VC_2);  
  24.         new_init_action(ASKFIRST, bb_default_login_shell, VC_3);  
  25.         new_init_action(ASKFIRST, bb_default_login_shell, VC_4);  
  26.         /* sysinit */  
  27.         new_init_action(SYSINIT, INIT_SCRIPT, "");  
  28.         return;  
  29.     }  
  30.   
  31. #if ENABLE_FEATURE_USE_INITTAB  
  32.     /* optional_tty:ignored_runlevel:action:command 
  33.      * Delims are not to be collapsed and need exactly 4 tokens 
  34.      */  
  35.     while (config_read(parser, token, 4, 0, "#:",  
  36.                 PARSE_NORMAL & ~(PARSE_TRIM | PARSE_COLLAPSE))) {  
  37.         /* order must correspond to SYSINIT..RESTART constants */  
  38.         static const char actions[] ALIGN1 =  
  39.             "sysinit\0""respawn\0""askfirst\0""wait\0""once\0"  
  40.             "ctrlaltdel\0""shutdown\0""restart\0";  
  41.         int action;  
  42.         char *tty = token[0];  
  43.   
  44.         if (!token[3]) /* less than 4 tokens */  
  45.             goto bad_entry;  
  46.         action = index_in_strings(actions, token[2]);  
  47.         if (action < 0 || !token[3][0]) /* token[3]: command */  
  48.             goto bad_entry;  
  49.         /* turn .*TTY -> /dev/TTY */  
  50.         if (tty[0]) {  
  51.             if (strncmp(tty, "/dev/", 5) == 0)  
  52.                 tty += 5;  
  53.             tty = concat_path_file("/dev/", tty);  
  54.         }  
  55.         new_init_action(1 << action, token[3], tty);  
  56.         if (tty[0])  
  57.             free(tty);  
  58.         continue;  
  59.  bad_entry:  
  60.         message(L_LOG | L_CONSOLE, "Bad inittab entry at line %d",  
  61.                 parser->lineno);  
  62.     }  
  63.     config_close(parser);  
  64. #endif  
  65. }  

        可以看见,如果没配置文件inittab存在,它会默认一些配置。其中抽取一个来分析,如下:

  1. static void new_init_action(uint8_t action_type, const char *command, const char *cons)  
  1. new_init_action(ASKFIRST, bb_default_login_shell, VC_2);  

                                          bb_default_login_shell     ->          "-/bin/sh"         

                                                      VC_2                    ->          "/dev/tty2"

action_type对应inittab配置文件中Action字段

command对应inittab配置文件中Process字段

cons对应inittab配置文件中id字段

        new_init_action函数做了这些工作:

1、创建init_action结构,并填充。

2、把这个结构放入init_action_list链表

        假如我们没有配置文件inittab存在,它会默认一些配置,我们从默认的new_init_action反推出默认的配置文件inittab。

  1. if (parser == NULL)  
  2. #endif  
  3.     {  
  4.         /* No inittab file -- set up some default behavior */  
  5.         /* Reboot on Ctrl-Alt-Del */  
  6.         new_init_action(CTRLALTDEL, "reboot""");  
  7.         /* Umount all filesystems on halt/reboot */  
  8.         new_init_action(SHUTDOWN, "umount -a -r""");  
  9.         /* Swapoff on halt/reboot */  
  10.         if (ENABLE_SWAPONOFF)  
  11.             new_init_action(SHUTDOWN, "swapoff -a""");  
  12.         /* Prepare to restart init when a QUIT is received */  
  13.         new_init_action(RESTART, "init""");  
  14.         /* Askfirst shell on tty1-4 */  
  15.         new_init_action(ASKFIRST, bb_default_login_shell, "");  
  16. //TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users  
  17.         new_init_action(ASKFIRST, bb_default_login_shell, VC_2);  
  18.         new_init_action(ASKFIRST, bb_default_login_shell, VC_3);  
  19.         new_init_action(ASKFIRST, bb_default_login_shell, VC_4);  
  20.         /* sysinit */  
  21.         new_init_action(SYSINIT, INIT_SCRIPT, "");  
  22.         return;  
  23.     }  

根据这段代码写成inittab配置文件:

  1. #id:runlevel:action:process  
  2.   
  3. ::CTRLALTDEL:reboot  
  4. ::SHUTDOWN:umount -a -r  
  5. ::RESTART:init  
  6. ::ASKFIRST:-/bin/sh  
  7. tty2::ASKFIRST:-/bin/sh  
  8. tty3::ASKFIRST:-/bin/sh  
  9. tty4::ASKFIRST:-/bin/sh  
  10. ::SYSINIT:/etc/init.d/rcS  

        我们把这些包含执行时机、应用程序等结构体填充到init_action_list链表,这就完成了解析inittab文件函数parse_inittab(),在init_main()函数继续走下去,执行run_actions()。

  1. /* Now run everything that needs to be run */  
  2.   
  3.     /* First run the sysinit command */  
  4.     run_actions(SYSINIT);  
  5.   
  6.     /* Next run anything that wants to block */  
  7.     run_actions(WAIT);  
  8.   
  9.     /* Next run anything to be run only once */  
  10.     run_actions(ONCE);  
  11.   
  12.     /* Redefine SIGHUP to reread /etc/inittab */  
  13. #if ENABLE_FEATURE_USE_INITTAB  
  14.     signal(SIGHUP, reload_signal);  
  15. #else  
  16.     signal(SIGHUP, SIG_IGN);  
  17. #endif  
  18.   
  19.     /* Now run the looping stuff for the rest of forever */  
  20.     while (1) {  
  21.         /* run the respawn/askfirst stuff */  
  22.         run_actions(RESPAWN | ASKFIRST);  
  23.   
  24.         /* Don't consume all CPU time -- sleep a bit */  
  25.         sleep(1);  
  26.   
  27.         /* Wait for any child process to exit */  
  28.         wpid = wait(NULL);  
  29.         while (wpid > 0) {  
  30.             /* Find out who died and clean up their corpse */  
  31.             for (a = init_action_list; a; a = a->next) {  
  32.                 if (a->pid == wpid) {  
  33.                     /* Set the pid to 0 so that the process gets 
  34.                      * restarted by run_actions() */  
  35.                     a->pid = 0;  
  36.                     message(L_LOG, "process '%s' (pid %d) exited. "  
  37.                             "Scheduling for restart.",  
  38.                             a->command, wpid);  
  39.                 }  
  40.             }  
  41.             /* see if anyone else is waiting to be reaped */  
  42.             wpid = wait_any_nohang(NULL);  
  43.         }  
  44.     }  

        可见,最先执行是的Action字段里的sysinit这类,接着是wait,接着是once,最后是respawn和askfirst。在run_actions函数里,它提取init_action_list链表的项来用waitfor()执行应用程序或者脚本,等执行完毕,再用delete_init_action()删除链表中的该项。

小结,根文件系统要顺利执行用户应用程序需要:

1、/dev/console      /dev/null   当不设置inittab里id字段的话,标准输入、输出、错误会自动定位到/dev/null  

2、/etc/inittab

3、配置文件里指定的程序或者脚本

4、/lib下的动态库

5、init本身,即busybox



我们依旧记得系统启动流程:硬件上电--》bootloader--》Linux内核--》挂载根文件系统--》应用程序

上一篇,我们建好了根文件系统的目录。这篇的任务是

利用交叉编译工具链,构建动态库

qt应用程序依赖一些动态库文件才能运行,如qt库,c库。因为应用程序本身需要使用C库的库函数,因此还必需制作for ARM的C库,并将其放置于/lib目录。还记得交叉编译工具链的3个组成部分吗?交叉编译器、for ARM的C库和二进制工具。for ARM的C库是现成的,我们只需要拷贝过来就可以了。遗憾的是:整个C库目录下的文件总大小有26M。而我们根文件系统所在分区不过区区16M而已,根本放不下。怎么办呢?我们只需要拷贝有用的。

交叉应用程序的开发需要用到交叉编译的链接库,交叉编译的链接库是在交叉工具链的lib目录下,我们在移植应用程序到我们的目标板的时候,需要把交叉编译的链接库也一起移植到目标板上,这里我用到的交叉工具链的路径是/opt/4.1.2/,所以链接库的目录是/opt/4.1.2/arm-linux/lib,此lib目录有下面这些东西:

  • 目标文件,如crtn.o,用于gcc链接可执行文件
  • libtool库文件(.la),在链接库文件时这些文件会被用到,比如他们列出了当前库文件所依赖的其它库文件,程序运行时无需这些文件
  • gconv目录,里面是各种链接脚本,在编译应用程序时,他们用于指定程序的运行地址,各段的位置等 (我的4.1.2没有这个目录)
  • 静态库文件(.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类文件当然要拷贝。 

  1. [root@localhost arm-linux]# du -c --si lib/*  

lib目录一共105M

  1. [root@localhost lib]# du -c --si *so*  

so动态库也有35M

因此我们要进一步按需定制

通过arm-linux-readelf命令来找出应用程序依赖于哪些动态链接库

  1. [root@localhost my-vod-qt]# arm-linux-readelf -d  my-vod-qt|grep Shared  
  2.  0x00000001 (NEEDED)                     Shared library: [libqte-mt.so.3]  
  3.  0x00000001 (NEEDED)                     Shared library: [libts-0.0.so.0]  
  4.  0x00000001 (NEEDED)                     Shared library: [libstdc++.so.6]  
  5.  0x00000001 (NEEDED)                     Shared library: [libm.so.6]  
  6.  0x00000001 (NEEDED)                     Shared library: [libgcc_s.so.1]  
  7.  0x00000001 (NEEDED)                     Shared library: [libc.so.6]  

my-vod-qt是一个qt应用程序,可见它依赖的库。

libqte-mt.so.3  是qte库
libts-0.0.so.0   是ts库

libstdc++.so.6  是C++库

libm.so.6  是数学库

libgcc_s.so.1  是gcc编译相关的库

libc.so.6   是C库

下面找一个/opt/4.1.2/arm-linux/lib目录下的库文件说明。

  1. -rwxr-xr-x 1 root root   226023 2010-05-30 libresolv-2.5.so  
  2. -rw-r--r-- 1 root root   313560 2010-05-30 libresolv.a  
  3. lrwxrwxrwx 1 root root       14 2011-12-15 libresolv.so -> libresolv.so.2  
  4. lrwxrwxrwx 1 root root       16 2011-12-15 libresolv.so.2 -> libresolv-2.5.so  

假如我们要用到libresolv这个动态库,我们需要拷贝哪些?

首先libresolv.a、xxx.la肯定不用的。上文已经提及过。

实际的共享链接库的格式是libLIBRARY_NAME-GLIBC_VERSION.so  这个需要拷贝,如libresolv-2.5.so

主修订版本的符号链接,指向实际的共享链接库:libLIBRARY_NAME.so.MAJOR_REVISION_VERSION,程序一旦链接了特定的链接库,将会参用该符号链接。程序启动时,加载器在加载程序前,会检索该文件。所以需要拷贝。 如:libresolv.so.2

另外,与版本无关的符号链接,指向主修订版本的符号连接(libc.so是唯一的例外,他是一个链接命令行:libLIBRARY_NAME.so,是为编译程序时提供一个通用条目)。这些文件在程序被编译时会被用到,但在程序运行时不会被用到,所以不必拷贝它。 如:

  1. lrwxrwxrwx 1 root root       18 2011-12-15 libXrandr.so -> libXrandr.so.2.1.0  
  1. lrwxrwxrwx 1 root root       14 2011-12-15 libresolv.so -> libresolv.so.2  

通过上面分析,我们只需要拷贝实际的共享链接库主修订版本的符号链接动态链接库加载器。

下面我们假设/opt/build_rootfs目录是要制作的根文件系统。并编写一个shell脚本用于copy实际的共享链接库、主修订版本的符合链接、动态连接器加载器及其符号链接到目标板根目录下的lib(/opt/build_rootfs/lib),拷贝这些库能适用大部分qt应用程序,而不仅仅是我的my-vod-qt。

vi cp.sh 

  1. for file in libc libcrypt libdl libm libpthread libresolv libutil libthread_db  
  2. do  
  3. cp $file*.so /opt/build_rootfs/lib         #拷贝实际的共享链接库  
  4. cp -d $file.so.* /opt/build_rootfs/lib     #拷贝主修订版本的符号链接  
  5. done  
  6. cp -d ld*.so* /opt/build_rootfs/lib        #拷贝动态连接器及其符合链接  
  7. cp -d libstdc++.so* /opt/build_rootfs/lib  
  8. cp -d libz.so* /opt/build_rootfs/lib  
  9. cp -d libjpeg.so* /opt/build_rootfs/lib  
  10. cp -d libgcc_s* /opt/build_rootfs/lib  

ps:cp -d或--no-dereference  当复制符号连接时,把目标文件或目录也建立为符号连接,并指向与源文件或目录连接的原始文件或目录。 
执行source cp.sh后,我们还可以把库的调试符号去掉,从而压缩库的大小

  1. arm-linux-strip -s /opt/build_rootfs/lib*  

再看看lib压缩后的体积

  1. [root@localhost build_rootfs]# du -c --si lib/*  

共6.2M

  1. [root@localhost lib]# ls  
  2. ld-2.5.so        libdl-2.5.so       libm.so            libstdc++.so.6  
  3. ld-linux.so.3    libdl.so           libm.so.6          libstdc++.so.6.0.8  
  4. libc-2.5.so      libdl.so.2         libmudflap.so      libthread_db-1.0.so  
  5. libcidn-2.5.so   libgcc_s.so        libmudflapth.so    libthread_db.so  
  6. libcidn.so       libgcc_s.so.1      libpthread-2.5.so  libthread_db.so.1  
  7. libcrypt-2.5.so  libjpeg.so         libpthread.so      libutil-2.5.so  
  8. libcrypto.so     libjpeg.so.62      libpthread.so.0    libutil.so  
  9. libcrypt.so      libjpeg.so.62.0.0  libresolv-2.5.so   libutil.so.1  
  10. libcrypt.so.1    libm-2.5.so        libresolv.so       libz.so  
  11. libc.so          libmemusage.so     libresolv.so.2     libz.so.1  
  12. libc.so.6        libmp3lame.so      libstdc++.so       libz.so.1.2.3  
  13. [root@localhost lib]#   

至此,一个qt应用程序假如放在根文件系统里,要运行它,它依赖的东西有了,就可以运行了。




一、FHS(Filesystem Hierarchy Standard)标准介绍

当我们在linux下输入ls  / 的时候,见到的目录结构以及这些目录下的内容都大同小异,这是因为所有的linux发行版在对根文件系统布局上都遵循FHS标准的建议规定。

该标准规定了根目录下各个子目录的名称及其存放的内容:

目录名存放的内容/bin必备的用户命令,例如ls、cp等/sbin必备的系统管理员命令,例如ifconfig、reboot等/dev设备文件,例如mtdblock0、tty1等/etc系统配置文件,包括启动文件,例如inittab等/lib必要的链接库,例如C链接库、内核模块/home普通用户主目录/rootroot用户主目录/usr/bin非必备的用户程序,例如find、du等/usr/sbin非必备的管理员程序,例如chroot、inetd等/usr/lib库文件/var守护程序和工具程序所存放的可变,例如日志文件/proc用来提供内核与进程信息的虚拟文件系统,由内核自动生成目录下的内容/sys用来提供内核与设备信息的虚拟文件系统,由内核自动生成目录下的内容/mnt文件系统挂接点,用于临时安装文件系统/tmp临时性的文件,重启后将自动清除

二、最基本的目录

 在嵌入式系统中,可以对根目录下的目录进行裁剪,例如:
• 为多用户提供可扩展环境的所以目录都可以删除;如(/home,/mnt,/root)
• 根据引导加载情况,/boot目录可以删除。

• 系统与程序正常运行的必要目录一般保留
 /bin,/dev,/etc,/proc,/sbin,/usr,/lib,/sys 一般保留。

Linux的根文件系统包括支持linux系统正常运行的基本内容,一般应包括以下几项内容:
� 基本的文件系统结构,如bin、dev、etc、sbin,lib、usr、proc。
� 基本程序运行所需的动态库。
� 基本的系统配置文件。
� 必要的设备文件支持。
� 基本的应用程序,如sh、ls、cp等(busybox提供)

 

构建根文件系统就是往相应的目录添加相应的文件。如:
� 在/dev添加设备文件(本篇介绍《根文件系统的构建与分析(三)之根文件目录及最简/dev目录》),
� 在/etc添加配置文件(请参考后续),
� 在/bin添加命令或者程序(根文件系统的构建与分析(四)之瑞士军刀busybox生成系统基本命令 ),
� 在/lib添加动态库等(请参考《根文件系统的构建与分析(二)之构建动态库》)。

三、最简化的/dev目录

在linux机器上,/dev目录常常可以看到几百个设备节点,但要手工创建它们吗?不需要,我们只需要创建几个设备节点。但是哪几个呢?构建/dev目录时,有两种方法:即静态构建和mdev设备管理工具构建。看了《根文件系统的构建与分析(一)之流程分析》这篇的都应该依稀记得最后提到init进程至少用到有两个,/dev/console和/dev/null。

下面我们创建根文件系统目录:

vim build_rootfs.sh

  1.  1 #!/bin/bash  
  2.  2   
  3.  3 echo "------ build  file system directory -------"  
  4.  4 mkdir /opt/build_rootfs  
  5.  5 mkdir /opt/build_rootfs/{bin,dev,etc,usr,lib,sbin,proc,sys,tmp}  
  6.  6 mkdir /opt/build_rootfs/usr/{bin,sbin,lib}  
  7.  7   
  8.  8 mknod  /opt/build_rootfs/dev/console c 5 1  
  9.  9 mknod  /opt/build_rootfs/dev/null    c 1 3  
  10. 10   
  11. 11 chmod 777 /opt/build_rootfs/dev/console  
  12. 12 chmod 777 /opt/build_rootfs/dev/null  
  13. 13   
  14. 14 echo "------  end  -------"  
  1. [root@localhost opt]# ls build_rootfs  
  2. bin  dev  etc  lib  proc  sbin  sys  tmp  usr  




ls /bin,ls /sbin,这些目录下存储的主要是常用命令的二进制文件,如ls、cp、rm等。如果要自己编写这几百个常用命令的源程序,好在我们有嵌入式Linux系统的瑞士军刀——busybox,事情就简单很多,他集合了许多系统基本命令,这些命令都是链接到busybox这个程序。



一、BusyBox介绍

• Busybox是一个开源的软件项目,其官方网站是:http://www.busybox.net
• Busybox集成了一百多个最常用的Linux命令程序和工具;
• Busybox  程序的体积非常之小,很适合嵌入式系统;

• Busybox 提供的程序包括:
–具有shell功能,如csh
–提供一个迷你的vi编辑器
–提供系统不可或缺的/sbin/init程序
–其他的系统基本命令,如:ls,mkdir,ifconfig等。

二、编译/安装busybox,生成/bin、/sbin、/usr/bin、/usr/sbin目录及其命令

1、下载源码

从官网(http://www.busybox.net/downloads/)上下载最新的版本:busybox-1.20.1.tar.bz2 28-May-2012 00:51 2.1M

2、解压源码。

  1. [root@localhost opt]# tar xvjf busybox-1.20.1.tar.bz2   

3、进入源码修改Makefile

  1. 164 CROSS_COMPILE ?=  

改成

  1. 164 CROSS_COMPILE ?= arm-linux-  

 

  1. 190 ARCH ?= $(SUBARCH)  

改成

  1. 190 ARCH ?= arm  

4、make menuconfig配置busybox


1)Busybox Settings   ==》  Build Options    ==》 Build BusyBox as a static binary (no shared libs),表示编译busybox时,是否静态链接C库。我们选择动态链接C库(默认动态)。

2)Busybox Settings   ==》 Installation Options   ==》 What kind of applet links to install (as soft-links) ,表示安装busybox时,将各个命令安装为指向busybox的软链接还是硬链接。我们选择软链接(默认软链接)。

3)Busybox Settings   ==》 Installation Options   ==》 (/opt/build_rootfs) BusyBox installation prefix,表示busybox的安装位置。我们选择/opt/build_rootfs

4)Busybox Settings   ==》 Busybox Library Tuning。保留Command line editing以支持命令行编辑;保留History size以支持记忆历史命令;选中Tab completion和Username completion以支持命令自动补全

选项Applets,他将busybox的支持的几百个命令分门别类。我们只要在各个门类下选择想要的命令即可。这里我们基本保持默认设置。

5)选中Networking Utilities — httpd下的Enable -u <user> option,以启用http服务器的功能allows the server to run as a specific user(默认选上)

更多的选项大家可以摸索一下,常用的它都默认了。

推出Exit后保存配置文件

5、编译busybox

make

可能因为我的交叉编译工具链是4.1.2,所以提示错误,mtd\mtd-user.h 没有那个文件或目录

解决:

  1. [root@localhost busybox-1.20.1]# cp /usr/include/mtd/ ./include/mtd/ -a  

接着make,又出现缺少mtd\ubi-user.h ,我从u-boot源码里拷贝一个过来。

  1. [root@localhost busybox-1.20.1]# cp /opt/u-boot-2009.11/include/mtd/ubi-user.h ./include/mtd  

6、安装busybox

make install

因为上一篇《根文件系统的构建与分析(三)之根文件目录及最简/dev目录》已经在/opt/build_rootfs建了根文件目录和拷贝了动态库在/lib和建了设备节点在/dev,为了更清晰看到busybox安装到根文件系统的有什么东西,我先搞一个空目录让它安装,看看会多了什么出来!我做的是把原来的/opt/build_rootfs目录备份,再清空之前的/opt/build_rootfs。

 

安装完成后,可以看到在/opt/build_rootfs目录下生成了bin、sbin、usr/bin、usr/sbin目录,其下包含了我们常用的命令,这些命令都是指向bin/busybox的软链接,而busybox本身的大小也只有900K:

  1. [root@localhost build_rootfs]# ls -l  
  2. 总计 28  
  3. drwxr-xr-x 2 root root 4096 06-26 02:10 bin  
  4. lrwxrwxrwx 1 root root   11 06-26 02:10 linuxrc -> bin/busybox  
  5. drwxr-xr-x 2 root root 4096 06-26 02:10 sbin  
  6. drwxr-xr-x 4 root root 4096 06-26 02:10 u)  

还记得之前讲过bootloader传到内核的参数init=\linuxrc吗,linuxrc就是内核启动的第一个进程,它链接到busybox。如果没有它也行,因为/sbin/init可以代替。

  1. [root@localhost build_rootfs]# ls -l /sbin/init  
  2. -rwxr-xr-x 1 root root 38652 2008-09-18 /sbin/init  
  1. [root@localhost build_rootfs]# ls -sh bin -l  
  2. 总计 1.3M  
  3. 4.0K lrwxrwxrwx 1 root root    7 06-26 02:10 addgroup -> busybox  
  4. 4.0K lrwxrwxrwx 1 root root    7 06-26 02:10 adduser -> busybox  
  5. 4.0K lrwxrwxrwx 1 root root    7 06-26 02:10 ash -> busybox  
  6. 4.0K lrwxrwxrwx 1 root root    7 06-26 02:10 base64 -> busybox  
  7. 916K -rwxr-xr-x 1 root root 908K 06-26 02:10 busybox  
  8. 4.0K lrwxrwxrwx 1 root root    7 06-26 02:10 cat -> busybox  
  9. 4.0K lrwxrwxrwx 1 root root    7 06-26 02:10 catv -> busybox  
  10. 4.0K lrwxrwxrwx 1 root root    7 06-26 02:10 chattr -> busyb  

看见bin命令的大小为1.3M,命令都是链接到busybox的,而busybox的大小仅为908k,十分娇小,集合多系统命令于一身,不愧为瑞士军刀。

PS:假如是旧版本,如1.16.0,可以不要usr这个目录,只要配置make menuconfig,Busybox setting  ---->    Installation Options  ---->  [*] don’t use /usr(新版本没这个选项),这样可以节省空间。

7、小结

至此,我们用busybox生成系统基本命令和系统不可或缺的/sbin/init程序和linuxrc。

其中/sbin/init是默认的,如果bootloader不传init=\linuxrc,内核配置也不填上参数init=\linuxrc,这个init就会被执行。
0 0
原创粉丝点击