免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1219 | 回复: 0
打印 上一主题 下一主题

制作一个小型的 Linux [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-04-25 15:53 |只看该作者 |倒序浏览
软件包下载:
http://ftp.osuosl.org/pub/nslu2/sources/
第一章                   前言
目的
     本文的目的,  是讲述嵌入式 Linux 系统的建立、开发的一般过程。      制作一个小型的 Linux
的系统,可以移植至其它硬盘、软盘、优盘、flash rom......
关于作者
     九贱,E 名 kendo,喜欢网络入侵技术、防火墙、入侵检测技术及网络技术,对 Linux
也颇感兴趣,想认识有共同爱好的朋友。最近闲暇,把一些学过的东西写下来,总结总结,
以作备忘这需。已完成的有《网络入侵检测设计与 Snort2.2 源码分析》和这篇《我也来学做
嵌入式 Linux》 。正在进行中的有《Windows 防火墙技术实现大全》和《Linux 防火墙实现及
         。大家可以在 CU 上,或者是到我的小站
www.skynet.org.cn
上与我交流
源码分析》
做一个嵌入式 Linux 系统究竟要做哪些工作
     做一个嵌入式 Linux 系统究竟需要做哪些工作?也就是本文究竟要讲述哪些内容?我
先介绍一个脉络,可以做为我们后面工作的一个总的提纲:
第一步、建立交叉编译环境
     没有交叉开发经验的读者,可能一时很难接受这个概念。首先,要明白两个概念:一般
我们工作的机器,称为开发机、主机;我们制作好的系统将要放到某台机器,如手机或另一
台 PC 机,这台机我们称为目标主机。
     我们一般开发机上已经有一套开发工具,      我们称之为原生开发套件,        我们一般就是用它
们来开发程序,那么,那什么又是交叉编译环境呢?其实一点也不神秘,也就是在开发机上
再安装一套开发工具,这套开发工具编译出来的程序,如内核、系统程序或者我们自己的程
序,是放在目标主机上运行的,需要用这套新的开发工具来完成编译等工作。
     那么或许有初学者会问,    直接用原生开发工具为目标主机编译程序不就完了?至少我当
初是这么想的。一般来说,我们的开发机都是 X86 平台,原生开发套件开发的工具,也针
对 X86 平台,而我们的目标主机可能是 PowerPC、IXP、MIPS......所以,我们的交叉编译
环境是针对某一类具体平台的。
     一般来讲,交叉开发环境需要二进制工具程序、编译器、C 链接库,嵌入式开发常用的
这三类开发软件是:
          :二进制工作库
Binutils
          :C/C++编译器
Gcc
          :C 函数库
uClibc
当然,GNU 包含的工具套件不仅于此,你还要以根据实际需要,进行选择。
第二步、编译内核
      开发工具是针对某一类硬件平台,内核同样也是。这一步,我们需要用第一步中建立的
交叉编译工具,对内核进行编译,对于有内核编译经验的人来说,这是非常简单的;
第三步、建立根文件系统
      也就是建立我们平常看到的 bin、dev、proc......这一大堆目录,以及一些必备的文件;
另外,我们还需要为我们的目标系统安装一些常用的工具软件,如 ls、ifconfig......当然,
一个办法是找到这些工具的源代码,              用第一步建立的交叉编译工具来编译,       但是这些软件一
是数量多,二是某些体积较大,不适合嵌入式系统,这一步,我们一般都是用 busybox 来完
成的,包括系统引导软件 init;
      最后,我们为系统还需要建立初始化的引导文件,如 inittab......
第四步、启动系统
      在这一步,   我们把建立好的目标、       文件、 程序、内核及模块全部拷贝到目标机存储器上,
如硬盘。然后为系统安装 bootloader,对于嵌入式系统,有许多引导程序可供我们使用。不
过它们许多都有硬件平台的限制。当然,如果你是工作在 X86,可以直接用 lilo 来引导,事
实上,本文就是采用的 lilo。
      做到这一步,将目标存储设备挂上目标机,如果顺利,就可以启动系统了。
      当然,针对某些特别的平台,不能像硬盘这样拷贝了,需要读卡器、烧录......但是基本
的方法是相通的!
第五步、优化和个性化系统
      通过前四步,我们已经得到了一个可以正常工作的系统。在这一步里,就是发挥你想像
的时候了......
本文的工作环境
项目根目录/home/kendo/project ------>;我将它指定至 PATH:$PRJROOT
子目录及说明
目录           内容
                目标板的引导加载程序,如 lilo 等
bootloader
                建立交叉编译平台的工具源码
build-tools
             调试工具及所有相关包
debug
            项目中用到的所有文档
doc
              编译好的内核映像,以及根文件系统
images
             各个版本的 Linux 内核源码
kernel
             制作好的根文件系统
rootfs
              目标板将要用到的系统应用系统,比如 thttpd,udhcpd 等
sysapps
            存放临时文件
tmp
            编译好的跨平台开发工具链以及 C 链接库
tools
工作的脚本
#!/usr/bin
export PROJECT=skynet
export PRJROOT=/home/${PROJECT}
export TARGET=i386-linux
export PREFIX=${PRJROOT}/tools
export TARGET_PREFIX=${PREFIX}/${TARGET}
export PATH=${PREFIX}/bin:/bin:/sbin:/usr/bin:/usr/sbin
cd $PRJROOT
第二章                             建立交叉编译环境
     在 CU 中发表的另一篇同名的贴子里,我讲述了一个全手工创建交叉编译环境的方法。
目前,创建交叉编译环境,包括建立根文件,一般来讲,有两种方法:
     手功创建
     可以得到最大程序的个性化定制,缺点是过程繁杂,特别是极易出错,注意这个“极”
字,包括有经验的开发人员;
     自动创建
     无它,方便而。
     因为前一篇文章中,              已经讲述了全手工创建交叉编译环境的一般性方法,     本文就不打算
再重复这个步骤了,感兴趣的朋友,可以再去搜索那篇贴子,提醒一点的就是,在准备工具
链的时候,要注意各个工具版本之间的搭配、每个工具需要哪些补丁,我建议你在 google
上针对这两项搜索一下,准备一个清单,否则......
     本章要讲述的是自动创建交叉编译环境的方法。目标,针对商业硬件平台,厂家都会为
你提供一个开发包,我用过 XX 厂家的 IXP425 和 MIPS 的,非常地方便,记得我第一次接
触嵌入式开发,拿着这个开发包自动化创建交叉编译环境、编译内核、建立根文件系统、创
建 Ram Disk,我反复做了三四次,结果还不知道自己究竟做了些什么,呵呵,够傻吧......
所以,建议没有这方面经验的读者,还是首先尝试一下手工创建的方法吧,而本章接下来的
内容,    是送给曾经被它深深伤害而不想再次去亲历这项工作而又想提高效率而又没有商业开
发包的朋友。
建立交叉开发工具链
准备工具:
     buildroot-0.9.27.tar.tar
     只需要一个软件?对,其它的不用准备了,buildroot 事实上是一个脚本与补丁的集合,
其它需要用到的软件,如 gcc、uClibc,你只需在 buildroot 中指明相应的版本,它会自动去
给你下载。
     事实上,buildroot 到网上去下载所需的所有工作是需要时间的,除非你的带宽足够,否
则下载软件时间或许会占去 80%,而我在做这项工作之间,所需的工作链全部都在我本地
硬盘上,我解压开 buildroot 后,新建 dl 文件夹,将所有工具源码的压缩包拷贝进去,呵呵,
buildroot 就不用去网上下载了。
我的软件清单:
Linux-libc-headers-2.4.27.tar.bz2
Gcc-3.3.4.tar.bz2
binutils 2.15.91.0.2.tar.bz2
uClibc 0.9.27.tar.bz2
genext2fs_1.3.orig.tar.gz
ccache-2.3.tar.gz
将它拷贝到${PRJROOT}/build-tools 下,解压
[root@skynet build-tools]# tar jxvf buildroot-0.9.27.tar.tar
[root@skynet build-tools]#cd buildroot
配置它:
[root@skynet build-tools]#make menuconfig
################################################################################
###出错参考
http://www.cnblogs.com/ottor/articles/1087734.html
################################################################################
Target Architecture (i386) --->; 选择硬件平台,我的是 i386
Build options --->; 编译选项
这个选项下重要的是:
(${PRJROOT}/tools) Toolchain and header file location?编译好的工具链放在哪儿?
如果你像我一样,所有工具包都在本地,不需它到网上自动下载,可以把 wget command 选
项清空;
Toolchain Options --->; 工具链选项
                                      头文件它会自动去下载,不过应该保证与你将要用的内核
--- Kernel Header Options
是同一个版本;
[] Use the daily snapshot of uClibc? 使用最近的 uClibc 的 snapshot
                                                        Binutils 的版本
Binutils Version (binutils 2.15.91.0.2) --->;
                                                    gcc 版本
GCC compiler Version (gcc 3.4.2) --->;
Build/install c++ compiler and libstdc++?
                                                                 支持的语言,我没有选择 java
[]     Build/install java compiler and libgcj?
                                               启用 ccache 的支持,      它用于编译时头文件的缓存
[ ] Enable ccache support?
处理,用它来编译程序,第一次会有点慢,但是以后的速度可就很理想了,呵呵......
                                     根据你的需要,选择 gdb 的支持
--- Gdb Options
Package Selection for the target --->;
这一项我没有选择任意一项,因为我打算根文件系统及 busybox                                     等工具链创建成工,手工
来做。
Target Options --->; 文件系统类型,根据实际需要选,我用的 ext2;
配置完成后,编译它:
[root@skynet build-tools]#make
这一项工作是非常花时间的,我的工具包全部在本地,也花去我一小时十三分的时间,如果
全要下载,我估计网速正常也要多花一两个钟头。
经过漫长的等待(事实上并不漫长,去打了几把游戏,很快过去了)                                             :
......
make[1]: Leaving directory `/home/skynet/build-tools/buildroot/build_i386/genext2fs-1.3'
touch -c /home/skynet/build-tools/buildroot/build_i386/genext2fs-1.3/genext2fs
[email=#-@find]#-@find[/email]
/home/skynet/build-tools/buildroot/build_i386/root/lib -type f -name \*.so\* | xargs
/home/skynet/tools/bin/i386-linux-uclibc-strip                         --remove-section=.comment
--remove-section=.note --strip-unneeded 2>;/dev/null || true;
/home/skynet/build-tools/buildroot/build_i386/genext2fs-1.3/genext2fs -i 503 -b 1056 \
           -d          /home/skynet/build-tools/buildroot/build_i386/root            -q      -D
target/default/device_table.txt /home/skynet/build-tools/buildroot/root_fs_i386.ext2
大功告成!!      !
清点战利品
让我来看看它究竟做了哪些事情吧:
[root@skynet skynet]# cd tools
[root@skynet tools]# ls
bin bin-ccache i386-linux i386-linux-uclibc include info lib libexec               man usr
bin:所有的编译工具,如 gcc,都在这儿了,只是加了些指定的前缀;
bin-ccache:如果在 Toolchain optaion 中没有选择对 ccache 的支持,就没有这一项了;
i386-linux:链接文件;实际指向 include
i386-linux-uclibc:uclibc 的相关工具;
include:供交叉开发工具使用的头文件;
info:gcc 的 info 文件;
lib:供交叉开发工具使用的链接库文件;
......
现在可以把编译工具所在目录 XXX/bin 添加至 PATH 了
测试工具链
如果你现在写一个程序,用 i386-linux-gcc 来编译,运行的程序会告诉你:
./test: linked against GNU libc
因为程序运行库会寻到默认的/lib:/usr/lib 上面去,而我们目前的 uclibc 的库并不在那里(虽
然对于目标机来讲,这是没有错的)                ,所以,也只能暂时静态编译,试试它能否工作了。当
然,你也可以在建好根文件系统后,试试用 chroot......
第三章                             编译内核
本章的工作,是为目标机建立一个合适的内核,对于建立内核,我想有两点值得考虑的:
1、功能上的选择,应该能够满足需要的情况下,尽量地小;
2、小不是最终目的,稳定才是;
所以,最好编译内核前有一份目标机硬件平台清单以及所需功能清单,这样,才能更合理地
裁减内核。
准备工具
Linux 内核源码,我选用的是 Linux-2.4.27.tar.bz2
编译内核
将 Linux-2.4.27.tar.bz2 拷贝至${PRJROOT}/kernel,解压
#cd linux-2.4.27
//配置
# make ARCH=i386 CROSS_COMPILE=i386-linux- menuconfig
//建立源码的依存关系
# make ARCH=i386 CROSS_COMPILE=i386-linux- clean dep
//建立内核映像
# make ARCH=i386 CROSS_COMPILE=i386-linux- bzImage
ARCH 指明了硬件平台,CROSS_COMPILE 指明了这是交叉编译,且编译器的名称为
i386-linux-XXX,这里没有为编译器指明路径,是因为我前面在 devedaq 脚本中已将其加入
至环境变量 PATH。
又是一个漫长的等待......
OK,编译完成,673K,稍微大了点,要移到其它平台,或许得想办法做到 512 以下才好,
回头来想办法做这个工作。
安装内核
内核编译好后,将内核及配置文件拷贝至${PRJROOT}/images 下。
# cp arch/i386/boot/bzImage ${PRJROOT}/images/bzImage-2.4.27-rmk5
# cp vmlinux ${PRJROOT}/images/vmlinux-2.4.27-rmk5
# cp System.map ${PRJROOT}/images/System-2.4.27-rmk5
# cp .config ${PRJROOT}/images/2.4.27-rmk5
我采用了后缀名的方式重命名,以便管理多个不同版本的内核,当然,你也可以不用这样,
单独为每个版本的内核在 images 下新建对应文件夹也是可行的。
安装内核模块
完整内核的编译后,             剩下的工作就是建立及安装模块了,                因为我的内核并没有选择模块的支
持 (这样扩展性差了一点,               但是对于我的系统来说,         功能基本上定死了,         这样影响也不太大) ,
所以,剩下的步骤也省去了,如果你还需要模块的支持,应该:
//建立模块
#make ARCH=i386 CROSS_COMPILE=i386-linux- modules
//安装内核模块至${PRJROOT}/images
#make ARCH=i386 CROSS_COMPILE= i386-linux- \
>;INSTALL_MOD_PATH=${PRJROOT}/images/modules-2.4.18-rmk5 \
>;modules_install
最后一步是为模块建立依存关系,不能使用原生的 depmod 来建立,而需要使用交叉编译工
具。需要用到 busybox 中的 depmod.pl 脚本,很可惜,我在 busybox1.0.0 中,并没有找到这
个脚本,所以,还是借用了 busybox0.63 中 scripts 中的 depmod.pl。
将 depmod.pl 拷贝至${PREFIX}/bin 目录中,也就是交叉编译工具链的 bin 目录。
#depmod.pl \
>;-k ./vmlinux –F ./System.map \
>;-b ${PRJROOT}/images/modules-2.4.27-rmk5/lib/modules >; \
>;${PRJROOT}/images/modules-2.4.27-rmk5/lib/modules/2.4.27-rmk5/modules.dep
注:后面讨论移植内核和模块内容时,我只会提到内核的拷贝,因为我的系统并没有模块的
支持。如果你需要使用模块,只需按相同方法将其拷贝至相应目录即可。
附,内核编译清单
                                            不选
Code maturity level options
                                             不选
Loadable module support
                                          根据实际,选择处理器类型
Processor type and features
General setup --->;
Networking support
PCI support
(Any)      PCI access mode
PCI device name database
System V IPC
Sysctl support
(ELF) Kernel core (/proc/kcore) format
Kernel support for ELF binaries
Power Management support
                                                       MTD 设备,我用 CF 卡,不选
Memory Technology Devices (MTD)        --->;
                                                              不选
Parallel port support --->;
                                                             我的系统用不着即插
Plug and Play configuration --->;
即用,不选
Block devices --->;
Loopback device support
RAM disk support
(4096) Default RAM disk size (NEW)
    Initial RAM disk (initrd) support
                                          --->; 不选
Multi-device support (RAID and LVM)
                                                基本上都选了
Networking options --->;
                                                    用了默认的
ATA/IDE/MFM/RLL support --->;
                                              不选
Telephony Support --->;
                                         不选
SCSI support --->;
                                              不选
Fusion MPT device support --->;
                                                  不选
I2O device support --->;
                                                    根据实际情况选择
Network device support --->;
                                                   不选
Amateur Radio support --->;
                                                  不选
IrDA (infrared) support --->;
                                                     不选
ISDN subsystem --->;
                                                        不选
Old CD-ROM drivers (not SCSI, not IDE)      --->;
                                                      不选
Input core support --->;
Character devices --->;
Virtual terminal
    Support for console on virtual terminal
Standard/generic (8250/16550 and compatible UARTs) serial support
    Support for console on serial port
                                               不选
Multimedia devices --->;
File systems --->;
Kernel automounter version 4 support (also supports v3)
Virtual memory file system support (former shm fs)
/proc file system support
Second extended fs support
Console drivers --->;
                               调试时接显示器用
VGA text console
剩下三个都不要
Sound --->;
USB support --->;
Kernel hacking --->;
第四章                                   建立根文件系统
1、建立目录
构建工作空间时,rootfs 文件夹用来存放根文件系统,
#cd rootfs
根据根文件系统的基本结构,建立各个对应的目录:
# mkdir bin dev etc lib proc sbin tmp usr var root home
# chmod 1777 tmp
# mkdir usr/bin usr/lib usr/sbin
# ls
dev etc lib proc sbin tmp usr var
# mkdir var/lib var/lock var/log var/run var/tmp
# chmod 1777 var/tmp
对于单用户系统来说,root 和 home 并不是必须的。
准备好根文件系统的骨架后,把前面建立的文件安装到对应的目录中去。
2、拷贝链接库
把 uclibc 的库文件拷贝到刚才建立的 lib 文件夹中:
# cd ${PREFIX}/lib
[root@skynet lib]# cp *-*.so ${PRJROOT}/rootfs/lib
[root@skynet lib]# cp -d *.so.[*0-9] ${PRJROOT}/rootfs/lib
3、拷贝内核映像和内核模块
因为没有模块,所以拷贝模块就省了,
新建 boot 目录,把刚才建立好的内核拷贝过来
# cd /home/kendo/control-project/daq-module/rootfs/
# mkdir boot
# cd ${PRJROOT}/images
# cp bzImages-2.4.18-rmk5 /home/kendo/control-project/daq-module/rootfs/boot
4、建立/dev 下的设备文件
在 linux 中,所有的的设备文件都存放在/dev 中,使用 mknod 命令创建基本的设备文件。
mknod 命令需要 root 权限,不过偶本身就是用的 root 用户,本来是新建了一个用户专门用
于嵌入式制作的,不过后来忘记用了......
# mknod -m 600 mem c 1 1
# mknod -m 666 null c 1 3
# mknod -m 666 zero c 1 5
# mknod -m 644 random c 1 8
# mknod -m 600 tty0 c 4 0
# mknod -m 600 tty1 c 4 1
# mknod -m 600 ttyS0 c 4 64
# mknod -m 666 tty c 5 0
# mknod -m 600 console c 5 1
基本的设备文件建立好后,再创建必要的符号链接:
# ln -s /proc/self/fd fd
# ln -s fd/0 stdin
# ln -s fd/1 stdout
# ln -s fd/2 stderr
# ls
console fd mem null random stderr stdin stdout tty tty0 tty1 ttyS0 zero
设备文件也可以不用手动创建,                 听说 RedHat /dev 下的脚本 MAKEDEV 可以实现这一功能,
不过没有试过......
基本上差不多了,不过打算用硬盘/CF 卡来做存储设备,还需要为它们建立相关文件,因为
我的 CF 在目标机器上是 CF-to-IDE,可以把它们等同来对待,先看看 Redhat 下边 had 的相
关属性:
# ls -l /dev/hda
brw-rw----       1 root   disk    3, 0 Jan 30 2003 /dev/hda
# ls -l /dev/hda1
brw-rw----       1 root   disk    3, 1 Jan 30 2003 /dev/hda1
对比一下,可以看出,had 类型是 b,即块设备,主编号为 3,次编号从 0 递增,根限位是
rw-rw----,即 660,所以:
# mknod -m 660 hda b 3 0
# mknod -m 660 hda1 b 3 1
# mknod -m 660 hda2 b 3 2
# mknod -m 660 hda3 b 3 3
5、添加基本的应用程序
未来系统的应用程序,基本上可以分为三类:
     基本系统工具,如 ls、ifconfig 这些......
     一些服务程序,管理工具,如 WEB、Telnet......
     自己开发的应用程序
这里先添加基本的系统工具,                   有想过把这些工具的代码下载下来交叉编译,                           不过实在是麻烦,
用 BusyBox,又精简又好用......
将 busybox-1.00.tar.gz 下载至 sysapps 目录下,解压:
#tar zxvf busybox-1.00.tar.gz
#cd busybox-1.00
//进入配置菜单
#make TARGET_ARCH=i386 CROSS=i386-linux- PREFIX=${PRJROOT}/rootfs menuconfig
//建立依存关系
#make TARGET_ARCH=i386 CROSS= i386-linux- PREFIX=${PRJROOT}/rootfs dep
//编译
#make TARGET_ARCH=i386 CROSS= i386-linux- PREFIX=${PRJROOT}/rootfs
//安装
#make TARGET_ARCH=i386 CROSS= i386-linux- PREFIX=${PRJROOT}/rootfs install
# cd ${PRJROOT}/rootfs/bin
# ls
addgroup busybox chown            delgroup    echo         kill    ls      mv          ping   rm
sleep
adduser     chgrp      cp         deluser     grep        ln        mkdir  netstat ps       rmdir
umount
ash          chmod       date      dmesg         hostname    login   mount  pidof     pwd      sh
vi
一下子多了这么多命令......
配置 busybox 的说明:
A、如果编译时选择了:
Runtime SUID/SGID configuration via /etc/busybox.conf
系统每次运行命令时,都会出现“Using fallback suid method ”
可以将它去掉,不过我还是在/etc 为其建了一个文件 busybox.conf 搞定;
B 、 Do           you     want       to    build    BusyBox       with    a   Cross    Compiler?
(i386-linux-gcc) Cross Compiler prefix
这个指明交叉编译器名称(其实在编译时的命令行已指定过了......)
    安装选项下的(${PRJROOT}/rootfs) BusyBox installation prefix,             这个指明了编译好后的工
C、
具的安装目录。
D、静态编译好还是动态编译?
[ ] Build BusyBox as a static binary (no shared libs)
动态编译的最大好处是节省了宝贵空间,                  一般来说都是用动态编译,   不过我以前动态编译出
过问题(其实是库的问题,不关 busybox 的事)             ,出于惯性,我选择了静态编译,为此多付
出了 107KB 的空间。
E、其它命令,根据需要,自行权衡。
6、系统初始化文件
      内核启动时,最后一个初始化动作就是启动 init 程序,当然,大多数发行套件的 Linux
都使用了与 System V init 相仿的 init,可以在网上下载 System V init 套件,下载下来交叉编
译。另外,我也找到一篇写得非常不错的讲解如何编写初始化文件的文件,bsd-init,回头
附在后面。不过,对于嵌入式系统来讲,BusyBox init 可能更为合适,在第 6 步中选择命令
的时候,应该把 init 编译进去。
inittab
#cd ${PRJROOT}/rootfs/etc
#vi inittab
我的 inittal 文件如下:
#指定初始化文件
::sysinit:/etc/init.d/rcS
#打开一个串口,波特率为 9600
::respawn:/sbin/getty 9600 ttyS0
#启动时执行的 shell
::respawn:/bin/sh
#重启时动作
::restart:/sbin/init
#关机时动作,卸载所有文件系统
::shutdown:/bin/umount -a –r
保存退出;
rcS 脚本:
#mkdir ${PRJROOT}/rootfs/etc/init.d
#cd ${PRJROOT}/rootfs/etc/init.d
#vi rcS
脚本内容如下:
#!/bin/sh
#Set Path
#PATH=/sbin:/bin:
#export PATH
PATH=$PATH:/usr/local/sbin:.
export PATH
#start syslogd
syslogd -m 60
klogd
#install /proc
mount -n -t proc none /proc
#reinstall root file system by read/write mode(need: /etc/fstab)
mount -n -o remount,rw /
#reinstall /proc
mount -n -o remount,rw -t proc none /proc
#Trap Ctrl+C only in this shell so we can interrupt subprocesses
trap ":" 2 3 20
for i in /etc/start/S??* ;do
      # Ignore dangling symlinks (if any).
      [ ! -f "$i" ] && continue
      echo "Running $i ."
      case "$i" in
             *.sh)
                   # Source shell script for speed.
                   (
                   trap - INT QUIT TSTP
                   set start
                   . $i
                   )
                   ;;
             *)
                   # No sh extension, so fork subprocess.
                   $i start
                   ;;
      esac
      echo "Done $i ."
      echo
done
保存退出。
脚本中最后一个 for 循环,是读取/etc/start 中所有的以 S 开头的脚本,新建此目录:
#mkdir ${PRJROOT}/roofts/etc/start
新建三个启动脚本:
S01network.sh S02route.sh S03server.sh
分别对应了启动网络/路由/服务程序
S01network.sh
       脚本用来启动网卡。
       我是根据 Red hat 的作法,把网卡配置放在/etc/sysconfig/network-scripts 目录下,类似于
ifcfg-ethXX 这样子,它们的语法是:
DEVICE=eth0
BOOTPROTO=static
BROADCAST=88.88.88.255
IPADDR=88.88.88.44
NETMASK=255.255.255.0
NETWORK=88.88.88.0
ONBOOT=yes
S01network.sh 的内容如下:
#!/bin/sh
. /etc/sysconfig/network
#enable ip_forword
echo >1 /proc/sys/net/ipv4/ip_forward
#enable syn_cookie
echo >1 /proc/sys/net/ipv4/tcp_syncookies
#enable loopback interface
/sbin/ifconfig lo 127.0.0.1
#eanble ethernet interface
/usr/sbin/bootife
#set hostname
if [ -z "$HOSTNAME" -o "$HOSTNAME" = "(none)" ]
then
           HOSTNAME=localhost
fi
/bin/hostname ${HOSTNAME}
请注意这个脚本文件,有两个地方:
1、包含了另一个配置文件/etc/sysconfig/network,在这里,我也是照抄了 redhat,我的
/etc/sysconfig/network 这个文件的内容如下:
NETWORKING=yes
HOSTNAME=skynet
GATEWAY=88.88.88.2
2、在启动网卡时,我使用了
#eanble ethernet interface
/usr/sbin/bootife
bootife 是 我 自 己 写 的 一 个 C 程 序 , 作 用 是 读 取 /etc/sysconfig/network-scripts/ 下 面 的
ifcfg-ethXX 文件,并配置之,本来这里就该用 shell 来完成更合适一点,无奈,偶 shell 功底
实在差了一点(刚学几天)                      ,所以被逼无奈用 C 来完成之,后面我会附上我的 C 的源码。
S02route.sh
在/etc/start 下,建立新脚本 S02route.sh,它的作用是启动所有配置的静态路由:
#!/bin/bash
. /etc/sysconfig/network
# Add non interface-specific static-routes.
if [ -f /etc/sysconfig/static-routes ]
then
             grep "eth*" /etc/sysconfig/static-routes | while read ignore args ; do
#                       echo "/sbin/route add -"$args
                        /sbin/route add -$args
             done
fi
#Add defalut gw
/sbin/route add default gw ${GATEWAY}
OK,启动时读取的配置文件是/etc/sysconfig/static-routes ,它的语法和 Redhat 是一样的,请
参照建立此文件。
S03server.sh
新建启动脚本 S03server:
#!/bin/sh
#------------------------------------------------------------------
#-- Source
#--         Author(s)             : kendo
#--                                    Email:
kendo999@sohu.com
#--                                    
http://www.skynet.org.cn
#--         2005/10/31
#------------------------------------------------------------------
. /etc/sysconfig/bootserver
if [ "$enable_httpd" = 1 ] ; then
           . /etc/scripts/httpd.sh $1
fi
if [ "$enable_adsl" = 1 ] ; then
           ......
fi
if [ "$enable_udhcpd" = 1 ] ; then
           ......
fi
很简单,根据相应变量的值,调用相应的脚本。
1、这些启动标志变量,我定义在了/etc/sysconfig/bootserver 当中,其内容如下:
#start server on system boot
#1:yes 0:no
enable_httpd=1
enable_adsl=1
enable_udhcpd=1
2、每种服务对应的脚本,我都放在了/etc/scripts 下面。这些脚本,取决于你打算使用哪些
服务程序了。脚本的来源,可以自己编写,有可能其源码中自带有,也可以到网上查找......
我就不再一一赘述了,
OK,基本上,脚本的修改就完成了,下一步,将是建立 RamDisk。
———————————————————————————————————————
———————
附,读取网卡配置文件,启动网卡的 C 源码:
/************************************************************************
** author:kendo
** date:2005/10/26
***********************************************************************/
#include
#include
#include
#include
#include
#include
#define NETCFGDIR                   "/etc/sysconfig/network-scripts/"
struct _ifcfg{
           char device[8];
           char bootproto[8];
           char br[16];
           char netmask[16];
           char ip[16];
           char network[16];
           int onboot;
};
void ParseKey(struct _ifcfg *ifcfg,char *key,char *value)
{
           if(!strcmp(key,"DEVICE"))
           {
                       strcpy(ifcfg->device,value);
           }
           else if(!strcmp(key,"BOOTPROTO"))
           {
                       strcpy(ifcfg->bootproto,value);
           }
           else if(!strcmp(key,"BROADCAST"))
           {
                       strcpy(ifcfg->br,value);
           }
           else if(!strcmp(key,"IPADDR"))
           {
                       strcpy(ifcfg->ip,value);
           }
           else if(!strcmp(key,"NETMASK"))
           {
                       strcpy(ifcfg->netmask,value);
           }
           else if(!strcmp(key,"NETWORK"))
           {
                       strcpy(ifcfg->network,value);
           }
           else if(!strcmp(key,"ONBOOT"))
           {
                       ifcfg->onboot=(strcmp(value,"yes") ? 0 : 1);
           }
}
int main(int argc,char **argv)
{
          FILE *fp;
          DIR *dir;
          int i;
          char filename[50],buf[80];
          char *index,*key,*value,*p;
          struct _ifcfg *ifcfg;
          struct dirent *ptr;
          ifcfg=(struct _ifcfg *)malloc(sizeof(struct _ifcfg));
          memset(ifcfg,0,sizeof(struct _ifcfg));
          dir=opendir(NETCFGDIR);                                    /*打开脚本目录*/
          while((ptr=readdir(dir))!=NULL)                         /*读取所有文件*/
          {
                     if(strncmp(ptr->d_name,"ifcfg-eth",9))                  /*这里,只启动了
以太网卡^o^*/
                     {
                                continue;
                     }
                     memset(filename,0,sizeof(filename));
                     sprintf(filename,"%s%s",NETCFGDIR,ptr->d_name);
                     if((fp=fopen(filename,"r"))==NULL)                   /*打开配置文件*/
                     {
                                continue;
                     }
                     while(!feof(fp))
                     {
                                memset(buf,0,sizeof(buf));
                                if(fgets(buf,80,fp)!=NULL)                 /*逐行读取分析*/
                                {
                                           p=strchr(buf,'n');
                                           if(p)
                                           {
                                                     *p='';
                                           }
                                           index=buf;
                                           key=strtok(index,"=");               /*读取配置变
量*/
                                           value=strtok(NULL,"=");                /*读取变量
的值*/
                                          ParseKey(ifcfg,key,value);       /*分析之,存入结构
ifcfg 中*/
                                }
                     }
                     /*构建相应的命令*/
                     memset(buf,0,80);
                     strcpy(buf,"/sbin/ifconfig");
                     if(ifcfg->onboot)
                     {
                                sprintf(buf,"%s %s %s netmask %s broadcast %s",
                                          buf,
                                          ifcfg->device,
                                          ifcfg->ip,
                                          ifcfg->netmask,
                                          ifcfg->br);
                                /*直接调用 system 来实现,当然也可以自己通过 ioctl 来设置,
相应源码,我以前在 c/c++版发过*/
                                system(buf);
                     }
          }
          free(ife);
          return 0;
}
fstab 文件
#vi fstab
我的 fstab 很简单:
/dev/hda1 / ext2 defaults 1 1
none /proc proc defaults 0 0
第五章 让 MyLinux 能够启动
     前一章,我们把编译好的内核、应用程序、配置文件都拷贝至 rootfs 目录对应的子目录
中去了,这一步,就是把这些文件移植至目标机的存储器。这里,我是先另外拿一块硬盘,
挂在我的开发机上做的测试,因为我的本本用来写文档,PC 机用来做开发机,已经没有另
外的机器了......但是本章只是讲述一个一般性的过程,并不影响你直接在目标主机上的工
作。
      因为以后目标机识别硬盘序号都是 hda,而我现在直接挂上去,则会是 hdb、hdc......这
样,安装 lilo 时有点麻烦(虽然也可以实现)                           。所以我想了另一个办法:
      把新硬盘挂在 IDE0 的 primary 上,进入 linux 后,会被认为是 hda;
      原来主机的装 Redhat 的硬盘,                我将它从 IDE0 的 primary 上变到了 IDE1 的 primary,    因
为它的 lilo 早已装好,基本上不影响系统的使用;
分区和格式化
BIOS 中改为从第二个硬盘启动;也就是从我原来开发机启动,新的硬盘被识别成了 had。
#fdisk /dev/hda
用 d 参数删除已存在的所有分区
用 n 参数新建一个分区,也是就/dev/hda1
格式化
#mkfs.ext2 /dev/hda1
安装 bootloader
      因为我是 X86 平台,所以直接用了 lilo,如果你是其这平台,当然,有许多优秀的
bootloader 供你选择,你只需查看其相应的说明就可以了。
      编译 lilo 配置文件,         我的配置文件名为 target.lilo.conf,置于${PRJROOT}/rootfs/etc 目录。
内容如下所示:
boot=/dev/hda
disk=/dev/hda
            bios=0x80
image=/boot/bzImage-2.4.18-rmk5
            label=Linux
            root=/dev/hda1
append="root=/dev/hda1"
            read-only
//新建文件夹,为 mount 做新准备
#mkdir /mnt/cf
//把目标硬盘 mount 上来
#mount –t ext2 /dev/hdc1 /mnt/cf
回到 rootfs
#cd ${PRJROOT}/rootfs
拷贝所有文件至目标硬盘
#cp –r * /mnt/cf
这样,我们所有的文件都被安装至目标硬盘了,当然,它还不能引导,因为没有 bootloader。
使用如下命令:
# lilo -r /mnt/cf -C etc/target.lilo.conf
Warning: LBA32 addressing assumed
Added Linux *
-r :改变根目标为/mnt/cf ,这样配置文件其实就是/mnt/cf/etc/target.lilo.conf,也就是我们先
前建立的文件。
当然,完成这一步,需要 lilo22.3 及以后版本,如果你的版本太旧,比如 Redhat9.0 自带的,
就会出现下面的信息:
#lilo –r /mnt/cf –C etc/target.lilo.conf
Fatal: open /boot/boot.b: No such file or directory
这时,你需要升级你的 lilo,或者重新安装一个。
启动系统
#umount /mnt/cf
#reboot
将 BIOS 改为从 IDE0 启动,也就是目标硬盘。如果一切顺利,你将顺利进入一个属于你的
系统。
回头再来看看我们的工作空间吧
[root@skynet lib]# df /dev/hda1
Filesystem              1K-blocks     Used Available Use% Mounted on
/dev/hda1                  3953036     1628 3750600 1% /mnt/cf
总共花去了我 1628KB 的空间,看来是没有办法放到软盘里边去了^o^,不过一味求小,并
不是我的目标。
[root@skynet skynet]# ls ${PRJROOT}
bootloader build-tools debug doc images kernel rootfs sysapps tmp tools
这几个目录中的文件,呵呵,与本文一开头规划的一样
[root@skynet skynet]# ls build-tools/
buildroot buildroot-0.9.27.tar.tar
包含了 buildroot 源码及压缩包,              事实上 buildroot 下边还包括了 GNU 其它工具的源码、     编译
文件等诸多内容,是我们最重要的一个文件夹,不过到现在它已经没有多大用处了,如果你
喜欢,可以将它删除掉(不建议)                     。
[root@skynet skynet]# ls images
2.4.18-rmk5 bzImage-2.4.18-rmk5 System-2.4.18-rmk5 vmlinux-2.4.18-rmk5
内核映像及配置文件等,如果你有模块,因为还有相应的目录
[root@skynet skynet]# ls kernel/
linux-2.4.27 linux-2.4.27.tar.bz2
内核源码及压缩包
[root@skynet skynet]# ls rootfs/
bin boot dev etc home lib linuxrc proc root           sbin tmp   usr var
制作好的根文件系统,重中之重,注意备份......
[root@skynet skynet]# ls sysapps/
busybox-1.00 busybox-1.00.tar.gz
busybox-1.00 源码包,或许你还要继续添加/删除一些命令......
[root@skynet skynet]# ls tools
bin i386-linux i386-linux-uclibc include info lib man
这个也很重要,我们制作好的交叉开发工具链。如果你要继续开发程序,这个目录重要性就
很高了。
其它目录暂时是空的。
第六章 完善 MyLinux
      关于进一步的调试,你可以在开发机上使用 chroot /mnt/cf /bin/sh 这样的命令,以使我
们在目标根文件系统上工作。
支持多用户
      因为我在编译 busybox 时,已经将它的多用户那一大堆命令编译了进来。现在关键是的
要为其建立相应的文件;
进入原来的开发机,进入 rootfs 目录,切换根目录
#chroot rootfs/ /bin/sh
      A、建立/etc/passwd 文件,我的文件内容如下:
root:x:0:0:root:/root:/bin/bash
      B、建立/etc/group 文件,我的文件内容如下:
root:x:0:
bin:x:1:
sys:x:2:
kmem:x:3:
tty:x:4:
tape:x:5:
daemon:x:6:
disk:x:7:
      C、为 root 建立密码
#passwd root
      试试用 addgroup/addusr......这堆命令。然后重启,从目标硬盘上启动;从 console 口,
9600 登陆试试(因为我在 inittab 中启用了 ttyS0,我未来的目标机,是没有显卡的,需要从
console 口或 SSH 进去管理)
MyLinux login: root
Password:
BusyBox v1.00 (2004.10.10-04:43+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.
~#
成功了......
增加 WEB Server
      Busybox 里边有 httpd 选项,不过我编译时并没有选择,所以还是自己来安装。我使用
的软件是 thttpd-2.25b.tar.gz,将它移至 sysapps 目录下。
[root@skynet sysapps]# tar zxvf thttpd-2.25b.tar.gz
[root@skynet sysapps]# cd thttpd-2.25b
//配置
[root@skynet thttpd-2.25b]# CC=i386-linux-gcc ./configure --host=$TARGET
......
i386-linux-gcc -static htpasswd.o -o htpasswd -lcrypt
make[1]: Leaving directory `/home/skynet/sysapps/thttpd-2.25b/extras'
//拷贝至根文件目录
[root@skynet thttpd-2.25b]# cp thttpd ${PRJROOT}/rootfs/usr/sbin
//trip 处理
[root@skynet thttpd-2.25b]# i386-linux-strip ${PRJROOT}/rootfs/usr/sbin/thttpd
增加 ADSL 拨号功能
准备工具:
ppp-2.4.1.tar.gz
rp-pppoe-3.6.tar.gz
将这两个包拷贝到 sysapps/中,解压。
#cd ppp-2.4.1
./configuare –-help
没有看到帮助信息,郁闷。于是先配置它吧:
./configuration
打开 MakeFile 文件,
vi Makefile
在最开始添加如下两句:
CC=i386-linux-gcc
DESTDIR=${PRJROOT}/rootfs
前者指明了交叉编译工具
后者指明了目标目录,因为我从后面三句以有的脚本:
BINDIR = $(DESTDIR)/usr/sbin
MANDIR = $(DESTDIR)/usr/man
ETCDIR = $(DESTDIR)/etc/ppp
可以看出 DESTDIR 决定了二制进文件目录,帮助文件目录和配置文件目录
建立设备文件:
#cd ${PRJROOT}/rootfs/dev
# mknod ppp c 108 0
# chmod 600 ppp
#make;make install
交叉编译 rp-pppoe-3.6.tar.gz
#cd rp-pppoe-3.6
# CC=i386-linux-gcc ./configure --prefix=${PRJROOT}/rootfs/usr --host=$TARGET
#make;make install
增加 SNMP 功能
     一般系统都会有 SNMP 的支持,下载了 net-snmp-5.1.3.1,先看看 INSTALL 和 FAQ 文
档(因为以前从来没有碰过这个东东,见笑了......)                                 ,按照说明,在原生主机上安装了一回,
安装完成后,发现在指定安装目录下主要包括了几块文件:
1. bin:SNMP 的一些功能脚本和程序;
2. sbin:主要的代理程序和 trap 程序:snmpd 和 snmptrap
3. include/lib:自身兼容及第三方开发所需的头文件及库文件;
4. share:主要是 MIB 文件;
     回到安装目录下,运行./configuare --help,仔细查看了其安装编译选项,因为我定位的
小型的系统,只需具备基本的 SNMP 功能即可,所以:
     那些 bin 目录下的功能程序也不需要,对应--disable-applications
     bin 下的脚本也是不需要的,对应:--disable-scripts
     用户手册也不需要:--disable-manuals
     关闭 ipv6 支持:--disable-ipv6
     还有一个--enable-mini-agent 选项,说明是编译出一个最小化的 snmpd,比较有趣,试
试先。
     对于交叉编译,还需要用--host 指明目标平台。
OK,看完了帮助说明,开始编译了:
1、配置,根据以上确定的选项:
[root@skynet root]# CC=i386-linux-gcc ./configure --host=$TARGET --enable-mini-agent
--disable-ipv6 --with-endianness=little --disable-applications --disable-manuals --disable-scripts
--disable-ucd-snmp-compatibility
CC 指明了编译器;          --host 指明了我的目标平台,             这个环境变量在我前面定义的 devedaq 脚本
中。
2、编译
还算顺利,继续编译它:
[root@skynet net-snmp-5.1.3.1]# make LDFLAGS="-static"
呵呵,因为没有装 lib 库,所以我用了-static 选项,指明是静态编译;
3、安装
      安装就需要指明安装路径了,路径可以在.config 的时候指定,因为那个时候,那串东
东太长了,我在 install 时指定也不迟:
#make prefix=${TARGET_PREFIX} exec_prefix=${TARGET_PREFIX} install
4、检查一下:
[root@skynet net-snmp-5.1.3.1]# ls -l ${TARGET_PREFIX}/sbin
total 2120
-rwxr-xr-x     1 root      root       2164301 Nov 16 09:22 snmpd
snmpd 就是我们要的代理主程序了,大约静态编译有 2M。
[root@skynet net-snmp-5.1.3.1]# ls -l ${TARGET_PREFIX}/bin
total 4380
-rwxr-xr-x     2 root      root        391980 Oct 14 2004 ar
-rwxr-xr-x     2 root      root        581228 Oct 14 2004 as
......
呵呵,那堆程序和脚本没有安装,如 snmpwalk......
ls ${TARGET_PREFIX}/lib
ls -l ${TARGET_PREFIX}/include
看看我们需要的 mib 文件:
[root@skynet net-snmp-5.1.3.1]# ls ${TARGET_PREFIX}/share/snmp
mib2c.access_functions.conf              mib2c.column_defines.conf     mib2c.int_watch.conf
mib2c.old-api.conf
mib2c.array-user.conf                 mib2c.column_enums.conf       mib2c.iterate_access.conf
mib2c.scalar.conf
mib2c.check_values.conf                mib2c.conf                          mib2c.iterate.conf
mibs
mib2c.check_values_local.conf             mib2c.create-dataset.conf         mib2c.notify.conf
snmpconf-data
5、移植
基本完成了,因为 snmpd 太大了点,对它进行 strip 处理:
先备个份:
[root@skynet              net-snmp-5.1.3.1]#          cp    ${TARGET_PREFIX}/sbin/snmpd
${TARGET_PREFIX}/sbin/snmpd.bak
[root@skynet net-snmp-5.1.3.1]# i386-linux-strip ${TARGET_PREFIX}/sbin/snmpd
[root@skynet net-snmp-5.1.3.1]# ls -l ${TARGET_PREFIX}/sbin/snmpd
-rwxr-xr-x               1 root                  root            503300 Nov 16 09:30
/home/skynet/tools/i386-linux/sbin/snmpd
经过处理后,还有近 500KB 了。
因为只有 SNMP agent 功能,即 snmpd 程序,其它的都可以忽略。用了静态编译,lib 下边
那些 libnetsnmp 文件都可以不需要了,程序运行
需要 MIB 库,也就是 share 下的内容,把这两个东东拷到 rootfs 相应的目录中去:
[root@skynet              net-snmp-5.1.3.1]#          cp    ${TARGET_PREFIX}/sbin/snmpd
${PRJROOT}/rootfs/usr/sbin
[root@skynet net-snmp-5.1.3.1]# mkdir -p ${PRJROOT}/rootfs/usr/local/share
[root@skynet           net-snmp-5.1.3.1]#         cp     -r ${TARGET_PREFIX}/share/snmp
${PRJROOT}/rootfs/usr/local/share
[root@skynet                      net-snmp-5.1.3.1]#        cp              EXAMPLE.conf
${PRJROOT}/rootfs/usr/local/share/snmp/snmpd.conf
最后一步是把安装目录下的配置文件范例拷到 snmpd 启动时默认的搜索目录中去。
6、测试
打开 snmpd.conf 看看:
[root@skynet net-snmp-5.1.3.1]# vi ${PRJROOT}/rootfs/usr/local/share/snmp/snmpd.conf
有如下语句:
#         sec.name source                    community
com2sec local          localhost          COMMUNITY
com2sec mynetwork NETWORK/24                    COMMUNITY
定义了两个用户,本地及网络的,以及它们的通读密钥,按自己的需要修改一下,如:
#         sec.name source                    community
com2sec local          127.0.0.1         public
com2sec mynetwork 0.0.0.0               public
后面是定义用户的用户组等一大堆东东,事实上不用修改它们了。运行它:
[root@skynet net-snmp-5.1.3.1]# chroot ${PRJROOT}/rootfs /bin/sh
BusyBox v1.00 (2004.10.13-06:32+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.
/ # snmpd
/ # exit
在我们自己的根文件系统环境下运行它,然后退出来。用 ps 查看:
#ps -aux
......
root       32270 0.0 0.3 1212 936 ?            S      09:38 0:00 snmpd
[root@skynet net-snmp-5.1.3.1]# netstat -anu
......
udp            0        0 0.0.0.0:161         0.0.0.0:*
呵呵,已经成功启动了。用一个 SNMP 管理软件试试,可以成功地获取到信息。OK!
总结一下:
1、主程序+MIB 库大了点,共计约 2M,不过我确实没有办法再小了,而且一味求小,也不
是我的目的。
2、功能稍微简单了些,只有 agent,如果需要,可以类似地把其它程序加上去就可以了。
3、第一次玩 net-snmp,还是有点生疏,比如我静态编译二进制程序,并不需要 include/lib
下的文件,但是如何关闭它们呢?我试过--disable-ucd-snmp-compatibility,不过好像不是这
个选项......下次改进了......
增加防火墙功能
准备工具:
iptables-1.2.7a.tar.bz2
将其拷贝到 sysapps 中,解压:
#tar jxvf iptables-1.2.7a.tar.bz2
#cd iptables-1.2.7
编译
make KERNEL_DIR=${PRJROOT}/kernel/linux-2.4.27 DO_IPV6=1 PREFIX=${PREFIX}
CC=i386-linux-gcc
KERNEL_DIR:对应的内核所在路径;
DO_IPV6:对应 IPV6 支持;
PREFIX:程序及库文件安装路径
CC:编译器
安装
[root@skynet iptables-1.2.7]# make install KERNEL_DIR=${PRJROOT}/kernel/linux-2.4.27
PREFIX=${PREFIX}
拷贝
[root@skynet sbin]# cp ${PRJROOT}/tools/sbin/* ${PRJROOT}/rootfs/sbin
[root@skynet sbin]# cp -r ${PRJROOT}/tools/lib/iptables ${PRJROOT}/rootfs/lib/
[root@skynet sbin]# du -s ${PRJROOT}/rootfs/lib/
976       /home/skynet/rootfs/lib
[root@skynet sbin]# ls -lh ${PRJROOT}/rootfs/sbin/ip*
-rwxr-xr-x    1 root        root           73K Nov 16 15:36 /home/skynet/rootfs/sbin/ip6tables
-rwxr-xr-x    1 root        root           72K Nov 16 15:36 /home/skynet/rootfs/sbin/iptables
-rwxr-xr-x           1 root               root                       77K Nov 16 15:36
/home/skynet/rootfs/sbin/iptables-restore
-rwxr-xr-x           1 root               root                       74K Nov 16 15:36
/home/skynet/rootfs/sbin/iptables-save
测试,chroot,试一下 iptables 能不能正常运行。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/67300/showart_585680.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP