虽然root_qtopia这个文件系统的GUI是基于Qtopia的,但其初始化启动过程却是由大部分由busybox完成,Qtopia(qpe)只是在启动的最后阶段被开启。
由于默认的内核命令行上有init=/linuxrc, 因此,在文件系统被挂载后,运行的第一个程序是根目录下的linuxrc。 这是一个指向/bin/busybox的链接,也就是说,系统起来后运行的第一个程序也就是busybox本身。
这 种情况下,busybox首先将试图解析/etc/inittab来获取进一步的初始化配置信息(参考busybox源代码init/init.c中的 parse_inittab()函数)。而事实上,root_qtopia中并没有/etc/inittab这个配置文件,根据busybox的逻辑,它 将生成默认的配置
- static void parse_inittab(void)
- {
- #if ENABLE_FEATURE_USE_INITTAB
- char *token[4];
- parser_t *parser = config_open2("/etc/inittab", fopen_for_read);
- if (parser == NULL)
- #endif
- {
- /* No inittab file -- set up some default behavior */
- /* Reboot on Ctrl-Alt-Del */
- new_init_action(CTRLALTDEL, "reboot", "");
- /* Umount all filesystems on halt/reboot */
- new_init_action(SHUTDOWN, "umount -a -r", "");
- /* Swapoff on halt/reboot */
- if (ENABLE_SWAPONOFF)
- new_init_action(SHUTDOWN, "swapoff -a", "");
- /* Prepare to restart init when a QUIT is received */
- new_init_action(RESTART, "init", "");
- /* Askfirst shell on tty1-4 */
- new_init_action(ASKFIRST, bb_default_login_shell, "");
- //TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users
- new_init_action(ASKFIRST, bb_default_login_shell, VC_2);
- new_init_action(ASKFIRST, bb_default_login_shell, VC_3);
- new_init_action(ASKFIRST, bb_default_login_shell, VC_4);
- /* sysinit */
- new_init_action(SYSINIT, INIT_SCRIPT, "");
- return;
- }
其中, 最重要的一个,就是new_init_action(SYSINIT, INIT_SCRIPT, ""), 也就决定了接下去初始化的脚本是INIT_SCRIPT所定义的值。这个宏的默认值是"/etc/init.d/rcS".
下面是文件系统中/etc/init.d/rcS的内容, 也是我们要分析的重点
- #! /bin/sh
- PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:
- runlevel=S
- prevlevel=N
- umask 022
- export PATH runlevel prevlevel
- #
- # Trap CTRL-C &c only in this shell so we can interrupt subprocesses.
- #
- trap ":" INT QUIT TSTP
- /bin/hostname FriendlyARM
- /bin/mount -n -t proc none /proc
- /bin/mount -n -t sysfs none /sys
- /bin/mount -n -t usbfs none /proc/bus/usb
- /bin/mount -t ramfs none /dev
- echo /sbin/mdev > /proc/sys/kernel/hotplug
- /sbin/mdev -s
- /bin/hotplug
- # mounting file system specified in /etc/fstab
- mkdir -p /dev/pts
- mkdir -p /dev/shm
- /bin/mount -n -t devpts none /dev/pts -o mode=0622
- /bin/mount -n -t tmpfs tmpfs /dev/shm
- /bin/mount -n -t ramfs none /tmp
- /bin/mount -n -t ramfs none /var
- mkdir -p /var/empty
- mkdir -p /var/log
- mkdir -p /var/lock
- mkdir -p /var/run
- mkdir -p /var/tmp
- /sbin/hwclock -s
- syslogd
- /etc/rc.d/init.d/netd start
- echo " " > /dev/tty1
- echo "Starting networking..." > /dev/tty1
- sleep 1
- /etc/rc.d/init.d/httpd start
- echo " " > /dev/tty1
- echo "Starting web server..." > /dev/tty1
- sleep 1
- /etc/rc.d/init.d/leds start
- echo " " > /dev/tty1
- echo "Starting leds service..." > /dev/tty1
- echo " "
- sleep 1
- /sbin/ifconfig lo 127.0.0.1
- /etc/init.d/ifconfig-eth0
- /bin/qtopia &
- echo " " > /dev/tty1
- echo "Starting Qtopia, please waiting..." > /dev/tty1
下面就逐个来分析:
- PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:
- runlevel=S
- prevlevel=N
- umask 022
- export PATH runlevel prevlevel
为启动环境设置必要的环境变量;
- /bin/hostname FriendlyARM
设置机器名字;
- /bin/mount -n -t proc none /proc
- /bin/mount -n -t sysfs none /sys
- /bin/mount -n -t usbfs none /proc/bus/usb
- /bin/mount -t ramfs none /dev
挂载“虚拟”文件系统,/proc, /sys,并且在/dev目录上挂载一个ramfs,相当于把原本NAND Flash上的只读的/dev目录“覆盖”上一块可写的空的SDRAM。
这里要注意的是,/sys和挂载了ramfs的/dev是正确创建设备节点的关键。对于2.6.29内核来说,已经没有了devfs的支持,创建设备节点只有通过两种办法由文件系统完成:
1) 制作文件系统镜像前用mknod手动创建好系统中所有的(包括可能有的)设备节点,并把这些节点文件一起做进文件系统镜像中;
2)在文件系统初始化过程中,通过/sys目录所输出的信息,在/dev目录下动态的创建系统中当前实际有的设备节点。
显 然,方法1)有很大的局限性,仅限于没有设备动态增加或减少的情况,不适用于很多设备热插拔的情况,比如U盘,SD卡等等。方法2)是目前大多数PC上 Linux的做法(基于udev实现)。这种方法有两个前提: /sys目录挂载和一个可写的/dev目录。这也就是为什么我们在这里需要挂载/sys和ramfs在/dev目录上。事实上,这种方法最早就是为热插拔 设计的,你可以理解为当系统启动是,所有设备一下子全部“插入”了进来。
这里有一点要说明的是,在文件系统初始化跑到这里之前,原本的/dev目录下必须有一个的设备节点:/dev/console。
- echo /sbin/mdev > /proc/sys/kernel/hotplug
- /sbin/mdev -s
- /bin/hotplug
这几个就是用来完成我上面所说的两个东西: 1)通过mdev -s 在/dev目录下建立必要的设备节点; 2)设置内核的hotplug handler为mdev, 即当设备热插拔时,由mdev接收来自内核的消息并作出相应的回应, 比如挂载U盘。
对于mdev,需要注意的是,文件系统里存在/etc/mdev.conf文件,它包含了mdev的配置信息。通过这个文件,我们可以自定义一些设备节点的名称或链接来满足特定的需要。这是root qtopia中mdev.conf的内容:
- # system all-writable devices
- full 0:0 0666
- null 0:0 0666
- ptmx 0:0 0666
- random 0:0 0666
- tty 0:0 0666
- zero 0:0 0666
- # console devices
- tty[0-9]* 0:5 0660
- vc/[0-9]* 0:5 0660
- # serial port devices
- s3c2410_serial0 0:5 0666 =ttySAC0
- s3c2410_serial1 0:5 0666 =ttySAC1
- s3c2410_serial2 0:5 0666 =ttySAC2
- s3c2410_serial3 0:5 0666 =ttySAC3
- # loop devices
- loop[0-9]* 0:0 0660 =loop/
- # i2c devices
- i2c-0 0:0 0666 =i2c/0
- i2c-1 0:0 0666 =i2c/1
- # frame buffer devices
- fb[0-9] 0:0 0666
- # input devices
- mice 0:0 0660 =input/
- mouse.* 0:0 0660 =input/
- event.* 0:0 0660 =input/
- ts.* 0:0 0660 =input/
- # rtc devices
- rtc0 0:0 0644 >rtc
- rtc[1-9] 0:0 0644
- # misc devices
- mmcblk0p1 0:0 0600 =sdcard */bin/hotplug
- sda1 0:0 0600 =udisk * /bin/hotplug
可 以看到,原本串口驱动注册的设备名是s3c2410_serial0, s3c2410_serial1和s3c2410_serial2,而mdev则会在/dev目录下对应生成ttySAC0, ttySAC1和ttySAC2以符合应用程序对于串口设备名称的习惯。同样的,/dev/sdcard和/dev/udisk永远分别指向SD卡和U盘 的第一个分区。(所以,用那些没有分区表的SD卡或U盘的兄弟知道原因了吧...)
- # mounting file system specified in /etc/fstab
- mkdir -p /dev/pts
- mkdir -p /dev/shm
- /bin/mount -n -t devpts none /dev/pts -o mode=0622
- /bin/mount -n -t tmpfs tmpfs /dev/shm
- /bin/mount -n -t ramfs none /tmp
- /bin/mount -n -t ramfs none /var
- mkdir -p /var/empty
- mkdir -p /var/log
- mkdir -p /var/lock
- mkdir -p /var/run
- mkdir -p /var/tmp
就像注释中所说的,这是用来挂载其他一些常用的文件系统,并在/var目录下(同样是ramfs,可写的)新建必要的目录。
- /sbin/hwclock -s
用来设定系统时间的,从硬件RTC中获取,不过似乎有问题
接下来就是启动系统服务了,包括log记录,网络, http server和自定义的"跑马灯服务"...
关于mdev.conf中的
- # misc devices
- mmcblk0p1 0:0 0600 =sdcard */bin/hotplug
- sda1 0:0 0600 =udisk * /bin/hotplug
一点补充说明:
这 两句配置的意思是当SD卡或者U盘插入/拔出时,将这个消息传递给自定义的热插拔handler, /bin/hotplug. 这个程序是友善之臂开发的用于自动挂载可移动设备的,目前是SD卡和U盘。它的逻辑很简单,将SD卡或者U盘的第一个分区作为FAT/FAT32挂载到 /sdcard或者/udisk.
但这也同时带来一个问题,当SD卡或者U盘上没有分区表或者第一个分区不是FAT/FAT32格式的时候,它就玩不转了,兄弟们要小心了:)
这是/bin/hotplug里的二进制数据片段,可以看到我上面说的逻辑:
000010d0h: 52 00 00 00 4D 00 00 00 00 00 00 00 41 00 00 00 ; R...M.......A...
000010e0h: 43 00 00 00 54 00 00 00 49 00 00 00 4F 00 00 00 ; C...T...I...O...
000010f0h: 4E 00 00 00 00 00 00 00 44 00 00 00 45 00 00 00 ; N.......D...E...
00001100h: 56 00 00 00 4E 00 00 00 41 00 00 00 4D 00 00 00 ; V...N...A...M...
00001110h: 45 00 00 00 00 00 00 00 61 00 00 00 64 00 00 00 ; E.......a...d...
00001120h: 64 00 00 00 00 00 00 00 72 00 00 00 65 00 00 00 ; d.......r...e...
00001130h: 6D 00 00 00 6F 00 00 00 76 00 00 00 65 00 00 00 ; m...o...v...e...
00001140h: 00 00 00 00 2F 00 00 00 64 00 00 00 65 00 00 00 ; ..../...d...e...
00001150h: 76 00 00 00 2F 00 00 00 75 00 00 00 64 00 00 00 ; v.../...u...d...
00001160h: 69 00 00 00 73 00 00 00 6B 00 00 00 00 00 00 00 ; i...s...k.......
00001170h: 2F 00 00 00 64 00 00 00 65 00 00 00 76 00 00 00 ; /...d...e...v...
00001180h: 2F 00 00 00 73 00 00 00 64 00 00 00 63 00 00 00 ; /...s...d...c...
00001190h: 61 00 00 00 72 00 00 00 64 00 00 00 00 00 00 00 ; a...r...d.......
000011a0h: 4D 00 00 00 44 00 00 00 45 00 00 00 56 00 00 00 ; M...D...E...V...
000011b0h: 00 00 00 00 6D 00 00 00 6D 00 00 00 63 00 00 00 ; ....m...m...c...
000011c0h: 62 00 00 00 6C 00 00 00 6B 00 00 00 30 00 00 00 ; b...l...k...0...
000011d0h: 70 00 00 00 31 00 00 00 00 00 00 00 73 00 00 00 ; p...1.......s...
000011e0h: 64 00 00 00 61 00 00 00 31 00 00 00 00 00 00 00 ; d...a...1.......
000011f0h: 76 00 00 00 66 00 00 00 61 00 00 00 74 00 00 00 ; v...f...a...t...
00001200h: 00 00 00 00 2F 00 00 00 64 00 00 00 65 00 00 00 ; ..../...d...e...
00001210h: 76 00 00 00 2F 00 00 00 77 00 00 00 61 00 00 00 ; v.../...w...a...
00001220h: 74 00 00 00 63 00 00 00 68 00 00 00 64 00 00 00 ; t...c...h...d...
00001230h: 6F 00 00 00 67 00 00 00 00 00 00 00 9A B2 01 81 ; o...g.......毑.?
00001240h: B0 ; ?
- syslogd
- /etc/rc.d/init.d/netd start
- echo " " > /dev/tty1
- echo "Starting networking..." > /dev/tty1
- sleep 1
- /etc/rc.d/init.d/httpd start
- echo " " > /dev/tty1
- echo "Starting web server..." > /dev/tty1
- sleep 1
- /etc/rc.d/init.d/leds start
- echo " " > /dev/tty1
- echo "Starting leds service..." > /dev/tty1
- echo " "
- sleep 1
启动一系列服务:
syslog - 用于记录内核和应用程序debug信息
netd - inetd, 一个挂载启动各种网络相关服务的看守进程
httpd - http server看守进程
leds - 跑马灯看守进程
其中,inetd的配置文件为/etc/inetd.conf,这是文件内容
- # /etc/inetd.conf: see inetd(8) for further informations.
- echo stream tcp nowait root internal
- echo dgram udp wait root internal
- daytime stream tcp nowait root internal
- daytime dgram udp wait root internal
- time stream tcp nowait root internal
- time dgram udp wait root internal
- # These are standard services.
- #
- ftp stream tcp nowait root /usr/sbin/ftpd /usr/sbin/ftpd
- telnet stream tcp nowait root /usr/sbin/telnetd /usr/sbin/telnetd -i
可以看到,这里启动的网络服务有两个: 1)ftp server 和 2)telnet server。有关网络服务的端口和协议等具体信息,可以参考/etc/services, /etc/protocols
再接下来
- /sbin/ifconfig lo 127.0.0.1
- /etc/init.d/ifconfig-eth0
配置网络设备(网卡):
1)设定本机回环地址为127.0.0.1
2)运行网卡设置脚本/etc/init.d/ifconfig-eth0
这是/etc/init.d/ifconfig-eth0的内容, 加入了我的一些注释
- #!/bin/sh
- echo -n Try to bring eth0 interface up......>/dev/ttySAC0
- #判断/etc/eth0-setting文件是否存在
- if [ -f /etc/eth0-setting ] ; then
- #读取配置文件信息
- source /etc/eth0-setting
- #如果根文件系统为nfs,则说明网卡已经配置OK,这里什么都不需要配置了
- if grep -q "^/dev/root / nfs " /etc/mtab ; then
- echo -n NFS root ... > /dev/ttySAC0
- #否则,根据配置文件中的MAC, IP, Mask和Gateway通过ifconfig命令相应地配置网卡
- else
- ifconfig eth0 down
- ifconfig eth0 hw ether $MAC
- ifconfig eth0 $IP netmask $Mask up
- route add default gw $Gateway
- fi
- #将配置文件中的DNS设置写入/etc/resolv.conf使之生效
- echo nameserver $DNS > /etc/resolv.conf
- #配置文件不存在,使用默认配置
- else
- #如果根文件系统为nfs,则说明网卡已经配置OK,这里什么都不需要配置了
- if grep -q "^/dev/root / nfs " /etc/mtab ; then
- echo -n NFS root ... > /dev/ttySAC0
- else
- #将网卡的IP地址设定为192.168.1.230
- /sbin/ifconfig eth0 192.168.1.230 netmask 255.255.255.0 up
- fi
- fi
- echo Done > /dev/ttySAC0
可以看到,NFS自动识别就是靠判断/etc/mtab中是否有nfs的挂载记录实现的。
这是root qtopia文件系统中/etc/eth0-settings文件
- IP=192.168.1.230
- Mask=255.255.255.0
- Gateway=192.168.1.1
- DNS=192.168.1.1
- MAC=08:90:90:90:90:90
终于到最后了,启动Qtopia GUI环境
- /bin/qtopia &
- echo " " > /dev/tty1
- echo "Starting Qtopia, please waiting..." > /dev/tty1
可以看到,这里Qtopia是通过运行/bin/qtopia来启动的。事实上,/bin/qtopia也是一个脚本,它的任务是设定Qtopia运行必要的环境, 最后通过调用qpe可执行文件真正启动Qtopia。这是它的全部内容,我加入了一些注释:
- #!/bin/sh
- #tslib环境变量设置,包括了touchscreen设备文件,tslib配置文件,tslib plug-in位置和touchscreen校准数据文件
- export TSLIB_TSDEVICE=/dev/input/event0
- export TSLIB_CONFFILE=/usr/local/etc/ts.conf
- export TSLIB_PLUGINDIR=/usr/local/lib/ts
- export TSLIB_CALIBFILE=/etc/pointercal
- #Qtopia环境变量设置,设定了Qtopia主要文件位置
- export QTDIR=/opt/Qtopia
- export QPEDIR=/opt/Qtopia
- #设定PATH和LD_LIBRARY_PATH以包含Qtopia的可执行文件和共享库文件,方便Qtopia正确运行
- export PATH=$QTDIR/bin:$PATH
- export LD_LIBRARY_PATH=$QTDIR/lib:/usr/local/lib:$LD_LIBRARY_PATH
- #通过判断/sys/devices/virtual/input/input0/uevent中是否包含touchscreen信息使Qtopia自动识别touchscreen和USB鼠标
- TS_INFO_FILE=/sys/devices/virtual/input/input0/uevent
- if [ -e $TS_INFO_FILE -a "/bin/grep -q TouchScreen < $TS_INFO_FILE" ]; then
- export QWS_MOUSE_PROTO="TPanel:/dev/input/event0 USB:/dev/input/mice"
- if [ -e /etc/pointercal -a ! -s /etc/pointercal ] ; then
- rm /etc/pointercal
- fi
- else
- export QWS_MOUSE_PROTO="USB:/dev/input/mice"
- >/etc/pointercal
- fi
- unset TS_INFO_FILE
- export QWS_KEYBOARD=TTY:/dev/tty1
- export KDEDIR=/opt/kde
- export HOME=/root
- #通过调用/opt/Qtopia/bin/qpe真正启动Qtopia
- exec $QPEDIR/bin/qpe 1>/dev/null 2>/dev/null
到此为止,文件系统从初始化到最终启动Qtopia GUI环境的全部过程就结束了,大家可以看到,友善之臂的“小秘密”其实都在这里,说穿了很简单:)只要大家能够静下心来认真看看脚本,看看源代码,加上一些背景知识的了解,搞清楚一个嵌入式系统就这么简单
/////////////////////////////////////////
通常/linuxrc这个文件只有在
1. 使用了Initial Ramdisk (initrd)
2. 内核命令行上指定了init=/linuxrc
这 两种情况下才有用,mini2440的root_qtopia属于情况2), 在root_qtopia中,/linuxrc是指向/bin/busybox的符号链接,也就是说,整个文件系统的入口就变成了busybox的 main()函数,busybox支持这种方式来启动busybox本身和整个文件系统的初始化。