2011-01-13

Embedded Linux from Scratch on VMWare: (1) Boot Up to sh# Prompt

在虚拟机上从头建造一个linux系统(穷人的嵌入式), 主要参考: http://uuu.enseirb.fr/~kadionik/rmll2005/presentation/michael_opdenacker.pdf.
target硬件配置为:

host也是vmware虚拟机, 安装 debian 5.0 lenny. 把 target 硬盘连接在 host 上(scsi 0:3, linux 设备节点为 /dev/sdd)
host 上用户名为v4, 工作目录为 ~/toy
需要的一些软件包放在~/tarballs/

0: 目标
========

建立一个最小的linux系统
  • bootloader 用 grub
  • 安装 busybox
  • 安装 uclibc 运行时库(为了以后能安装其他工具); busybox 动态链接到 uclibc

1: 建立 cross toolchain
=======================

target 与 host 都是虚拟机, 架构是相同的(x86), 为何还需要 cross toolchain呢? 罪恶之源在uclibc: (1)用来build uclibc 的工具, 必须是built against uclibc的(见: http://www.uclibc.org/toolchains.html; 鸡->蛋->鸡->...); (2)用来build 与 uclibc 链接的程序的工具, 也必须是built against uclibc的; 而host上编译工具是链接到glibc的, 不符合这2个要求
利用buildroot工具来建造一个 cross toolchain
手工建造cross toolchain也是uclibc难搞
理论上, 用 buildroot 可以建造出我们最终想要的 target rootfs(uclibc + busybox + kernel + ...), 但是, 实践证明(theory vs. reality): (1)目标越大, 出问题的可能性越高; (2)自动化程度越高, 出了问题越难解决(花费额外时间解决自动化工具本身的问题; buildroot 的 make 系统的质量还比不上 kernel 或 gcc)
此外, 我也希望了解手工建立一个linux系统的过程. 因此, 我这里只是用buildroot建立一个最简化的toolchain(uclibc+gcc+binutils+kernel headers(?)), 不包含任何其它工具. 如果以后需要其它的工具, 可以用这个 toolchain 自己 build, 即使有问题, 也可各个击破
v4@elws:~/toy$ tar xjvf ../tarballs/buildroot-2010.11.tar.bz2 -C .
v4@elws:~/toy$ cd buildroot-2010.11/
v4@elws:~/toy/buildroot-2010.11$ make distclean
v4@elws:~/toy/buildroot-2010.11$ make allnoconfig # 只需要 toolchain
v4@elws:~/toy/buildroot-2010.11$ make menuconfig
-> Target Architecture: 选i386
-> Target Architecture Variant: 选pentium pro
-> Toolchain: 选择 Build/install c++ compiler and libstdc++?
v4@elws:~/toy/buildroot-2010.11$ make source # 提前下载所需软件包
buildroot 把自动下载的软件包放在 dl/ 目录. gcc, uclibc 等比较慢, 可以自己提前下载对应版本的放到 dl/ 下. 我总结的 buildroot-2010.11 对应的软件包及版本号(不完全):
  • gcc-4.3.5.tar.bz2
  • uClibc-0.9.31.tar.bz2
  • linux-2.6.36.1.tar.bz2
  • gmp-4.2.4.tar.bz2
  • mpfr-2.4.1.tar.bz2
  • binutils-2.20.1.tar.bz2
v4@elws:~/toy/buildroot-2010.11$ make uclibc-menuconfig
uclibc配置保存在 output/toolchain/uClibc-0.9.31/.config
uclibc很可能以后还需要重新配置(缺少某些功能没有选择), 需要回到这里重复开始
-> Target Architecture Features and Options
-> Target x86 Processor Family 选Pentium-Pro
v4@elws:~/toy/buildroot-2010.11$ cp output/toolchain/uClibc-0.9.31/.config toolchain/uClibc/uClibc-0.9.31.config
buildroot 使用的uclibc配置为 toolchain/uClibc/uClibc-0.9.31.config (通过make menuconfig 查看/修改)
v4@elws:~/toy/buildroot-2010.11$ make
make 一次要很久(更糟糕的是: 有时, 重复make时, buildroot会再次下载已经下载过的软件包, 这对身在大国的网络用户来说是一个噩梦)(足够洗个热水澡->出去跑一圈->再洗个热水澡:<) 生成的cross toolchain 在 output/staging(不是output/toolchain, 参考 http://buildroot.uclibc.org/buildroot.html#using)
  • c 头文件: output/staging/usr/include (包含 linux 头文件!)
  • 库文件: output/staging/usr/lib
  • 工具: output/staging/usr/bin/i686-linux-{gcc, ld, strip...}
2: build kernel
=================

build kernel 要小心的是使用cross toolchain
v4@elws:~/toy$ tar xjvf buildroot-2010.11/dl/linux-2.6.36.1.tar.bz2 -C .
内核版本最好与buildroot 使用的一致. 这里直接使用buildroot下载的
v4@elws:~/toy$ cd linux-2.6.36.1/
v4@elws:~/toy/linux-2.6.36.1$ make mrproper
修改 Makefile 中这一行: EXTRAVERSION = .1-toy-1.0
v4@elws:~/toy/linux-2.6.36.1$ make ARCH=x86 allnoconfig # 从零开始
kernel支持的架构在arch/目录下. 这里其实可以省略 ARCH=...
v4@elws:~/toy/linux-2.6.36.1$ make ARCH=x86 menuconfig
选择:
-> Processor type and features
-> Processor family(选 Processor family, 默认已选中)
Generic x86 support
计划在target 硬盘上建立 ext3 文件系统, 因此kernel要支持 ext3
-> File systems
-> Ext3 journalling file system support
硬盘为scsi, 要支持pci, scsi
-> Bus options (PCI etc.)
-> PCI support
PCI Express support
-> Device Drivers
-> SCSI device support
SCSI disk support
-> Device Drivers
-> Block devices
接下来的部分比较 tricky(提前公布谜底): host 与 target 都是虚拟机, 它们硬件架构是相同的. 在 host 执行 lspci 命令, 关于 scsi 控制器的输出为
00:10.0 SCSI storage controller: LSI Logic / Symbios Logic 53c1030 PCI-X Fusion-MPT Dual Ultra320 SCSI (rev 01)
说明 scsi 控制器采用了 Fusion 架构. linux 要从 Fusion 设备上启动, 必须在内核内建 Fusion 驱动
-> Device Drivers
-> Fusion MPT device support
-> Fusion MPT ScsiHost drivers for SPI
顺便启用DMA
-> Device Drivers
-> DMA Engine support
另外, 要启用 ELF 支持, 否则kernel 启动后将不能执行任何程序! (提前公布谜底. 我通过失败得来)
-> Executable file formats / Emulations
-> Kernel support for ELF binaries
保存配置后开始编译
v4@elws:~/toy/linux-2.6.36.1$ make ARCH=x86 CROSS_COMPILE=$HOME/toy/buildroot-2010.11/output/staging/usr/bin/i686-linux-
kernel 编译一般不会出什么问题, 因为 kernel 不依赖于任何外部库(但是编译过程中要使用一些工具, 如 perl)
10分钟左右编译完, 生成的kernel为 arch/x86/boot/bzImage

3: boot kernel
=================

接下来在target硬盘上建立文件系统, 安装grub和kernel
下面的操作以root执行
elws:~# fdisk /dev/sdd # 在target 硬盘上划分一个主分区
elws:~# mke2fs -j /dev/sdd1
elws:~# mkdir -p /mnt/toy/
elws:~# mount /dev/sdd1 /mnt/toy/
elws:~# grub-install --root-directory=/mnt/toy/ /dev/sdd
可以给grub设置一下启动菜单, 免得以后启动target手动输入启动参数
elws:~# vi /mnt/toy/boot/grub/menu.lst
输入其内容为:
default 0
timeout 5

title toy linux
root (hd0,0)
kernel /boot/bzImage rw root=/dev/sda1
把kernel 拷贝到 target 硬盘
elws:~# cp /home/v4/toy/linux-2.6.36.1/arch/i386/boot/bzImage /mnt/toy/boot/
现在可以关闭host(target 硬盘还挂在host上), 启动 target. 如果前面一切正常, kernel 应该可以挂载根分区, 然后到运行 init 时 kernel panic, 如下:


4: build busybox
===============

使用cross toolchain 来 build busybox
v4@elws:~/toy$ tar xjvf ../tarballs/busybox-1.18.1.tar.bz2 -C .
v4@elws:~/toy$ cd busybox-1.18.1/
v4@elws:~/toy/busybox-1.18.1$ make defconfig
v4@elws:~/toy/busybox-1.18.1$ make menuconfig
-> Busybox Settings
-> Build Options
-> Build BusyBox as a static binary (no shared libs) 不要选择(默认不选择)
-> Busybox Settings
-> Build Options
-> Cross Compiler prefix
填: /home/v4/toy/buildroot-2010.11/output/staging/usr/bin/i686-linux-
v4@elws:~/toy/busybox-1.18.1$ make
如果编译成功: (如果出错了, 看本节末尾的错误记录)
v4@elws:~/toy/busybox-1.18.1$ make install
busybox 安装在 _install/

编译过程中可能出错, 记录如下

(1)
In file included from /home/v4/toy/buildroot-2010.11/output/staging/usr/include/limits.h:27,
from /home/v4/toy/buildroot-2010.11/output/staging/usr/lib/gcc/i686-unknown-linux-uclibc/4.3.5/include-fixed/limits.h:122,
from /home/v4/toy/buildroot-2010.11/output/staging/usr/lib/gcc/i686-unknown-linux-uclibc/4.3.5/include-fixed/syslimits.h:7,
from /home/v4/toy/buildroot-2010.11/output/staging/usr/lib/gcc/i686-unknown-linux-uclibc/4.3.5/include-fixed/limits.h:11,
from include/platform.h:153,
from include/libbb.h:13,
from include/busybox.h:10,
from applets/applets.c:9:
/home/v4/toy/buildroot-2010.11/output/staging/usr/include/features.h:216:5: error: #error It appears you have defined _FILE_OFFSET_BITS=64. Unfortunately, uClibc was built without large file support enabled.
解决:(先后顺序重要! 如果buildroot不支持, uclibc也不会支持)
重新配置buildroot, 添加LFS
-> Toolchain
-> Enable large file (files > 2 GB) support?
重新配置uclibc, 添加Large File Support (LFS)
-> General Library Settings
-> Large File Support
(2)
networking/inetd.c:163:21: error: rpc/rpc.h: No such file or directory
解决: (顺序重要!)
给buildroot增加rpc支持:
-> Toolchain
-> Enable IPv6
Enable RPC
需要给uclibc增加rpc支持:
-> Networking Support
-> Remote Procedure Call (RPC) support
IP version 6 support
Use netlink to query interfaces
5: 启动到 sh# 提示符
==================

现在要将busybox以及uclibc运行时库安装到target
检查busybox需要的动态库:
v4@elws:~/toy$ buildroot-2010.11/output/staging/usr/bin/i686-linux-ldd busybox-1.18.1/_install/bin/busybox
checking sub-depends for 'not found'
checking sub-depends for 'not found'
libm.so.0 => not found (0x00000000)
libc.so.0 => not found (0x00000000)
/lib/ld-uClibc.so.0 => /lib/ld-uClibc.so.0 (0x00000000)
即: busybox 只依赖于libc.so, libm.so 和 ld-uClibc.so

(以下以root执行)
elws:~# mount /dev/sdd1 /mnt/toy/
elws:~# mkdir -p /mnt/toy/etc \
> /mnt/toy/etc/init.d \
> /mnt/toy/lib \
> /mnt/toy/proc \
> /mnt/toy/sys \
> /mnt/toy/dev \
>
elws:~# cd /home/v4/toy/buildroot-2010.11/output/target/lib/
elws:/home/v4/toy/buildroot-2010.11/output/target/lib# rsync -a ld-uClibc* libc.so.0 libuClibc-0.9.31.so libm* /mnt/toy/lib/
elws:/home/v4/toy/buildroot-2010.11/output/staging# cd
elws:~# rsync -a /home/v4/toy/busybox-1.18.1/_install/ /mnt/toy/
elws:~# sync
创建 busybox init 脚本:
elws:~# vi /mnt/toy/etc/inittab
输入以下内容:
# This is run first script
::sysinit:/etc/init.d/rcS

# Start an "askfirst" shell on the console
::askfirst:-/bin/sh

# Stuff to do when restarting the init process
::restart:/sbin/init

# Stuff to do before rebooting
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
创建系统启动时运行的脚本(系统初始化):
elws:~# mkdir -p /mnt/toy/etc/init.d
elws:~# vi /mnt/toy/etc/init.d/rcS
在启动脚本中mount 虚拟文件系统:
#!/bin/sh

mount -t proc none /proc
mount -t sysfs none /sys
创建 /dev/console, null 2个设备节点(否则, target kernel 启动后, shell 的输出不显示在屏幕上):
elws:~# mkdir -p /mnt/toy/dev
elws:~# mknod /mnt/toy/dev/console c 5 1
elws:~# mknod /mnt/toy/dev/null c 1 3
(注: 可通过在 host 上 ls -l /dev/xyz 查看节点号)

修改文件权限:
elws:~# cd /mnt/toy/
elws:/mnt/toy# chown -R root:root .
elws:/mnt/toy# chmod +x etc/init.d/rcS
关闭host, 启动 target:


6: TODO
============

现在已经建造了一个最基本的linux系统, rootfs 大小为2.3MB:
elws:/mnt# du -sh toy/
2.3M toy/
通过细致地精简busybox及uclibc, 还可较大幅度减小. 接下来要做:
  • acpi 电源管理. 目前 halt 后, 虚拟机电源没有切断
  • tcp/ip 网络支持
  • 启用 framebuffer, 获得图形控制台(以及企鹅logo!)

No comments:

Post a Comment