BabyLinux制作过程详解
作者:GuCuiwen email:
[email protected]
版权声明:
本文档可以在网络上在非商业范围内自由转载,转载请注明出处
如果转载版面包含商业广告,请向作者支付至少每千字100园的稿费
以书面,书籍形式转载和出版请按至少每千字100园人民币的标准向作者支付稿费
一,什么是BabyLinux
二,为什么要做这样一个linux
三,什么人适合读这篇文档
四,应该具备的知识
五,linux系统引导过程简介
六,编译内核
七,编译busybox
八,制作根文件系统
九,制作ramdisk映象文件
十,内核与busybox的整合
十一,安装测试和内容调整
十二,babylinux中的BUG
十三,接下来要做的事情
十四,参考文献
一,什么是BabyLinux
BabyLinux不是一个完整的发行版,他是利用原有的一套完整的linux系统的内核原代码和编译工具,利用busybox内建的强大功能,在一张软盘上做的一个很小的linux系统.他具备一个linux系统的基本特征,支持linux系统最常用的一百多个命令,支持多种文件系统,支持网络等等,你可以把他当做一张linux起动盘和修复盘来用,你也可以把他当做一个静态路由的路由器软件,当然,你也可以把他当做一个linux玩具,向你的朋友炫耀 linux可以做的多么小.我把他叫做BabyLinux因为他很小巧,小的很可爱,像一个刚刚出生的小baby.
二,为什么要作这样一个linux
先说说我一开始的想法,当我一开始接触linux的时候,看到书上说,linux通常安装只需要60M左右的空间,但是我发现装在我硬盘上的 Redhat 6.0确要占据好几百M的空间.为什么我的linux这么大呢? 后来我发现,装在我机器上的那么多东西只有不到30%是我平时常用的,还有30%是我极少用到的,另外的40%基本上是不用的.于是,我和大多数初学者一样,开始抱怨,为什么linux不能做的精简一点呢?于是,我萌发了自己裁减系统的想法.可惜那个时候我还没有听说过有LFS和Debain.等到我积累了足够的linux知识后,我开始制作这样一个小系统.
制作这样一个小系统最大的意义在于,你可以通过制作系统了解linux的启动过程,学会ramdisk的使用,让你在短时间内学到更多的 linux知识.当然,你会得到很大的乐趣.这个项目只是做一个具有基本特征的linux系统,如果你想自己做一个具有完整功能的linux,请阅读 Linux From Scratch (LFS)文档.
三,什么人适合读这篇文档
如果你是一个linux爱好者,并且很想了解linux的启动过程和系统的基本结构,而且是一个喜欢动手研究小玩意的人,那么这个文档可以满足你的需求.如果你仅仅是用linux来做一些普通的日常工作,而不在乎你的linux到底怎么工作,那么这份文档也许不太适合你.另外,如果你是linux 爱好者,但是目前还是一个刚刚入门的newbi,我建议你先把linux命令学好.不过我想我会尽可能的把这份文档写详细一些,如果你有足够的毅力,或许一个newbi也能成功做一个babylinux.或者,你遇到一件很不巧的事情,比如你的老婆来例假了,你的这个周末就泡汤了,那么阅读这篇文档并做一个linux小玩具可以打发你的时间.
四,应该具备的知识
在做一个babylinux之前,你应当已经会应用linux最常用的命令.并且至少有一次成功编译并安装系统内核的经历,会通过编译源代码来安装软件.如果你具备了这些条件,那么做这样一个小系统会很顺利,如果你还没有掌握这些知识,你可能会遇到一些困难.但是只要有毅力,也可以成功.你不需要具备编程的知识,因为我的目标是:让具有中等以上linux水平的爱好者可以通过阅读文档轻松完成这个项目.关于一张软盘上的linux还有一个很著名的 linux叫LOAP (Linux On A
Floppy) 但是他是由比较专业的人员需要编写很多程序完成的.而且没有关于他制作过程的文档.
五,linux系统引导过程简介
首先,主板的BIOS会读取硬盘的主引导记录(MBR),MBR中存放的是一段很小的程序,他的功能是从硬盘读取操作系统核心文件并运行,因为这个小程序太小了,因此通常这个小程序不具备直接引导系统内核的能力,他先去引导另一个稍微大一点的小程序,再由这个大一点的小程序去引导系统内核.在linux系统中这样的小程序有LILO和GRUB.在这个项目中,我决定用LILO来做系统引导程序.在软盘上启动linux系统的过程和在硬盘上启动的过程相似.
Linux系统内核被引导程序装入内核并运行后,linux内核会检测系统中的各种硬件.并做好各种硬件的初始化工作,使他们在系统正式运行后能正常工作.之后内核做的最后一个工作是运行
/sbin下的init程序,init是英文单词initialization(初始化)的简称,init程序的工作是读取/etc/inittab文件中描述的指令,对系统的各种软硬件环境做最初化设定.最后运行mingetty等待用户输入用户名登录系统.所有的工作就这么简单,虽然linux启动的时候有很多内容,看上去十分高深,但是都不过是对这个过程的扩充.明白了这个道理,你可以写一些脚本程序让他在系统启动的特定时间运行完成任务.事实上系统内核并不关心/sbin下的init是不是真的init,只要是放在/sbin下名叫init的可执行程序他都可以执行.可以做以下实验:
编写一个非常简单的C程序:
main()
{
printf(“hello,world! ”);
}
保存后以init.c保存他,并用gcc编译.
#gcc –-static -o init init.c
这里的--static 参数告诉gcc把这个程序静态联接,这样这个程序不倚赖任何库就能运行.把编译好的init程序拷贝到/sbin下,备份好原来的那个.重新启动系统最后系统的输出结果是: hello,world!
然后停在那里.做这个实验以前先确定你知道如何把系统恢复到原来的状态,有一个简单的方法,在内核启动前给他加上init=参数,比如你原先的 init被你改成了init.bak 只要在启动的时候给内核加上init=/sbin/init.bak就可以用原来的init程序启动系统.
做完以上实验,就明白了内核和init程序之间的关系.此外,init程序不一定是一个二进制可执行程序,他可以是一个bash脚本,一个指向另一个程序的联接,他的位置也并不一定要在/sbin下,只要在启动内核时,给内核加上init参数就能被运行,比如,开始时给内核加上init= /bin/bash参数,内核在最后一步就直接运行bash给出提示符,不用登录系统就可以输入命令了.其功能类似单用户模式启动系统. /sbin/init 程序只是内核默认运行的第一个程序.
六,编译一个linux系统内核
1,编译前的规划和准备
在编译内核前,请先确定你的需求,把你的需求罗列成一张详细的表格.你需要让内核支持什么硬件,支持多少种分区类型和文件系统,支持哪些网卡,支持哪些网络协议.等等.请尽可能详细的罗列这些内容,但是你也不要太贪心,因为你所有能利用的空间只有1440K,如果你编译出一个大于1440K或很接近这个数字的内核,你的这个项目就不能完成了,你已经没有空间再放ramdisk映象文件,除非你原意再多出一张软盘,做一个两张软盘的小linux系统.对于声卡驱动之类,我劝你还是放弃吧,因为一个声卡驱动也许只让你的内核增大了十多K,但是你有了一个声卡驱动就务必要有一个播放器吧,否则声卡驱动就没有意义,可一个播放器的大小可不是一张软盘可以装得下的.在我先前制作的babylinux内核有900多K,其中,文件系统部分站了大部分,因为我的目标是把他做成一个系统修复盘.因此我在内核中编译7种文件系统的支持,每减少一个文件系统就可以减小几十甚至200多K的内核大小.越是复杂,越是安全的文件系统,其支持模块也越大,比如在linux下FAT模块只有32K,VFAT只有17K,但是ext3的模块就有86K,JFS达到216K, reiserfs模块是224K,可以想像,编译一个支持7个文件系统的900多K的内核,文件系统部分就占了600K以上的空间,所以如果某一个文件系统是你根本不用的,那么还是不要编译进内核把,这样至少可以省下100多K的空间.对于其他的驱动,比如网卡,通常大小只有8,9K,最大的也不过10多 K,因此可以把常用的网卡芯片的驱动都编译进去.另外如果你想让你的babylinux支持U盘,那么scsi的驱动模块也是不可小看的,他通常要接近 150K,因为U盘是被当做scsi设备来驱动的.另外你还需要让你的内核支持即插即用,这些都是不小的空间开销,我的建议是你放弃一两个你不用的文件系统.总之,你最后编译出来的内核大小最好不要超过900K,否则你在busybox里只能编译进去很少的命令.
在我编译的busybox中,我编译进去120多个命令,基本上把busybox支持的命令都包括进去了.加上小系统所必需的文件系统目录, /dev下的设备文件,以及/etc下几个必需的配置文件,做成ramdisk压缩后的大小是440多K, 加上900K左右的内核刚好可以放入一张1440K软盘,请注意,你应该留下至少50K的空间,因为我们要在软盘上创建一个ext2文件系统,而文件系统本生需要占据大概25K的磁盘空间.另外lilo的引导文件boot.b的大小是5.7K,还有装上lilo后自动产生的map文件也要10多K的空间, map文件的具体大小由内核安装的实际大小决定,通常不会超过30K.
综上所述,请遵循下面的公式:
内核大小+文件系统压缩印象文件+50K bin/busybox
drwxr-xr-x 2 root root 4096 11月 24 15:28 sbin
其中可执行文件busybox在bin目录下,其他的都是指向他的符号链接.
我编译出来的busybox可执行文件是935K,加上符号链接,整个_install目录是952K.加上845K的内核不是已经超过1440K了吗?别担心,我们将对整个根文件系统做大幅度的压缩.
八,制作根文件系统
1,基本目录结构
#cd /babylinux/rootfs
#mkdir etc usr var tmp proc home root dev
其中etc,proc和dev是一定要建的,bin和sbin不用建,因为busybox中已经有了.
其他的可以象征性的建几个就可以了.
拷贝busybox
#cp -R /babylinux/busybox-1.00/_install/* /babylinux/rootfs/
2,建立设备文件名
#cd /babylinux/rootfs/dev
你可以用mknod手工建立,也可以直接从原系统的/dev目录下拷贝过来.
手工建立的方法:
#ls -l /dev/console
crw------- 1 root root 5, 1 11月 30 09:02 /dev/console
这样就查看到了console设备的主设备号是5,辅设备号是1,是一个标记为C的字符设备.
于是,我们可以用mknod建立一个同样的设备文件:
#mknod console c 5 1
但是手工方法建立太麻烦了,通常直接从/dev下把需要的设备文件拷贝过来.
这些设备文件是特殊文件,在拷贝时一定要加上-R参数才能拷贝.
#cp -R /dev/console ./
#cp -R /dev/null ./
#cp -R /dev/zero ./
...
以下是我认为需要的设备名:
cdrom fd0 hda14 hda4 hdb11 hdb19 hdc hdc16 hdc6 hdd13 hdd3 loop2 ram2
console fd0H1440 hda15 hda5 hdb12 hdb2 hdc1 hdc17 hdc7 hdd14 hdd4 loop3 tty0
fb hda hda16 hda6 hdb13 hdb3 hdc10 hdc18 hdc8 hdd15 hdd5 loop4 tty1
fb0 hda1 hda17 hda7 hdb14 hdb4 hdc11 hdc19 hdd hdd16 hdd6 loop5 tty2
fb1 hda10 hda18 hda8 hdb15 hdb5 hdc12 hdc2 hdd1 hdd17 hdd7 null tty3
fb2 hda11 hda19 hdb hdb16 hdb6 hdc13 hdc3 hdd10 hdd18 hdd8 ram tty4
fb3 hda12 hda2 hdb1 hdb17 hdb7 hdc14 hdc4 hdd11 hdd19 initctl ram0 tty5
fb4 hda13 hda3 hdb10 hdb18 hdb8 hdc15 hdc5 hdd12 hdd2 loop1 ram1 zero
其中,fd0,hda,ram,ram1,tty1,null,zero,loop1,fb0,fb等是必备的.
其它的hda,hda1,hdb等可以根据实际需要决定.但是上表中的选择是比较合理的,即能满足大部分的需要,有没有不用的设备浪费空间.注意,千万不要把/dev下的设备全拷贝过来,那将产生大约420K的/dev目录,这对babylinux来说太大了.
3,建立etc目录下的配置文件
busybox.conf group inittab motd passwd resolv.conf shadow-
fstab init.d issue mtab profile shadow
其中init.d是一个目录,从busybox-1.00源代码目录下拷贝过来.
#cp -R /babylinux/busybox-1.00/examples/bootflopyp/etc/init.d /babylinux/rootfs/etc/
busybox.conf是一个空文件.
其他文件的内容如下:
fstab
/dev/fd0 / ext2 defaults 0 0
none /proc proc defaults 0 0
/dev/cdrom /mnt/cdrom udf,iso9660 noauto,owner,kudzu,ro 0 0
/dev/fd0 /mnt/floppy auto noauto,owner,kudzu 0 0
group
root:x:0:root
inittab
::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
tty2::respawn:/bin/getty 38400 tty2
tty3::respawn:/bin/getty 38400 tty3
tty4::respawn:/bin/getty 38400 tty4
# Stuff to do when restarting the init process
::restart:/bin/init
# Stuff to do before rebooting
::ctrlaltdel:/bin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/bin/swapoff -a
issue
Baby Linux release 0.1
motd
mtab
passwd
root::0:0:root:/root:/bin/ash
profile
# /etc/profile: system-wide .profile file for the Bourne shells
echo
echo
export PS1="[u@h w]$"
echo "Done"
alias ll='ls -l'
alias du='du -h'
alias df='df -h'
alias rm='rm -i'
echo
resolv.conf
nameserver 202.96.209.5
nameserver 202.96.209.6
shadow
root:$1$$adltAB9Sr/MSKqylIvSJT/:12705:0:99999:7:::
shadow-
root:$1$DWU.tenP$B7ANiXoGoiZMwJR6Ih8810:12705:0:99999:7:::
其中有很多是从原系统的/etc下拷贝过来修改的,如果你是一个具有中等以上水平的linux爱好者,那么应该一看就明白了,当然,你也可以根据自己的需要修改这些文件.其中最重要的是fstab和inittab,busybox内建的init程序用到的inittab文件的语法和一般的不一样, 不能直接把原系统/etc下inittab文件拷贝过来.可以把busybox-1.00目录下的示例文件拷贝过来修改用.具体请看busybox的文档.busybox的init也可以不用inittab.但是在我制作babylinux过程中有一个非常奇怪的bug.所有/sbin下的 busybox链接在做成压缩的根文件系统,解压后都不能正常运行,显示找不到该命令.只有当我在/bin下做这些链接时才能运行.具体原因还不太清除, 所以你需要做下面的工作:
#cd /babylinux/rootfs/sbin
#ls
chroot getty ifconfig losetup pivot_root reboot swapoff sysctl
fdisk halt init mkswap poweroff route swapon telnetd
查看到sbin下有上述链接
转到bin下
#cd /babylinux/rootfs/bin
重新做这些链接:
#ln -s busybox chroot
#ln -s busybox getty
#ln -s busybox ifconfig
...
然后把sbin下的链接删除,以节省空间
#rm -rf /babylinux/rootfs/sbin/*
再把原先inittab中所有的sbin改成bin
init.d下的文件:
rcS
请确保这个文件是可执行的,否则请改成可执行的:
#chmod u+x rcS
rcS的内容:
#! /bin/sh
mount -o remount,rw /
/bin/mount -a
>/etc/mtab
echo
echo
echo
echo
echo -en " Welcom to 33[0;32mBabyLinux33[0;39m "
echo -en "33[0;36m "
echo
echo -en "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ "
echo -en "+ This is a tiny linux system based on a floppy.It contains "
echo -en "+ more than 100 basic Linux commands and tools.The kernel of "
echo -en "+ this tiny system support all kinds of normal filesystems. "
echo -en "+ linux ext2,ext3,jfs,reiserfs and windows fat,vfat,ntfs[readonly] "
echo -en "+ is supported! So it is a powerful small system you can use it "
echo -en "+ as a linux and windows rescue disk.Beside this,the kernel also "
echo -en "+ contains the drivers of Reltek8139,NE2000,via-rhine ethernet "
echo -en "+ adpater. you can configure the IPaddress and netmask with tools "
echo -en "+ 'ifconfig' and config the default gateway with command 'route'. "
echo -en "+ Is there anything else? Haha,this is a telnet server build-in "
echo -en "+ you can type 'telnetd' to startd it and thus your friends can "
echo -en "+ logon to your system to help you solve the problem. "
echo -en "+ 33[0;32mAll these great features are powered by BusyBox 1.033[0;36m "
echo -en "+ This is a free system tool developed by GuCuiwen. "
echo -en "+ RUN YOUR OWN RISK of using it ! if you have any problem please "
echo -en "+ mailto :
[email protected] Enjoy!! "
echo -en "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ "
echo -en "33[0;39m "
hostname BabyLinux
可以自己作相应的修改.
以上是babylinux根文件系统的所有内容,他的总大小应该在1M左右.
[root@gucuiwen baby]# du -hs
1.1M .
九,制作ramdisk映象文件
babylinux根文件系统所有东西都放在/babylinux/rootfs下,我们将利用ramdisk把这些内容做成ramdisk映象文件并压缩他.
以下主要是ramdisk的用法,看完以下内容,你应当学会ramdisk的使用.
[root@gucuiwen babylinux]# dd if=/dev/zero of=/dev/ram1
dd: 正在写入 ‘/dev/ram1’: 设备上没有空间
读入了 8193+0 个块
输出了 8192+0 个块
zero是一个特殊的设备,表示全部为0的字符块.上面这条命令的意思是把系统的第一个ramdisk用全部为0的数据填充,因为ramdisk 默认大小为4M,因此当读满8192个块(每块512字节)后,显示'设备上没空间'.这很正常,/dev/ram1已经被填充满了.
如果指定块的大小:
[root@gucuiwen babylinux]# dd if=/dev/zero of=/dev/ram1 bs=1M count=4
读入了 4+0 个块
输出了 4+0 个块
不会有错误提示,这里演示了dd的一般用法,接下来还要频繁用到dd命令.
在/dev下有很多ramdisk设备,ram1,ram2,ram3....
一般用第一个就可以了.
填充后,ram1就有可空间,可以在这个空间上创见一个文件系统:
[root@gucuiwen babylinux]# mkfs.ext2 -m0 /dev/ram1
mke2fs 1.32 (09-Nov-2002)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
1024 inodes, 4096 blocks
0 blocks (0.00%) reserved for the super user
First data block=1
1 block group
8192 blocks per group, 8192 fragments per group
1024 inodes per group
Writing inode tables: done
Writing superblocks and filesystem accounting information: done
This filesystem will be automatically checked every 37 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.
将ram1挂装到文件系统中:
先建立一个挂装点:
#mkdir /mnt/ram
挂上ram1:
#mount /dev/ram1 /mnt/ram
将先前做好的babylinux根文件系统拷贝到ram1上.
#cp -R /babylinux/rootfs/* /mnt/ram
做完以上几步,你应就白了ramdisk设备的含义,他是和hda1,hdb1,一样的块设备,用mount挂到文件系统下后就可以访问,往里放东西,但是所有的东西在内存上.关机将丢失所有东西.
拷贝好babylinux根文件系统后卸载ram1:
#umount /dev/ram1
这时,虽然不能通过/mnt/ram这个挂装点访问ram1中的内容了,但是他却切切实实得在内存中存在.
再用dd把这个ram1以映象方式取出来:
[root@gucuiwen babylinux]# dd if=/dev/ram1 of=/babylinux/ramdisk.img
读入了 8192+0 个块
输出了 8192+0 个块
验证一下取出来的内容:
[root@gucuiwen babylinux]# file ramdisk.img
ramdisk.img: Linux rev 1.0 ext2 filesystem data
他是一个ext2 文件系统,类似一个ISO光盘映象文件.
因次,我们可以用loop设备来把他重新挂装到文件系统里:
[root@gucuiwen babylinux]# mount -o loop ramdisk.img /mnt/ram/
为了方便,我仍旧把他挂在/mnt/ram下,因此,在先前一定要把/dev/ram1 umount掉
查看/mnt/ram下的内容,他应该和/babylinux/rootfs下的一模一样,否则就是出错了:
[root@gucuiwen babylinux]# ls /mnt/ram
bin dev etc home lost+found mnt proc root sbin tmp usr var
这样,我们就得到了一个ramdisk根文件系统映象:ramdisk.img
把他umount掉:
#umount /mnt/ram
如果是第一次接触ramdisk,你可能对上述的内容很迷惑,如果这样,请反复阅读和理解上面的内容,自己多动手做几次试验,就可以理解.
压缩ramdisk.img印象文件:
[root@gucuiwen babylinux]# gzip -v9 ramdisk.img
ramdisk.img: 87.9% -- replaced with ramdisk.img.gz
查看压缩后的大小:
[root@gucuiwen babylinux]# ls -lh ramdisk.img.gz
-rw-r--r-- 1 root root 495K 11月 30 11:32 ramdisk.img.gz
我得到的压缩ramdisk映象文件安是495K. 加上内核的845K,是1340K
符合公式:
内核大小+文件系统压缩印象文件+50K =ramdisk.img.gz的大小.
挂上软盘
#mount /dev/fd0
将内核拷贝到软盘:
#cp /babylinux/bzImage /mnt/floppy/
将lilo引导文件安boot.b 拷贝到软盘
#cp /boot/boot.b /mnt/floppy
新建一个lilo.conf 配置文件:
prompt
timeout=60
default=linux
boot=/dev/fd0
map=/mnt/floppy/map
install=/mnt/floppy/boot.b
linear
image=/mnt/floppy/bzImage
label=linux
read-only
vga=788
root=/dev/fd0
append="load_ramdisk=1 ramdisk_start=940"
vga=788表示让内核支持字符界面的高分辨率显示,你可以改成vga=ask,这样可以在启动的时候选择分辨率.
红色一行是关键,load_ramdisk=1告诉内核在启动的时候转载压缩的ramdisk印象文件,
ramdisk_start=940 告诉内核从软盘的第940K的地方去寻找并装载压缩的ramdisk印象文件.
关于ramdisk的用法和更多参数请查看linux0内核文档/usr/src/linux/Documents/ramdisk.txt
接下来再用dd命令把ramdisk.img.gz装到软盘上.
#dd if=/babylinux/ramdisk.img.gz of=/dev/fd0 bs=1k seek=940
这里的seek=940 表示把ramdisk.img.gz装到软盘的第940K开始的地方.
详细内容请看dd的联机文档 man dd
为什么要从940k开始呢?
因为刚才作了一个920K的文件系统.我把他装在文件系统20K以后的地方.
当然,如果你的空间十分紧张,连这20K都不舍得浪费,那么可以这样:
#dd if=/babylinux/ramdisk.img.gz of=/dev/fd0 bs=1k seek=921
当然,别忘记修改lilo.conf文件. ramdisk_start=921
接下来装lilo引导程序就大功告成了.
#lilo -C lilo.conf
如果你的磁盘上还有一点点空余空间,那么可以把lilo.conf也拷贝上去,以备将来使用.
#cp lilo.conf /mnt/floppy
#umount /dev/fd0
整个工程已经完成了,你可以重新启动机器,设置电脑从软盘启动.看看有没有成功.
十一,安装测试和内容调整
如果在整合内核和ramdisk映象过程中,出现磁盘空间不够的情况,请重新编译内核和busybox
可以根据实际需要,调整内核和busybox,比如你要内核支持很多东西,但是只需要一个支持50个命令的busybox,那么可以自己做相应调整.
十二,babylinux中的BUG
有些命令的输出结果会有偏差,比如用 busybox的df 看磁盘使用情况,和实际的不一样.
十三,接下来要做的事情
做一个基于64M U盘的linux小系统.
计划支持如下特征:
a.软盘babylinux的所有功能
b.图形界面的支持.
c.一个轻量级的窗口管理器(window maker)
d.网络的支持,
e.至少一个图形web浏览器,可以上网.
f.一个音乐播放器和一个视频播放器.
g.支持中文的显示和输入.
h.可以修改配置并保存数据
我还计划做一个live CD,但是目前已经有很多live CD了,而且都做的非常好.
但是我会自己做一个作为学习linux的一种手段.如果有时间,可能写一个做U盘linux和live CD
的教程.但是,我想不会写的和这个文档一样详细了,我的时间有限.可能大概讲一下原理和步骤.有经验的linux爱好者应该可以通过阅读文档完成制作.
本文地址:http://com.8s8s.com/it/it33545.htm