这几天学习Android,基于x86平台。先了解一下android的安装过程。在其官方网站上下载了Android的iSO,就解压出来看看,需要说明的是以下的操作都是在root用户下进行的。以下是学习过程中,从网上找到的文件,这篇文章的主要参考链接为:https://www.cnblogs.com/jjxxjnzy/archive/2013/10/14/3368068.html,感谢!
1 2 | mkdir android mount android-x86-4.3-20130725.iso android |
通过以上的命令可以把我们下载的android镜像挂载到android目录下,看一目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 | ├── initrd.img ├── install.img ├── isolinux │ ├── android-x86.png │ ├── boot.cat │ ├── isolinux.bin │ ├── isolinux.cfg │ ├── TRANS.TBL │ └── vesamenu.c32 ├── kernel ├── ramdisk.img ├── system .sfs └── TRANS.TBL |
对以上文件详细的了解可以参考csdn的这篇博文
接下来看一下install.img这个文件,我们用file命令看一下这个文件属性
1 2 | android# file install.img install.img: gzip compressed data, from Unix, last modified: Fri Jul 26 02:05:58 2013, max compression |
可以看到这个文件是一个gzip的文件,所以我们可以解压出来看看,命令如下:
1 2 3 4 5 | mkdir install cp /tmp/android/install.img /tmp/install mv install.img install.img.gz gunzip install.img.gz cpio -i -F install.img |
这个时候我们看下install这个目录的结构:
1 2 | install# ls bin grub install.img lib sbin scripts |
可以看出通过这个文件的解压多出了以下目录:
bin grub lib sbin scripts
不过通过以上的目录可以看出,基于x86结构的android是用grub引导的。接下来看一下他的启动流程,在看启动流程之前我们先看一下isolinux.cfg这个文件,这个文件的具体作用在上面的内容中已经说过,在此不在赘述,文件内容如下:
default vesamenu.c32
timeout 600
menu background android-x86.png
menu title Android-x86 Live & Installation CD 4.3-test
menu color border 0 #ffffffff #00000000
menu color sel 7 #ffffff00 #ff000000
menu color title 0 #ffffffff #00000000
menu color tabmsg 0 #ffffffff #00000000
menu color unsel 0 #ffffffff #00000000
menu color hotsel 0 #ffffff00 #ff000000
menu color hotkey 7 #ffffff00 #00000000
label livem
menu label Live CD - ^Run Android-x86 without installation
kernel /kernel
append initrd=/initrd.img root=/dev/ram0 androidboot.hardware=android_x86 video=-16 quiet SRC= DATA=
label vesa
menu label Live CD - ^VESA mode
kernel /kernel
append initrd=/initrd.img root=/dev/ram0 androidboot.hardware=android_x86 video=-16 quiet nomodeset vga=788 SRC= DATA=
label debug
menu label Live CD - ^Debug mode
kernel /kernel
append initrd=/initrd.img root=/dev/ram0 androidboot.hardware=android_x86 video=-16 vga=788 DEBUG=2 SRC= DATA=
label install
menu label Installation - ^Install Android-x86 to harddisk
kernel /kernel
append initrd=/initrd.img root=/dev/ram0 androidboot.hardware=android_x86 video=-16 INSTALL=1 DEBUG=
我们主要看下面的label这几个选项。一共有四个label选项,前面的两个不太熟悉,不过在这里不影响我们。第三个为debug模式,第四个为install模式,也就是android的安装,从加在这里的内核参数可以看出,系统启动的是initrd.img里的内容,那接下来我们就分析一下initrd.img 这个文件。我们用file命令看一下这个文件的属性就可以知道他和上文说的install.img的文件属性是一样的,我们用同样的方法把他解压出来,内容如下:
initrd# ls
android bin hd init lib mnt proc sbin scripts sfs sys tmp
以上就是把initrd.img文件解压出来的内容,系统的启动就是从这个地方开始,熟悉linux的朋友看到这个就知道是从那个文件开始执行了。接下来我们看一下init这个文件,这个文件是一个文本文件,可以看一下它的文件属性
1 2 | initrd# file init init: a /bin/busybox sh script, ASCII text executable |
下面可以看一下这个脚本的内容:
1 2 3 4 5 6 7 8 9 10 11 12 | echo -n Detecting Android-x86... mount -t tmpfs tmpfs /android cd /android while :; do for device in ${ROOT:-/dev/sr* /dev/[hs]d[a-z]*}; do check_root $device && break 2 mountpoint -q /mnt && umount /mnt done sleep 1 echo -n . done |
这个是脚本的初始函数,相当于c语言的mian函数,首先看到的地一个mount命令挂载了tmpfs文件系统,对这个不是很熟悉,等有时间了学习了一下这个在把这个补上。看看接下来while循环,在这一段内容里重点看一下 check_root,这个是定义的一个函数,这个函数定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | try_mount() { RW=$1; shift if [ "${ROOT#*:/}" != "$ROOT" ]; then # for NFS roots, use nolock to avoid dependency to portmapper RW= "nolock,$RW" fi # FIXME: any way to mount ntfs gracefully? mount -o $RW $@ || mount.ntfs-3g -o rw,force $@ } check_root() { try_mount ro $1 /mnt && [ -e /mnt/$SRC/ramdisk.img ] [ $? -ne 0 ] && return 1 zcat /mnt/$SRC/ramdisk.img | cpio -id > /dev/null if [ -e /mnt/$SRC/ system .sfs ]; then mount -o loop /mnt/$SRC/ system .sfs /sfs mount -o loop /sfs/ system .img system elif [ -e /mnt/$SRC/ system .img ]; then mount -o loop /mnt/$SRC/ system .img system elif [ -d /mnt/$SRC/ system ]; then remount_rw mount --bind /mnt/$SRC/ system system else rm -rf * return 1 fi mkdir cache mnt mnt/sdcard mount -t tmpfs tmpfs cache echo " found at $1" } |
这个函数的作用主要是挂载ramdisk.img和system.sfs,这两个文件可以看看我们最初始解压的文件。这里需要说明的是system.sfs这个文件解压出来是system.img。system.img里包含的就是具体的系统文件了。这个到后面会讲到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | ln -s mnt/$SRC /src ln -s android/ system / ln -s ../ system /lib/modules /lib ln -s ../ system /lib/firmware /lib if [ -n "$INSTALL" ]; then cd / zcat /src/install.img | cpio -iud > /dev/null fi if [ -x system /bin/ln -a \( -n "$DEBUG" -o -n "$BUSYBOX" \) ]; then mv /bin /lib . system /bin/ln -s android/lib /lib system /bin/ln -s android/bin /bin sed -i 's|\(PATH *\)\(/sbin\)|\1/bin:\2|' init.rc mv /sbin/* sbin rmdir /sbin ln -s android/sbin / fi # ensure keyboard driver is loaded [ -n "$INSTALL" -o -n "$DEBUG" ] && modprobe atkbd if [ 0$DEBUG -gt 0 ]; then echo -e "\nType 'exit' to continue booting...\n" debug_shell debug-found fi # load scripts for s in `ls /scripts/* /src/scripts/*`; do test -e "$s" && source $s done |
下图为system.img文件内容:
![](https://i-blog.csdnimg.cn/blog_migrate/91f007b31a91c52a8c37ad9baaefdbc2.png)
app | 主要存放的是常规下载的应用程序,可以看到都是以APK格式结尾的文件,在这个文件夹下的程序为系统默认的组件,自己安装的软件将不会出现在这里,而是\data\文件夹中。 应用举例: \AlarmClock.apk 闹钟 \Browser.apk 浏览器 \Bugreport.apk 错误报告 \ Calculator.apk 计算器 |
bin | 目录下的文件都是系统的本地程序,是binary二进制的程序,主要是Linux系统自带的组件: \app_process 系统进程 \dalvikvm Dalvik虚拟机宿主 \dbus-daemon 系统BUS总线监控 \debuggerd 调试器 \debug_tool 调试工具 \dexopt DEX选项 \dhcpcd DHCP服务器 |
etc | 配置文件,如: \bluetooth 蓝牙设备配置文件 |
fonts | 字体,中文字库,unicode字库等 \fonts\DroidSans-Bold.ttf \fonts\DroidSansFallback.ttf |
framework | 平台框架,如: \am.jar \am.odex \android.awt.jar AWT库 \android.awt.odex |
lib | 系统运行库,如: \libaes.so \libagl.so \libandroid_runtime.so Android运行时库 \libandroid_servers.so 系统服务组件 \liba io.so 音频处理 \liba ioeq.so EQ均衡器 \liba ioflinger.so 音频过滤器 \libbltooth.so 蓝牙组件 |
media | 各种铃声,系统提示音等 |
usr | 用户文件夹,包含共享、键盘布局、时间区域文件等。 \keychars \keylayout \share \srec |
在文件系统中,android4层架构显现的很明显:app应用,framework框架,lib运行库。android系统4层架构(应用层,框架层,运行库层,内核层)之间,应用层通过框架对使用到库进行调用、内核负责内存管理进程调度等。通过在不同层上的裁剪可以实现部分功能的移出。例如:应用层上移出APK,相应应用无法使用;运行库层移出运行所需的库文件,所有调用该库文件均报错不能执行;在内核层上,通过config编译内核也可达到同样目的。
以上做的就是一些初始化的工作,并且根据不同启动参数,所做的操作也略有不同。可以看看上面标出的红色部分,此处的主要作用是如果判断出系统启动时所带的参数为install,则进入安装模式。接着往下看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | [ "$AUTO" != "1" ] && detect_hardware && FOUND=1 [ -n "$INSTALL" ] && do_install load_modules mount_data mount_sdcard setup_tslib setup_dpi post_detect find_network_dev_name if [ 0$DEBUG -gt 1 ]; then echo -e "\nUse Alt-F1/F2/F3 to switch between virtual consoles" echo -e "Type 'exit' to enter Android...\n" debug_shell debug-late fi [ -n "$DEBUG" ] && SWITCH=${SWITCH:-chroot} # We must disable mdev before switching to Android # since it conflicts with Android's init echo > /proc/sys/kernel/hotplug exec ${SWITCH:-switch_root} /android /init # avoid kernel panic while :; do echo echo ' Android-x86 console shell. Use only in emergencies.' echo debug_shell fatal-err done |
可以看出上面的代码用红色标注了两处,其中第一处是用来判断是否是install模式,在这个地方需要注意下,如果是正常的启动此处是不会执行到的,如果是安装模式则会调用安装脚本,这个安装脚本是放在install.img 这个文件里。这个文件的目录在上文我已经列过了。具体的看看do_install这个函数,这个函数是定义在install.img 解压出文件的scripts/1-install这个文件当中,在这个脚本中为什么能调用这个函数呢,注意这一句:
1 2 3 4 | # load scripts for s in `ls /scripts/* /src/scripts/*`; do test -e "$s" && source $s done |
这一段代码遍历了/scripts/和 /src/scripts/这两个目录下的脚本文件。注意后面的source $s,正是有了这一句,我们才可以在在init脚本里调用1-install这个脚本的函数。这个install.sh就是实现了我们的安装功能。
install.img文件中,我们先看一下这个包里有那些文件,在这里我以树的形式列出包含有那些文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | install# tree . ├── bin │ ├── cfdisk │ ├── dialog │ └── pv ├── grub │ ├── android-x86.xpm.gz │ ├── e2fs_stage1_5 │ ├── fat_stage1_5 │ ├── iso9660_stage1_5 │ ├── ntfs_stage1_5 │ ├── stage1 │ ├── stage2 │ └── stage2_eltorito ├── lib │ ├── libncursesw.so.5 │ ├── libntfs.so.10 │ ├── libtinfo.so.5 │ ├── libuuid.so.1 │ └── terminfo │ └── l │ └── linux ├── sbin │ ├── grub │ └── mkntfs └── scripts └── 1-install |
这个函数包含在1-install这个文件里,可以看一下这个函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | do_install() { until install_hd; do if [ $retval -eq 255 ]; then dialog --title ' Error! ' --yes-label Retry --no-label Reboot \ --yesno '\nInstallation failed! Please check if you have enough free disk space to install Android-x [ $? -eq 1 ] && rebooting fi done [ -n "$VESA" ] || runit= "Run Android-x86" dialog --clear --title ' Congratulations! ' \ --menu "\n Android-x86 is installed successfully.\n " 11 51 13 \ "$runit" "" "Reboot" "" 2> $tempfile case "`cat $tempfile`" in Run*) cd /android umount system if mountpoint -q /sfs; then umount /sfs mount -o loop /hd/$asrc/ system .sfs /sfs mount -o loop /sfs/ system .img system else mount -o loop /hd/$asrc/ system .img system fi if [ -d /hd/$asrc/data ]; then mount --bind /hd/$asrc/data data elif [ -e /hd/$asrc/data.img ]; then mount -o loop /hd/$asrc/data.img data fi ;; *) rebooting ;; esac |
在这个函数中使用了until这样一个关键字,这个是shell脚本的语句,具体的作用大家google吧,接下来调用了install_hd这个函数,这个函数也是在这个文件里定义的,具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | install_hd() { select_dev || rebooting retval=1 case "$choice" in [sh]d*) install_to $choice retval=$? ;; Create*) cfdisk ;; Detect*) dialog --title " Detecting... " --nocancel --pause "" 8 41 1 ;; esac return $retval } |
在这个函数里可以看到调用了一个select_dev的函数,这个函数也定义在该文件中,定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | select_dev() { fdisk -l | grep ^/dev | cut -b6-12,55- | awk '{ if (!match($2, "Extended" )) { printf ( "\"%-28s" , $0) system ( "echo -n `cat /sys/block/*/" $1 "/../device/model`" ) printf ( "\" \"\"\n" ) } } END { printf ( "\"Create/Modify partitions\" \"\"\n\"Detect devices\" \"\"" ) }' > $menufile choose "Choose Partition" "Please select a partition to install Android-x86:" return $retval } |
在上面的函数中还调用了另外一个函数,choose,该函数定义如下:
1 2 3 4 5 6 7 8 | choose() { dialog --clear --title " $1 " \ --menu "$2" 20 71 13 --file $menufile 2> $tempfile retval=$? choice=`cat $tempfile` } |
在这个函数中需要注意的是最后一个choice值,这个值在后面会用到。接下来我们回到install_hd,这个函数,继续往下执行:
1 2 3 4 5 6 7 8 9 10 11 12 | case "$choice" in [sh]d*) install_to $choice retval=$? ;; Create*) cfdisk ;; Detect*) dialog --title " Detecting... " --nocancel --pause "" 8 41 1 ;; esac |
因为此处我们主要讲解安装,所以其他的来那个选项我们暂时不分析了,等后面在补上。我们看一下上述表红色的部分。其中retval=$?用来取得调用select_dev函数的返回值。接下来我们看一下install_to这个函数,这个函数也是定义在这个文件里,由于这个函数内容比较多,我们一段一段分析:
1 2 3 4 5 6 7 8 9 | cd / mountpoint -q /hd && umount /hd while [ 1 ]; do format_fs $1 try_mount rw /dev/$1 /hd && break dialog --clear --title " Error " --defaultno --yesno \ "\n Cannot mount /dev/$1\n Do you want to format it?" 8 37 [ $? -ne 0 ] && return 255 done |
这一段代码的主要功能是完成对磁盘的分区与格式化,具体的调用是在format_fs中实现,该函数实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | format_fs() { local cmd echo -e '"Do not format" ""\next3 ""\next2 ""\nntfs ""\nfat32 ""' > $menufile choose "Choose filesystem" "Please select a filesystem to format $1:" case "$choice" in ext3) cmd= "mke2fs -jL" ;; ext2) cmd= "mke2fs -L" ;; ntfs) cmd= "mkntfs -fL" ;; fat32) cmd= "newfs_msdos -L" ;; *) ;; esac if [ -n "$cmd" ]; then dialog --title " Confirm " --no-label Skip --yesno \ "\n You chose to format $1 to $choice.\n All data in that partition will LOSE.\n\n Are you sure to format the partition $1?" 10 51 [ $? -ne 0 ] && return 1 $cmd Android-x86 /dev/$1 | awk '{ # FIXME: very imprecise progress if (match($0, "done" )) printf ( "%d\n" , i+=33) }' | progress_bar "Formatting" "Formatting partition $1..." fi } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | fs=`cat /proc/mounts | grep /dev/$1 | awk '{ print $3 }' ` asrc=android-$VER dialog --title " Confirm " --no-label Skip --defaultno --yesno \ "\n Do you want to install boot loader GRUB?" 7 47 if [ $? -eq 0 ]; then cp -af /grub /hd d=0 while [ 1 ]; do h=`echo $d | awk '{ printf("%c", $1+97) }' ` [ -d /sys/block/[sh]d$h/$1 ] && break d=$(($d+1)) done p=$((`echo $1 | cut -b4-`-1)) create_menulst $p create_winitem $1 $d rm -f /hd/boot/grub/stage1 echo "setup (hd$d) (hd$d,$p)" | grub > /dev/tty5 [ $? -ne 0 ] && return 255 fi |