嵌入式linux基础教程 用户空间初始化(2)

时间:2021-04-01 18:46:55

                                                      用户空间初始化

       Web服务器启动脚本示例

       这个示例很简单但是它可以说明一些机制,指导你设计自己的系统启动和关机行为。这个例子是基于busybox,他和init的初始化行为稍有不同。

       在典型的包含Web服务器的嵌入式应用中,你也许会期望系统中有多个服务器,用于系统维护和远程访问。在这个例子中,我们启用了访问HTTP和Telnet的服务器。下面代码显示了一个简单的rc.sysinit脚本,用于我们假想的Web服务器设备。

       Web服务器的rc.sysinit

       #!/bin/sh

      

       echo  "This  is  rc.sysinit"

 

       busybox  mount  -t  proc  none  /proc

       

       # 加载系统日志

       /sbin/syslogd

       /sbin/klogd


       #开启老式的PTY,以支持telnet

       busybox  mkdir  /dev/pts

       busybox  mknod  /dev/ptmx  c  5   2

       busybox  mount  -t   devpts  devpts  /dev/pts

       这个简单的脚本首先挂载proc文件系统,接着启动系统日志,借此我们可以记录系统的运行信息。在系统出错时,这些系统日志特别有用。脚本中的最后一些条目用于开启对UNIX  PTY子系统的支持,这个例子中使用的Telnet服务器在实现功能时需要该子系统

       下面的代码清单是在运行级别2的启动脚本中使用的命令。这个脚本中包含的命令用于开启我们需要的服务

       运行级别2的启动脚本示例

       #!/bin/sh

      

       echo  "This  is  runlv2.startup"

      

       echo  "Starting  Internet  Superserver"

       inetd


       echo "Starting  web  server"

       webs  &

       这个运行级别2的启动脚本很简单。首先我们开启了所谓的因特网超级服务器inetd,它会拦截常见的TCP/IP请求并启动相应的服务。本例中,启用Telnet服务的配置文件是/etc/inted.conf;接着执行Web服务器程序webs。虽然很简单但这是一个可以正常工作的脚本,能够启动Telnet和Web服务。为了完成整个配置,还需要提供一个关机脚本,用于在系统关机之前关闭web服务器和因特网超级服务器。在这个简单的场景中,对于正确的关机,这些操作已经足够了。

      初始RAM磁盘

       LINUX内核中包含了两种挂载早期根文件系统的机制,用于执行某些和启动相关的系统初始化及配置。我们先来讨论老式方法,即初始RAM磁盘或initrd。

       初始RAM磁盘,或简称为initrd,是一种用于启动早期用户空间处理流程的老式方法。对这个功能的支持必须编译至内核中。编译内核时,相关的选项可以在内核配置工具中找到,具体位置是General  Setup中的ARM  disk  support选项。

       初始RAM磁盘是一个功能完备的小型根文件系统,它通常包含一些指令,用于在系统引导完成之前加载一些特定的设备驱动程序。比如,在linux工作站发行版中,初始RAM磁盘的作用就是在挂载真正的根文件系统之前加载EXT3文件系统的设备驱动程序。Initrd一般用于加载访问真正的根文件系统必须的设备驱动程序。

       使用initrd进行引导

      为了使用initrd的功能,大多数架构的引导加载程序会将initrd镜像传递给内核。常见的场景是,引导加载程序先将压缩过的内核镜像加载到内存中,接着将initrd镜像加载到另一段可用的内存中。在这个工程中,引导加载程序负责将控制权转交给内核之前,将initrd镜像的加载地址传递给内核。具体的机制取决于架构、引导加载程序和平台的实现。然而,内核必须知道initrd镜像的位置才能够加载它。

       有些架构和镜像会构造单个合成的二进制镜像。当引导加载程序所引导的linux不支持加载initrd镜像时就会采用这种方式。在这种情况下,内核和initrd镜像只是简单的拼接在一起,形成一个合成的镜像。可以在内核的makefile中找到对这种合成镜像的引用,名为bootpImage。目前,只有ARM架构使用了这种方式。

       那么,内核怎么知道哪里可以找到initrd镜像呢?除非引导加载程序里做了特别处理,通常情况下,使用内核命令行就可以将initrd镜像的起始地址和大小传递给内核。下面是一个在采用TI  OMAP 5912处理器的ARM参考板上使用内核命令行的例子:

       console=ttyS0,  115200  root=/dev/nfs\

               nfsroot=192.168.1.9:/home/chris/sandbox/omap-target\

               initrd=0x10800000,0x14af47

        在设备ttyS0上指定一个控制台,波特率为115200

        通过NFS(网络文件系统)挂载根文件系统

        在主机192.168.1.9(目录为/home/chris/sandbox/omap-target)上找到NFS根文件系统

        加载并挂载初始RAM磁盘,物理内存地址为0x10800000,大小为0x14af47(一般指压缩后的大小)

       引导加载程序对initrd的支持

       下面来看一个简单的例子,它基于流行的U-Boot引导加载程序并运行在ARM处理器之上。U-Boot能够直接引导linux内核,并且能够在引导内核镜像时包含initrd镜像。

        ramdisk支持的内核引导

        [uboot]>tftp  0x10000000  kernel-uImage

        ...

        Load  address:0x10000000

        Loading:#################done

        Bytes  transferred = 1069092(105024  hex)

       

        [uboot]>tftp  0x10800000  initrd-uboot

        ...

        Loading  address:0x10800000

        Loading:###################################done

        Bytes  transferred = 282575(44fcf  hex)

       

        [uboot]>bootm  0x10000000  0x10800040

        Uncompressing  kernel..............done.

        ...

        RAMDISK  driver  initialized: 16  RAM  disks  of  16384K  size  1024  blocksize

        ...

        RAMDISK:Compressed  image  found  at  block  0

        VFS:Mounted  root(ext2  filesystem),

        Greetings:this  is  linuxrc  from  Initial  RAMDisk

        Mounting  /proc  filesystem

       

        BusyBox  v1.00(2015.03.14-16:37+0000)Built-in  shell(ash)

        Enter  'help' for  a  list  of  built-in  commands.

        #(<<<<Busybox命令提示符)

       tftp命令指示U-Boot从TFTP服务器上下载内核镜像。内核镜像被下载下来后,他会被放到目标系统内存的基地址处,地址值为256MB(十六进制表示为0x10000000)。接着,另一个镜像即初始RAM磁盘镜像,也被下载下来并存放到一个较高的内存地址处(256MB+8MB)。最后我们运行U-Boot的bootm命令,这个命令的含义是“从内存引导”bootm有两个参数,一个是linux内核镜像的地址,另一个参数时可选的,如果有的话,指初始RAM磁盘镜像的地址。

      U-Boot引导加载程序的一个特性:它完全支持通过以太网加载内核镜像和ramdisk镜像,这个特性非常有利于开发。也可以使用别的方法将内核和ramdisk镜像加载到目标板上。也可以使用基于硬件的闪存编程工具将这些镜像烧写到闪存中,或者可以使用一个串行端口,通过RS-232下载内核和文件系统镜像。然而,由于这些镜像一般都比较大使用这种基于TFTP下载方式,会节省大量的开发时间。无论采用哪种引导加载程序,确保它支持通过网络下载镜像。

     initrd的奥秘所在:linuxrc

     当内核引导时,它首先会检测initrd镜像是否存在。然后,它会将这个压缩的二进制文件从内存中的指定位置复制到一个合适的内核ramdisk中,并挂载它作为根文件系统。initrd的奥秘来自一个特殊文件的内容,而这个文件就存储在initrd镜像中。当内核挂载初始的ramdisk时,它会查找一个名为linuxrc的特殊文件。它将这个文件当作一个脚本文件并执行其中包含的命令。这种机制允许设计者控制initrd的行为。下面代码显示了一个Linuxrc文件的内容

       linuxrc文件示例

       #!/bin/sh


       echo  'Greetings:  this  is  'linuxrc'  from  Initial  Ramdisk'

       echo  'Mounting  /proc  filesystem'

       mount  -t  proc  /proc/proc


       busybox  sh

       实际上,这个文件会包含一些命令,而他们需要在挂载真正的根文件系统之前执行。举例来说,该文件可能会包含一条加载CompactFlash驱动程序的命令,以便从CompactFlash设备上获取一个真正的根文件系统。在简单的inittab那个例子中,仅仅创建了一个busybox  shell,并且暂停了引导过程以便检查。你可以从ramdisk支持的内核引导程序中看到由busybox  shell生成的#命令行提示符。如果在此输入exit命令,内核会继续其引导过程直至完成。

       当内核将ramdisk(initrd镜像)从物理内存复制到一个内核ramdisk之后,它会将这块物理内存归还到系统的可用内存池中。你可以认为这是将initrd镜像转移了一下,从物理内存的一个固定地址转移到内核自身的虚拟内存中(形式是一个内核ramdisk设备)。

       挂载根文件系统使用了mount命令似乎多了一个/proc,下面这个命令也是有效的

                mount  -t  proc  none  /proc

       mount命令会忽略device字段,因为没有任何物理设备和proc文件系统相关联。命令中由-t  proc就足够了,这会指示mount将/proc文件系统挂载/proc挂载点(目录)上。使用前一种命令是为了说明我们实际上是将一个内核伪设备(/proc)挂载到/proc上。mount命令会忽略device参数,你可以选择自己喜欢的方式。使用前一种命令形式挂载成功后,在命令行中输入mount时,输出信息中的device字段会显示为proc,而不是none,这就会提醒你这是一个虚拟文件系统。

       initrd探究

      作为linux引导过程的一部分,内核必须找到并挂载一个根文件系统。在引导过程的后期,内核通过函数prepare_namespace()决定要挂载的文件系统及挂载点,这个函数位于文件.../init/do_mount.c中。如果内核支持initrd并且内核命令行也按此进行配置的,内核会解压物理内存中的initrd镜像,并最终将这个文件的内容复制到一个ramdisk设备中。这时,我们拥有了一个位于内核ramdisk中的合适的文件系统。当文件系统被读入到ramdisk中后,内核实际上会挂载ramdisk设备作为其根文件系统。最后,内核生成一个内核线程,用以执行initrd镜像中的linuxrc文件。

       当linuxrc脚本执行完毕后,内核会卸载initrd,并继续执行系统引导的最后一些步骤。如果真正的根设备中有一个名为/initrd的目录,linux会将initrd文件系统挂载到这个路径上(称为挂载点)。如果最终这个根文件系统不包含这个目录,initrd镜像就简单的被丢弃了。

       如果内核命令行中包含参数root=参数并指定一个ramdisk(root=/dev/ram0),那么前面描述的initrd的行为会发生两个重要的改变。首先,linuxrc文件将不会得到处理。其此,内核不会尝试挂载另一个文件系统作为其根文件系统。这意味着你可以拥有一个linux系统,其中initrd时它唯一的根文件系统。这对于小型的系统配置很有用,这类系统中唯一的根文件系统就是ramdisk。如果在内核命令行中指定/dev/ram0,当整个系统初始化完成后,initrd就会称为最终的根文件系统。

      构造initrd镜像

      开发嵌入式系统会遇到一些挑战,其中之一就是创建合适的根文件系统镜像。创建合适的initrd镜像就更具有挑战性了,因为我们需要限定它的大小并专门制作。这一节将研究initrd的需求以及其文件系统的内容。

       下列代码清单是运行tree命令显示本章示例initrd镜像的内容

        .

        --bin

              --busybox

              --echo->busybox

              --mount->busybox

              --sh->busybox

        --dev

              --console

              --ram0

              --ttyS0

        --etc

        --linuxrc

        --proc

        initrd基于busybox具有很多功能。这个例子中的busybox是静态连接的,不依赖于任何系统程序库。

      使用initramfs

      执行早期的用户空间程序还有一种更好的机制,就是使用initramfs。从概念上将它类似于initrd。它的作用也是类似的:挂载真正的根文件系统前加载一些必须的设备驱动程序。然而,它与initrd的机制有很多不同。

       initramfs是在调用do_basic_setup()之前加载的,这就提供了一种在加载设备驱动程序之前加载固件的机制。可以参考linux内核代码关于这个子系统的文档。.../Documentation/filesystem/ramfs-rootfs-initramfs.txt。

       initramfs是一个cpio格式的档案文件,而initrd是文件系统镜像。无需称为root用户就可以创建initramfs。它已经集成到linux内核源码树中了,当构建内核镜像时,会自动创建一个默认小型initramfs镜像。改动这个小型镜像要比构件和加载新的initrd镜像容易的多。

       下面代码显示了linux内核源码树.../usr目录的内容,initramfs镜像就是在这里构建的。

       total  72

       -rw-r--r--  1  chris  chris  1146  2009-12-16  12:36  built-in.o

       -rwxr-xr-x  1 chris  chris  15567  2009-12-16  12:36  gen_init_cpio         

      -rwxr-xr-x  1 chris  chris  12543  2009-12-16  12:36  gen_init_cpio.c

      -rwxr-xr-x  1 chris  chris   1024  2009-12-16  12:36  initramfs_data.bz2.s

       ...

       ...

       linux内核源码树的.../scripts目录中有一个名为gen_initramfs_list.sh的脚本文件,其中定义了哪些文件会默认包含在initramfs档案文件中。对于最新的linux内核,这些默认文件类似于下列代码所列出的文件

       dir  /dev  0755  0  0

       nod  /dev/console  0600  0  0  c  5   1

       dir  /root   0700   0   0

       这会生成一个小型的默认目录结构,其中包含/root和/dev两个顶层目录,还包含了一个单独的代表控制台的设备节点。内核文档.../Documentation/filesystem/ramfs-rootfs-initramfs中详细讲述了如何指定initramfs文件系统的组成部分。总而言之,上面的代码清单会生成一个名为/dev的目录项(dir),文件权限为0755,用户ID和组ID都是0(代表root用户)。第二行定义了一个名为/dev/console的设备节点(nod),文件权限为0600,用户ID和组ID都是0,它是一个字符设备,主设备号为5此设备号为1。第三行创建了一个目录名为/root。规格于/dev目录类似/

      定制initramfs

       由两种针对特定需求钉子initramfs的方法。一种方法是创建一个cpio格式的档案文件,其中包含了你所需的所有文件,另一种方法是指定一系列目录和文件,这些文件会和gen_initramfs_list.sh所创建的默认文件合并在一起。你可以通过内核配置工具来为initramfs指定一个文件源。在内核配置中开启INITRAMFS-SOURCE,并将它指向开发工作站上的一个目录。内核的构建系统会使用这个目录中的文件作为initranfs镜像的源文件。让我们使用一个最小的文件系统来研究以下它的机制。

        首先我们构造一个文件集合,其中包含了一个最小化系统所需的文件。initramfs应当是短小精干的,由此我们可以基于静态编译的busybox来构造它。静态编译busybox意味着它不依赖于任何系统程序库。除了busybox外,我们需要的文件很少:一个代表控制台的设备节点,它位于/dev。还有一个指向busybox的符号连接,名为init。最后我们需要包含一个busybox的启动脚本,用于系统启动后生成一个shell与我们交互。

        tree  ./usr/myinitramfs_root/

        .

        --bin

              --busybox

              --sh->busybox

        --dev

              --console

        --etc

              --init.d

                   --rcS

        --init->/bin/sh

       当我们将内核配置参数INITRAMFS_SOURCE指向这个目录时,内核会自动构建生成initramfs(这个会是个压缩的cpio档案文件),并且将他连接到内核镜像中。

       符号连接init的作用,当内核配置了initramfs时,它会在initramfs镜像的根目录中搜索一个名为/init的可执行文件。如果能够找到这个文件,内核会将他作为init进程执行,并设置它的PID为1.如果找不到这个文件,内核会忽略过initramfs。并继续进行常规的根文件系统处理。这一处理逻辑可以在源码文件.../init/main.c中找到。一个名为ramdisk_execute_command的字符指针中包含了指向这个初始化命令的指针,默认为指向“/init”。

       有一个名为rdinit=的内核命令行参数,如果设置了这个参数,那么以上字符指针就不再指向"/init"。这个参数的使用类似于init=。要使用这个参数,只需在内核命令行中添加它就行了。例如,在内核命令行中设置rdinit=/bin/sh就可以直接调用shell应用busybox。

      关机

       在设计嵌入式系统时,我们常常会忽略有序关机的重要性。不恰当的关机会影响启动时间,甚至会破坏某些文件系统。EXT2文件系统:当系统意外掉电后再启动时,fsck会花费大量的时间检查文件系统。对于哪些配备了大容量磁盘系统的服务器来说,使用fsck检查一些大的EXT2文件系统会花费几个小时。

       每个嵌入式系统往往都有自己的关机策略。使用于某个系统的策略并不一定使用于其他的系统。不同系统的关机规模也有所不同,复杂的系统会进行完整的SYSTEM  V方式关机。而简单的系统只使用一个简单的脚本来关机和重起。由几个Linux下的工具可以帮助实现关机操作,包括shutdown、halt和reboot.当然,前提是这些工具必须能够使用于你所选择的架构。

       一个关机脚本应该可以终止所有的用户空间系统。一般而言,关机进程首先会向所有进程发送SIGTERM信号,通知他们系统正在关机。关机进程会等待一小段时间,确保所有进程得以完成自身的关机操作,比如关闭文件、保存状态等。之后,关机进程会向所有进程发送SIGKILL信号,终止这些进程。关机进程还应该卸载所有已经挂载的文件系统。并调用与具体架构相关的关机或重起函数。linux的shutdown命令结合init完成这类行为。