构建最简单的根文件系统

       本文将介绍如何构建一个最简单的根文件系统,并且初步分析内核如何执行第一个内核程序。

    挂接根文件系统

      在挂接根文件系统之前,需要制作根文件系统。根文件系统里面需要一些基本的命令,目录和设备文件,下面来介绍如何使用busybox来制作根文件系统。

      我们都知道,init进程是系统启动后执行的第一个应用程序,根据一般的Linux应用程序配置结构,一个可执行文件通常搭配一个对于的配置文件,例如samba功能对于/etc/samba/smb.conf配置文件,nfs功能对于/dev/exports配置文件,那么init初始化功能肯定也是对于一个配置文件,这个配置文件叫做/etc/inittab.配置文件根据不同的级别,指定在不同的情况下,执行不同的应用程序。

    通过Busybox自带的配置文件格式介绍,我们了解到inittab的文件格式:

    Format for each entry: <id>:<runlevels>:<action>:<process>

  id项 : 每一个id会变成 /dev/id ,用作程序的输入输出文件所使用的终端设备,例如id=s3c2410_serial0,则对于终端为s3c2410_serial0

           不需要终端输出的可以设置为空

  runlevels项:用于启动级别的控制,在arm-linux中可省略不写。

  action项: 执行应用程序执行时机,只能设置为几个有效的时机,busybox支持如下几个时间点。

      Valid actions include: sysinit[系统引导时启动], respawn[只要进程一停止,该进程就重新启动], askfirst, wait[进程只阻塞运行一次], once[进程只运行一次], restart, ctrlaltdel, and shutdown.

      askfirst :从名字上可以知道,该触发点是需要先向用户发出提问,看是否需要执行。

      ctraltdel:当用户按下ctrl+alt+del时,内核会向init进程发起SIGINT信号,当init进程接收到此信号,会执行指定的ctrlaltdel类型的应用程序,默认执行reboot命令。

  process:指定应用程序全路径

  如果用户没有在根文件系统中设置/etc/inittab文件,busybox会提供一个默认的配置选项,其等效于如下配置。

  image

busybox会按照sysinit--->wait---->once---->[respawn--->askfirst----> respawn----> askfirst—>… ]

除了busybox提供的基本linux文件框架之外,根文件系统还需要程序运行库。

有了上面的知识铺垫,下面来构建最小根文件系统,那么最小根文件系统里面应该有哪些呢?

1. /dev/console  . /dev/null

2. /sbin/init ----> busybox

3.  /etc/inittab 以及其中定制的应用程序

4. 运行时的C库

  制作根文件系统的操作步骤:

   1. 配置编译busybox

      我这里选用的是busybox-1.7.0,编译步骤和编译kernel差不多,先make menuconfig,再make ,最后make install。这里面,需要注意如下几点:

       如果在menuconfig中无法配置交叉编译工具,那么需要在 Makefile中修改image,指定特定的工具。

      添加命令行代码补全功能:  Busybox Settings--->Busybox Library Tuning---> Tab completion

      是否选用静态连接: Busybox Settings ----> Build Options ----> Build Busybox as a static binary     一般都设置成动态库

      是否支持热插拔:Linux System Utilities  -----> mdev    

    然后make,执行  make CONFIG_PREFIX=../test_busybox_compile install  ,千万不要直接make install,这样会覆盖PC机上面的原有的代码。安装完成后结果如下:

image

busybox编译的结果有bin目录,sbin目录和usr目录,根目录下面还有一个linuxrc链接文件,指向bin/busybox。查看一下所有生成的文件,他们都是链接文件,均指向/bin/busybox。这样的目录构造对于启动一个正常的linux来说还是不够的,下面继续制作。

2. 添加其他必须的文件

     添加基本的设备节点,仿照当前系统的console设备节点和null设备节点,在busybox的目录下也建立对于的节点。

   image

3. 构造自己的启动配置文件 /etc/inittab

    为了简单起见,这里面只设置启动时开启shell输入。首先创建etc目录,然后执行如下操作。

image

  4. 安装C库

    将交叉编译工具链中的所有动态链接库都拷贝到busybox的安装目录中,先创建lib目录,然后执行如下操作:

    本机交叉编译工具安装在/opt目录下,在拷贝C库时要注意,-d选项,这个选项使得拷贝过程中,链接文件保持文件属性不变化,否则,链接文件的内容将是所指向的全部内容,而不在是链接文件本身。

    image

5.  制作镜像文件

      根文件系统的格式一般有两种,一种是yaffs格式,这种有两类,一类是针对小页512字节的nand flash文件系统,称为yaffs1,另一类是针对大页2K的nand flash文件系统。这里根据硬件的情况,采用的是yaffs2格式的文件系统。

image

生成最小根文件系统镜像,然后烧写到板子上去,烧写命令如下:

tftp 0x30000000 test_busybox_compile_fs.yaffs2

nand erase root ; nand write.yaffs  0x30000000 0x00260000 $(filesize); reset

启动后显示如下:

image ,大功告成。

 

经过上述几个步骤的操作,一个最简单的根文件系统已经建立好了,此后,可以以此为基础,逐渐添加功能。

  

   附加功能

   上面制作的最小根文件系统有很多不完善的地方,在开机之后,需要自动执行一些挂载命令。

   1。 创建proc目录

        在/etc/inittab里面添加这句话, ::sysinit:/etc/init.d/rcS,使得开机后自动执行rcS脚本文件。

        在rcS脚本文件中,添加  mount –t proc none /proc 就可以开启自动挂载proc虚拟文件系统

   2. 如果需要挂载多个文件系统,推荐使用mount -a选项,这个命令会读取etc/fstab配置文件内的挂载命令。

image

   此时,修改rcS中的文件,改成mount -a,在etc/fstab目录下创建上述文件内容。

   3. 使能热插拔功能 mdev

     根据Busybox中的mdev.txt中的说明,我们知道,使能mdev功能需要做如下操作:下述操作均在根文件系统跟目录

      mkdir sys & mount –t sysfs sysfs /sys            # mdev通过sysfs文件系统获得设备信息

      mount –t tmpfs mdev /dev                           # 使用内存文件系统,减少对flash的读写

      mkdir /dev/pts                                           # devpts支持外部网络连接(telnet)的虚拟终端

      mount –t devpts devpts /dev/pts                   

      echo /bin/mdev > /proc/sys/kernel/hotplug      # 设置内核热拔插事件回调程序

       mdev –s                                                    #在/dev目录下,生成内核目前支持的所有设备节点

    上述挂载相关的命令可以写在fstab中,其他执行命令可以写在rcS启动脚本中,最后结果如下:

     fstab:

image

   rcS:

image

  按照上述操作进行制作烧写后,一个基本架构比较完整的根文件系统就全部建立好了。

 

  知识介绍:mdev工作原理

   执行mdev -s时,mdev扫描/sys/class 和 /sys/block中两个目录下的dev属性文件,从该dev属性文件中,获取设备编号,并以包含该dev属性文件的目录名称作为设备名,device_name。而/sys/class 和 device_name 之间的那部分目录成为subsystem,

           [subsystem] [device_name] dev

例如:cat  /sys/class/tty/tty0/dev    里面的内容为4:0 【major:minor】,那么subsystem为tty,device_name为tty0

      mdev会根据此信息,在dev目录下创建相应的设备文件

image

   当mdev因uevent事件(以前叫hotplug事件)被调用时,mdev通过由uevent事件传递给它的环境变量获得到两个变量,

   一个是  引发该uevent事件的action

    另一个是 该设备所在的device path。

    mdev判断action,若是add,则表示有新设备(虚拟设备或者是物理设备)加入系统,mdev会通过设备路径下的dev属性文件获得设备编号,然后根据路径相关信息在/dev目录下建立设备节点。若动作是remove,即表示设备已从系统中移除,则删除/dev/目录下的设备节点。

   由上可知,要想发挥mdev自动创建设备节点的功能,必须有三个条件。

   1. 在/sys/class/的某一个subsystem下   

   2. 在subsystem下创建一个以设备名device_name作为名称的目录

   3. 在此目录里面,包含一个dev属性文件,内容以”major:minor\n”的形式输出设备编号。

    一个类class可以看成是一个容器,这个大容器里面包含并管理很多class_device,每一个class_device都对于着一个具体的设备。

   优化操作:

    按照上面制作出来的根文件系统,还有很多可以优化的地方,比如说,可以按照实际需要裁剪文件系统中的C库,可以使用arm-linux-strip优化lib下面的动态链接库和bin/busybox

 

如何执行第一个程序

   1:  static int noinline init_post(void)
   2:  {
   3:      free_initmem();
   4:      unlock_kernel();
   5:      mark_rodata_ro();
   6:      system_state = SYSTEM_RUNNING;
   7:      numa_default_policy();
   8:   
   9:      /*hao: open console device */
  10:      if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
  11:          printk(KERN_WARNING "Warning: unable to open an initial console.\n");
  12:   
  13:      (void) sys_dup(0);  /*hao: copy an opened file num,make two file num pointed to the same file.  */
  14:      (void) sys_dup(0);  /*make 1(standard output) and 2(error output) point to 0(standard input)*/
  15:   
  16:      if (ramdisk_execute_command) {
  17:          run_init_process(ramdisk_execute_command);
  18:          printk(KERN_WARNING "Failed to execute %s\n",
  19:                  ramdisk_execute_command);
  20:      }
  21:   
  22:      /*
  23:       * We try each of these until one succeeds.
  24:       *
  25:       * The Bourne shell can be used instead of init if we are
  26:       * trying to recover a really broken machine.
  27:       */
  28:       /*
  29:       hao: execute_command equal the bootargs named init.  e.g : init=/linuxrc
  30:        then execute_command= = "/linuxrc
  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:   
  38:      /*hao: if we don't define init cmd, then we call /sbin/init to start system. */
  39:      run_init_process("/sbin/init");
  40:      run_init_process("/etc/init");
  41:      run_init_process("/bin/init");
  42:      run_init_process("/bin/sh");
  43:   
  44:      panic("No init found.  Try passing init= option to kernel.");
  45:  }

     在内核启动的后面,执行init_post,里面会打开一个终端设备(这里指的是/dev/console),然后将标准输出文件和错误输出文件指向标准输入文件所指向的console设备中去。

     ramdisk_execute_command为uboot传给kernel的rdinit参数,这里为空,所以ramdisk_execute_command也为空。

     execute_command为uboot传给kernel的init参数,这里为/linuxrc,所以execute_command=linuxrc。

     如果uboot没有定义rdinit和init参数,那么kernel会搜索并依此执行/sbin/init,/etc/init…直到某一个程序执行成功,如果都没有找到,则会显示kernel panic,打印对应的提示信息。

 

参考链接:

BusyBox简化嵌入式Linux系统

posted @ 2015-01-20 23:10  浩天之家  阅读(3258)  评论(0编辑  收藏  举报