友善之臂 mini2440 root_qtopia 文件系统启动过程分析
2010-05-06 21:49:45| 分类: 嵌入式点滴|字号 订阅
本文简介: 友善之臂提供的根文件系统十分具有创新意义,其功能之强大,先进,实用至今保持在领先地位, 网友 kasim 对其作了详尽的剖析, 道出了很多“秘密” 对于任何致力于嵌入式 Linux开发的人员是不可多得的好资料,现整理如下。
原文网址:http://www.arm9home.net/read.php?tid-1702.html
下面是这篇文章的内容,我们对此进行了简单的编辑和修改:
对于 mini2440 最新的 root_qtopia 文件系统启动过程,我在这里做了一些简单的分析,和大家分享一下经验,不足之处也请大家及时指出。
其实,虽然 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 的逻辑,它将生成默认的配置
复制代码
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. }
其中,最重要的一个,就是 new_init_action(SYSINIT, INIT_SCRIPT, ""),也就决定了
接下去初始化的脚本是 INIT_SCRIPT 所定义的值。这个宏的默认值是"/etc/init.d/rcS".
下面是文件系统中/etc/init.d/rcS 的内容, 也是我们要分析的重点
复制代码
1. #! /bin/sh
2.
3. PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:
4. runlevel=S
5. prevlevel=N
6. umask 022
7. export PATH runlevel prevlevel
8.
9. #
10. # Trap CTRL-C &c only in this shell so we can interrupt subprocesses.
11. #
12. trap ":" INT QUIT TSTP
13. /bin/hostname FriendlyARM
14.
15. /bin/mount -n -t proc none /proc
16. /bin/mount -n -t sysfs none /sys
17. /bin/mount -n -t usbfs none /proc/bus/usb
18. /bin/mount -t ramfs none /dev
19.
20. echo /sbin/mdev > /proc/sys/kernel/hotplug
21. /sbin/mdev -s
22. /bin/hotplug
23. # mounting file system specified in /etc/fstab
24. mkdir -p /dev/pts
25. mkdir -p /dev/shm
26. /bin/mount -n -t devpts none /dev/pts -o mode=0622
27. /bin/mount -n -t tmpfs tmpfs /dev/shm
28. /bin/mount -n -t ramfs none /tmp
29. /bin/mount -n -t ramfs none /var
30. mkdir -p /var/empty
31. mkdir -p /var/log
32. mkdir -p /var/lock
33. mkdir -p /var/run
34. mkdir -p /var/tmp
35.
36. /sbin/hwclock -s
37.
38. syslogd
39. /etc/rc.d/init.d/netd start
40. echo " " > /dev/tty1
41. echo "Starting networking..." > /dev/tty1
42. sleep 1
43. /etc/rc.d/init.d/httpd start
44. echo " " > /dev/tty1
45. echo "Starting web server..." > /dev/tty1
46. sleep 1
47. /etc/rc.d/init.d/leds start
48. echo " " > /dev/tty1
49. echo "Starting leds service..." > /dev/tty1
50. echo " "
51. sleep 1
52.
53. /sbin/ifconfig lo 127.0.0.1
54. /etc/init.d/ifconfig-eth0
55.
56. /bin/qtopia &
57. echo " " > /dev/tty1
58. echo "Starting Qtopia, please waiting..." > /dev/tty1
下面就逐个来分析:
复制代码
1. PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:
2. runlevel=S
3. prevlevel=N
4. umask 022
5. export PATH runlevel prevlevel
为启动环境设置必要的环境变量:
复制代码
1. /bin/hostname FriendlyARM
设置机器名字;:
复制代码
1. /bin/mount -n -t proc none /proc
2. /bin/mount -n -t sysfs none /sys
3. /bin/mount -n -t usbfs none /proc/bus/usb
4. /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。
其实,要搞清楚“程序”这种东西,没有什么好的办法,无非两个东西,一是看源代码,二是看脚本(其实还是一个东西,看脚本也是为了看脚本调用了什么程序
复制代码
1. echo /sbin/mdev > /proc/sys/kernel/hotplug
2. /sbin/mdev -s
3. /bin/hotplug
这几个就是用来完成我上面所说的两个东西: 1)通过 mdev -s 在/dev 目录下建立必要的设备节点; 2)设置内核的 hotplug handler 为 mdev, 即当设备热插拔时,由 mdev 接收来自内核的消息并作出相应的回应, 比如挂载 U 盘。
对于 mdev,需要注意的是,文件系统里存在/etc/mdev.conf 文件,它包含了 mdev 的配置信息。通过这个文件,我们可以自定义一些设备节点的名称或链接来满足特定的需要。这是 root qtopia 中 mdev.conf 的内容:
复制代码
1. # system all-writable devices
2. full 0:0 0666
3. null 0:0 0666
4. ptmx 0:0 0666
5. random 0:0 0666
6. tty 0:0 0666
7. zero 0:0 0666
8.
9. # console devices
10. tty[0-9]* 0:5 0660
11. vc/[0-9]* 0:5 0660
12.
13. # serial port devices
14.
s3c2410_serial0 0:5 0666 =ttySAC0
15. s3c2410_serial1 0:5 0666 =ttySAC1
16. s3c2410_serial2 0:5 0666 =ttySAC2
17. s3c2410_serial3 0:5 0666 =ttySAC3
18.
19. # loop devices
20. loop[0-9]* 0:0 0660 =loop/
21.
22. # i2c devices
23. i2c-0 0:0 0666 =i2c/0
24. i2c-1 0:0 0666 =i2c/1
25.
26. # frame buffer devices
27. fb[0-9] 0:0 0666
28.
29. # input devices
30. mice 0:0 0660 =input/
31. mouse.* 0:0 0660 =input/
32. event.* 0:0 0660 =input/
33. ts.* 0:0 0660 =input/
34.
35. # rtc devices
36. rtc0 0:0 0644 >rtc
37. rtc[1-9] 0:0 0644
38.
39. # misc devices
40. mmcblk0p1 0:0 0600 =sdcard */bin/hotplug
41. 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 盘的兄弟知道原因了吧...)
复制代码
1. # mounting file system specified in /etc/fstab
2. mkdir -p /dev/pts
3. mkdir -p /dev/shm
4. /bin/mount -n -t devpts none /dev/pts -o mode=0622
5. /bin/mount -n -t tmpfs tmpfs /dev/shm
6. /bin/mount -n -t ramfs none /tmp
7. /bin/mount -n -t ramfs none /var
8. mkdir -p /var/empty
9. mkdir -p /var/log
10. mkdir -p /var/lock
11. mkdir -p /var/run
12. mkdir -p /var/tmp
就像注释中所说的,这是用来挂载其他一些常用的文件系统,并在/var 目录下(同样是ramfs,可写的)新建必要的目录。
复制代码
1. /sbin/hwclock -s
用来设定系统时间的,从硬件 RTC 中获取,要获取正确的时间,必须先设置好正确的时间(如何设置 RTC 见用户手册说明),目前友善之臂的开发板出厂时并没有设置实际的时间,而是系统默认的。
接下来就是启动系统服务了,包括 log 记录,网络, http server 和自定义的"跑马灯服务"...
关于 mdev.conf 中的一点补充说明:
复制代码
1. # misc devices
2. mmcblk0p1 0:0 0600 =sdcard */bin/hotplug
3. 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 ; ?
复制代码
1. syslogd
2. /etc/rc.d/init.d/netd start
3. echo " " > /dev/tty1
4. echo "Starting networking..." > /dev/tty1
5. sleep 1
6. /etc/rc.d/init.d/httpd start
7. echo " " > /dev/tty1
8. echo "Starting web server..." > /dev/tty1
9. sleep 1
10. /etc/rc.d/init.d/leds start
11. echo " " > /dev/tty1
12. echo "Starting leds service..." > /dev/tty1
13. echo " "
14. sleep 1
启动一系列服务:
syslog - 用于记录内核和应用程序 debug 信息
inetd, 一个挂载启动各种网络相关服务的看守进程
netd -
http server 看守进程
httpd -
跑马灯看守进程
leds -
其中,inetd 的配置文件为/etc/inetd.conf,这是文件内容
复制代码
1. # /etc/inetd.conf: see inetd(8) for further informations.
2. echo stream tcp nowait root internal
3. echo dgram udp wait root internal
4. daytime stream tcp nowait root internal
5. daytime dgram udp wait root internal
6. time stream tcp nowait root internal
7. time dgram udp wait root internal
8.
9. # These are standard services.
10. #
11. ftp stream tcp nowait root /usr/sbin/ftpd /usr/sbin/ftpd
12. telnet stream tcp nowait root /usr/sbin/telnetd /usr/sbin/telnetd -i
可以看到,这里启动的网络服务有两个: 1)ftp server 和 2)telnet server。有关网络服务的端口和协议等具体信息,可以参考/etc/services, /etc/protocols
再接下来
复制代码
1. /sbin/ifconfig lo 127.0.0.1
2. /etc/init.d/ifconfig-eth0
配置网络设备(网卡):
1)设定本机回环地址为 127.0.0.1
2)运行网卡设置脚本/etc/init.d/ifconfig-eth0
这是/etc/init.d/ifconfig-eth0 的内容, 加入了我的一些注释
复制代码
1. #!/bin/sh
2.
3. echo -n Try to bring eth0 interface up......>/dev/ttySAC0
4.
#判断/etc/eth0-setting 文件是否存在
5.
6. if [ -f /etc/eth0-setting ] ; then
7. #读取配置文件信息
8. source /etc/eth0-setting
9.
10. #如果根文件系统为 nfs,则说明网卡已经配置 OK,这里什么都不需要配置了
11. if grep -q "^/dev/root / nfs " /etc/mtab ; then
12. echo -n NFS root ... > /dev/ttySAC0
#否则,根据配置文件中的 MAC, IP, Mask 和 Gateway 通过 ifconfig 命令相应地配置网卡
13.
14. else
15. ifconfig eth0 down
16. ifconfig eth0 hw ether $MAC
17. ifconfig eth0 $IP netmask $Mask up
18. route add default gw $Gateway
19. fi
20.
#将配置文件中的 DNS 设置写入/etc/resolv.conf 使之生效
21.
22. echo nameserver $DNS > /etc/resolv.conf
23. #配置文件不存在,使用默认配置
24. else
25.
26. #如果根文件系统为 nfs,则说明网卡已经配置 OK,这里什么都不需要配置了
27. if grep -q "^/dev/root / nfs " /etc/mtab ; then
28. echo -n NFS root ... > /dev/ttySAC0
29. else
#将网卡的 IP 地址设定为 192.168.1.230
30.
31. /sbin/ifconfig eth0 192.168.1.230 netmask 255.255.255.0 up
32. fi
33. fi
34.
35. echo Done > /dev/ttySAC0
可以看到,NFS 自动识别就是靠判断/etc/mtab 中是否有 nfs 的挂载记录实现的。
这是 root qtopia 文件系统中/etc/eth0-settings 文件
复制代码
1. IP=192.168.1.230
2. Mask=255.255.255.0
3. Gateway=192.168.1.1
4. DNS=192.168.1.1
5. MAC=08:90:90:90:90:90
终于到最后了,启动 Qtopia GUI 环境
复制代码
1. /bin/qtopia &
2. echo " " > /dev/tty1
3. echo "Starting Qtopia, please waiting..." > /dev/tty1
可以看到,这里 Qtopia 是通过运行/bin/qtopia 来启动的。事实上,/bin/qtopia 也是一个脚本,它的任务是设定 Qtopia 运行必要的环境, 最后通过调用 qpe 可执行文件真正启动Qtopia。这是它的全部内容,我加入了一些注释:
复制代码
1. #!/bin/sh
2.
#
tslib 环境变量设置,包括了 touchscreen 设备文件,tslib 配置文件,tslib plug-in 位置和 touchscreen 校准数据文件.
3
4. export TSLIB_TSDEVICE=/dev/input/event0
5. export TSLIB_CONFFILE=/usr/local/etc/ts.conf
6. export TSLIB_PLUGINDIR=/usr/local/lib/ts
7. export TSLIB_CALIBFILE=/etc/pointercal
#Qtopia 环境变量设置,设定了 Qtopia 主要文件位置
8.
9. export QTDIR=/opt/Qtopia
10. export QPEDIR=/opt/Qtopia
11.
#设定 PATH 和 LD_LIBRARY_PATH 以包含 Qtopia 的可执行文件和共享库文件,方便 Qtopia 正确运行
12. export PATH=$QTDIR/bin:$PATH
13. export LD_LIBRARY_PATH=$QTDIR/lib:/usr/local/lib:$LD_LIBRARY_PATH
14.
15.
#通过判断/sys/devices/virtual/input/input0/uevent 中是否包含 touchscreen 信息使 Qtopia 自动识别touchscreen
和 USB 鼠标
16. TS_INFO_FILE=/sys/devices/virtual/input/input0/uevent
17. if [
-e $TS_INFO_FILE
-a "/bin/grep
-q TouchScreen < $TS_INFO_FILE" ]; then
18. export QWS_MOUSE_PROTO="TPanel:/dev/input/event0 USB:/dev/input/mice"
19. if [
-e /etc/pointercal
-a ! -s /etc/pointercal ] ; then
20. rm /etc/pointercal
21. fi
------ -e 存在 -s 存在,且非空档 !-s 就是空档的意思 -a
双方都成立 grep -q
静默模式, 不输出任何结果(stderr 除外. 常用以获取 return value, 符合为 true, 否则为 false .)
22. else
23. export QWS_MOUSE_PROTO="USB:/dev/input/mice"
24. >/etc/pointercal
25. fi
26. unset TS_INFO_FILE
27.
28. export QWS_KEYBOARD=TTY:/dev/tty1
29. export KDEDIR=/opt/kde
30.
31. export HOME=/root
32.
33. #通过调用/opt/Qtopia/bin/qpe 真正启动 Qtopia
34. exec $QPEDIR/bin/qpe
1>/dev/null
2>/dev/null
---------- '1' 标准输出 '2' 标准错误输出
到此为止,文件系统从初始化到最终启动 Qtopia GUI 环境的全部过程就结束了,大家可以看到,友善之臂的“小秘密”其实都在这里,说穿了很简单:)只要大家能够静下心来认真看看脚本,看看源代码,加上一些背景知识的了解,搞清楚一个嵌入式系统就这么简单