Linux操作系统不仅仅有Linux的内核, 更需要具备各种服务才能成为一个完整的操作系统. 了解Linux系统的启动过程, 对理解Linux系统是非常有益的.但是Linux系统涉及的服务众多, 不同的发行版又有差异, 所以这里只描述一个轮廓, 日后逐渐充实.
按下电源的时候, CPU执行的第一条指令是BIOS(基本输入输出系统).
对BIOS的细节知之甚少, 以后遇到的时候, 回来补充.
BIOS执行的最后, 将启动磁盘的第一个扇区(MBR)加载到内存中固定的位置(0000:7C00), 然后CPU开始执行0000:7C00处的指令, 也就是MBR中存储的指令.
MBR(Master Boot Record, 主引导记录), 是磁盘的第一个扇区, 前446字节存放引导程序, 中间64字节存放磁盘分区表, 最后两个字节固定为0x55、0xAA(0x550xAA是识别MBR的标记). 内存中0000:7C00之前的位置用来存放BIOS中断表等BIOS程序加载进去的内容.
跳转到0000:7C00后, 开始执行的就是引导程序. 因为MBR只有512字节, 所以引导程序不完成太多任务, 主要是继续加载更多的内容. Linux系统的引导程序通常是grub.
grub2.0中,执行的第一条指令(加载到0000:7C00处的指令)在grub-2.00.tar\grub-2.00\grub-2.00\grub-core\boot\i386\pc\boot.S中.
.globl _start, start;
_start:
start:
/*
* _start is loaded at 0x7c00 and is jumped to with CS:IP 0:0x7c00
*/
/*
* Beginning of the sector is compatible with the FAT/HPFS BIOS
* parameter block.
*/
jmp LOCAL(after_BPB)
nop /* do I care about this ??? */
可以从boot.S开始梳理Grub的引导过程.
引导程序通过BIOS中断完成对硬件的操作.
中断表大全 http://www.oldlinux.org/Linux.old/docs/interrupts/int-html/int.htm
grub的安装方式非常简单.假设我们要在磁盘sda中安装grub, 并且将grub需要的文件存放在第一个分区sda1中.
mount /dev/sda1 /mnt
grub-install --root-directory=/mnt --no-floppy sda
执行上述命令后, sda的MBR就被写入了grub的引导程序, 在/mnt目录下则多出一个boot目录, 里面存放着grub需要的文件.
这时候从sda启动就可以看到grub的执行.(如果要看到完整的引导,需要在boot目录下进行grub的引导配置, 指定内核和initrd, 参见grub的使用)
grub引导的具体过程涉及的内容比较深入, 目前还不甚明了, 日后有需要时补充.
grub将内核加载, 并加载了一个位于内存的根文件系统, 作为内核看到的第一个根文件系统.
initrd是内核看到的第一个根文件系统, 这个根文件系统中包含必要的驱动和设备文件, 在这里加载驱动后, 才能读取到最终的文件系统, 这时切换到最终的根文件系统.
可以将initrd解压, 查看包含的内容和完成的操作.
CentOS系列的发行版, 可以在/boot下找到img文件, 例如initrd-2.6.18-308.el5.img, 这个就是initrd. 在grub的grub.conf可以看到系统启动时使用的时那个img.
initrd-2.6.18-308.el5.img是一个gzip压缩文件, 首先需要修改后缀, 然后用gunzip解压, 再用cpio读取.
cp initrd-2.6.18-308.el5.img /tmp/initrd-2.6.18-308.el5.img.gz
cd /tmp
gunzip initrd-2.6.18-308.el5.img.gz
mkdir cpio;
cd cpio;
cpio -i -d < ../initrd-2.6.18-308.el5.img
initrd中包含一个init程序, 这是内核执行的第一个程序. CentOS中init是一个nash脚本.
CentOS中init的内容:
(挂载虚拟文件系统)
echo Creating /dev
mount -o mode=0755 -t tmpfs /dev /dev #dev使用虚拟文件系统tmpfs(内存)中
mkdir /dev/pts #/dev/pts存放ssh登陆时生成的虚拟终端设备文件
mount -t devpts -o gid=5,mode=620 /dev/pts /dev/pts #/dev/pts的文件系统类型devpts, 虚拟终端文件设备
mkdir /dev/shm #/dev/shm 只建立目录
mkdir /dev/mapper #/dev/mapper 只建立目录
(创建必要的设备文件)
echo Creating initial device nodes
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mknod /dev/urandom c 1 9
mknod /dev/systty c 4 0
...
...
mknod /dev/ttyS3 c 4 67
echo Setting up hotplug.
hotplug
echo Creating block device nodes.
mkblkdevs
(加载必要的驱动)
echo "Loading ehci-hcd.ko module"
insmod /lib/ehci-hcd.ko
echo "Loading ohci-hcd.ko module"
insmod /lib/ohci-hcd.ko
echo "Loading uhci-hcd.ko module"
insmod /lib/uhci-hcd.ko
mount -t usbfs /proc/bus/usb /proc/bus/usb
echo "Loading jbd.ko module"
insmod /lib/jbd.ko
echo "Loading vmxnet3.ko module"
...
...
insmod /lib/vmxnet3.ko
echo "Loading pvscsi.ko module"
insmod /lib/pvscsi.ko
echo "Loading vmxnet.ko module"
insmod /lib/vmxnet.ko
echo Waiting for driver initialization.
stabilized --hash --interval 1000 /proc/scsi/scsi
mkblkdevs
echo Scanning and configuring dmraid supported devices
resume LABEL=SWAP-sda2
(切换到最终的根文件系统)
echo Creating root device.
mkrootdev -t ext3 -o defaults,ro /dev/sda3 #定义root路径
echo Mounting root filesystem.
mount /sysroot #将实体根目录挂载到sysroot
echo Setting up other filesystems.
setuproot #将通过initrd的init建立的/proc /sys /dev目录中的资料转移到/sysroot
echo Switching to new root and running init.
switchroot #切入实体根目录,将原先系统的所有内容清空
initrd最后切换的最终的根文件系统, 位于内存中的临时的根文件系统随之被清空.
切换到最终的根文件系统后, 开始了Linux操作系统的初始化. 这时候就看到发行版之间的差异了, 不同的发行版有不同的处理方式, 初始化的内容也不尽相同.
初始化程序是一系列的shell脚本, 繁多复杂, 牵扯到太多的服务, 如果能够全部了解, 那么对Linux系统将会非常熟悉.
初始化脚本的组织方式主要分为System V(CentOS)和event-base(Ubuntu)两种风格.
在System V风格中,init通过/etc/inittab文件的内容得知初始化脚本、运行级别、每个级别的初始化脚本.
在event-base风格中,init遍历/etc/init目录中的事件文件,执行需要执行的脚本.
CentOS5.8版本中使用的System V风格,6.4版本中换用了event-base风格.
System V风格使用sysinit机制,所有init要执行的事项都在/etc/inittab文件中指明.
/etc/inittab是init的配置文件,指定了需要运行的脚本.可以查看对应的linux手册,man 5 inittab./etc/inittab中的每一行都使用下面的格式.
id:runlevels:action:process
id是由1-4个字符组成,用来唯一标记每一行.
runlevels指定该行使用的运行级别.
action指定init应该采取的动作
respawn: 如果process运行结束,init立即重新调用process
wait: process执行一次,init等待process运行结束
once: process执行一次
boot: 在系统启动时运行process,忽略runlevel
bootwait: 在系统启动时运行process,init等待process运行结束
off: 什么都不做
ondemand:
initdefault: 默认的运行级别
sysinit: 系统初始化执行,在boot和bootwait之前执行
powerwait:
powerfail:
powerokwait:
powerfailnow:
ctrlaltdel: init收到ctl_alt_del组合键信号时执行
kbrequest:
process是要执行的命令.
下面是CentOS5.8的/etc/inittab文件.
默认运行级别是5
init进程依次执行/etc/rc.d/rc.sysinit
对应运行级别的process(/etc/rd.c/rc 0)
重复运行/sbin/mingetty ttyXX.
id:5:initdefault:
# System initialization.
si::sysinit:/etc/rc.d/rc.sysinit
l0:0:wait:/etc/rc.d/rc 0
l1:1:wait:/etc/rc.d/rc 1
l2:2:wait:/etc/rc.d/rc 2
l3:3:wait:/etc/rc.d/rc 3
l4:4:wait:/etc/rc.d/rc 4
l5:5:wait:/etc/rc.d/rc 5
l6:6:wait:/etc/rc.d/rc 6
# Trap CTRL-ALT-DELETE
ca::ctrlaltdel:/sbin/shutdown -t3 -r now
# When our UPS tells us power has failed, assume we have a few minutes
# of power left. Schedule a shutdown for 2 minutes from now.
# This does, of course, assume you have powerd installed and your
# UPS connected and working correctly.
pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down"
# If power was restored before the shutdown kicked in, cancel it.
pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"
# Run gettys in standard runlevels
1:2345:respawn:/sbin/mingetty tty1
2:2345:respawn:/sbin/mingetty tty2
3:2345:respawn:/sbin/mingetty tty3
4:2345:respawn:/sbin/mingetty tty4
5:2345:respawn:/sbin/mingetty tty5
6:2345:respawn:/sbin/mingetty tty6
event-base风格的初始化机制,称为upstart机制.初始化过程被拆分成一个个事件,每个事件都各自的事件文件,事件文件中指明事件执行的时机和要执行的操作.
事件文件存放在/etc/init目录下,启动时init进程到/etc/init目录扫描执行需要执行的事件文件.
可以指定事件直接的依赖关系,例如B事件必须在A事件完成后开始.
init进程发出第一个事件startup,然后执行在startup时执行的事件,这批事件执行结束后各自发出相应的事件(事件文件中的emits命令),从而又启动一批事件,直至完成.
下面是centOS6.4中的事件文件/erc/init/rcS.conf.
指定了事件执行的时机(start on startup)V
停止的时机(stop on runlevel)
事件类型(task)
信息打印输出的位置(console output)
事件执行前进行的操作(pre-start script .... end script)
事件(exec /etc/rc.d/rc.sysinit)
事件结束后执行的操作(post-stop script ... end script)
# rcS - runlevel compatibility
#
# This task runs the old sysv-rc startup scripts.
start on startup
stop on runlevel
task
# Note: there can be no previous runlevel here, if we have one it's bad
# information (we enter rc1 not rcS for maintenance). Run /etc/rc.d/rc
# without information so that it defaults to previous=N runlevel=S.
console output
pre-start script
for t in $(cat /proc/cmdline); do
case $t in
emergency)
start rcS-emergency
break
;;
esac
done
end script
exec /etc/rc.d/rc.sysinit
post-stop script
if [ "$UPSTART_EVENTS" = "startup" ]; then
[ -f /etc/inittab ] && runlevel=$(/bin/awk -F ':' '$3 == "initdefault" && $1 !~ "^#" { print $2 }' /etc/inittab)
[ -z "$runlevel" ] && runlevel="3"
for t in $(cat /proc/cmdline); do
case $t in
-s|single|S|s) runlevel="S" ;;
[1-9]) runlevel="$t" ;;
esac
done
exec telinit $runlevel
fi
end script
从上面的事件文件中可以看到,CentOS6.4将原先在inittab中指定的rc.sysinit定位为一个rcS.conf事件,并且从inittab中获取运行级别,通过这种方式兼容了以往的System V风格的初始化过程.
Ubuntu中事件文件使用同样的格式.
查看事件文件的linux帮助手册,man 5 init.
以下几个事件要在startup时执行.
lja@ubuntu:/etc/init$ grep startup *
hostname.conf:start on startup
module-init-tools.conf:start on (startup
mountall.conf:start on startup
udev-fallback-graphics.conf:start on (startup and
udev-finish.conf:start on (startup
udevmonitor.conf:start on (startup
udevtrigger.conf:start on (startup
过滤掉同时依赖别的事件的事件后,得到最早执行的两个事件.
hostname.conf
mountall.conf
通过下面分析可以知道,第一批事件完成了两个工作,设置主机名,加载文件系统(磁盘).
hostname事件只是简单的设置系统的主机名.
# hostname - set system hostname
#
# This task is run on startup to set the system hostname from /etc/hostname,
# falling back to "localhost" if that file is not readable or is empty and
# no hostname has yet been set.
description "set system hostname"
start on startup
task
exec hostname -b -F /etc/hostname
mountall发出的多个事件,带动其他事件的执行.
# mountall - Mount filesystems on boot
#
# This helper mounts filesystems in the correct order as the devices
# and mountpoints become available.
description "Mount filesystems on boot"
start on startup
stop on starting rcS
expect daemon
task
emits virtual-filesystems
emits local-filesystems
emits remote-filesystems
emits all-swaps
emits filesystem
emits mounting
emits mounted
# temporary, until we have progress indication
# and output capture (next week :p)
console output
script
. /etc/default/rcS
[ -f /forcefsck ] && force_fsck="--force-fsck" # 环境变量指示是否需要修复文件系统
[ "$FSCKFIX" = "yes" ] && fsck_fix="--fsck-fix"
# set $LANG so that messages appearing in plymouth are translated
if [ -r /etc/default/locale ]; then
. /etc/default/locale
export LANG LANGUAGE LC_MESSAGES LC_ALL # 语言环境
fi
exec mountall --daemon $force_fsck $fsck_fix # mountall命令,加载fstab中指定的挂载
end script
post-stop script # 事件结束
rm -f /forcefsck 2>dev/null || true
end script
systemd supports SysV and LSB init scripts and works as a replacement for sysvinit
在/boot下找到initrd-2.6.18-308.el5.img,复制到另一个目录下(防止操作将原文件覆盖),并修改后缀名为.gz,如下:
cp initrd-2.6.18-308.el5.img /root/initrd/initrd-2.6.18-308.el5.img.gz
initrd-2.6.18-308.el5.img是一个gzip压缩文件,修改后缀后,使gunzip可以识别解压:
gunzip initrd-2.6.18-308.el5.img.gz
解压后得到.img文件,使用cpio解压.imgd到一个新目录中:
mkdir cpio;
cd cpio;
cpio -i -d < ../initrd-2.6.18-308.el5.img
在cpio目录下得到initrd中所有文件:
bin dev etc init lib proc sbin sys sysroot
cpio中的文件就是内核在initrd环境中运行时使用的根文件系统。其中的init时执行的第一个进程。Cento5.8的initrd中的cpio/init是一个nash脚本,完成到磁盘上的根文件系统的切换.
nash是专门用于inird的类似shell的解析程序,内置了一些必用的命令。
cpio/init完成以下工作:
echo Creating /dev
mount -o mode=0755 -t tmpfs /dev /dev #dev使用虚拟文件系统tmpfs(内存)中
mkdir /dev/pts #/dev/pts存放ssh登陆时生成的虚拟终端设备文件
mount -t devpts -o gid=5,mode=620 /dev/pts /dev/pts #/dev/pts的文件系统类型devpts, 虚拟终端文件设备
mkdir /dev/shm #/dev/shm 只建立目录
mkdir /dev/mapper #/dev/mapper 只建立目录
echo Creating initial device nodes
mknod /dev/null c 1 3
mknod /dev/zero c 1 5
mknod /dev/urandom c 1 9
mknod /dev/systty c 4 0
mknod /dev/tty c 5 0
mknod /dev/console c 5 1
mknod /dev/ptmx c 5 2
mknod /dev/rtc c 10 135
mknod /dev/tty0 c 4 0
mknod /dev/tty1 c 4 1
mknod /dev/tty2 c 4 2
mknod /dev/tty3 c 4 3
mknod /dev/tty4 c 4 4
mknod /dev/tty5 c 4 5
mknod /dev/tty6 c 4 6
mknod /dev/tty7 c 4 7
mknod /dev/tty8 c 4 8
mknod /dev/tty9 c 4 9
mknod /dev/tty10 c 4 10
mknod /dev/tty11 c 4 11
mknod /dev/tty12 c 4 12
mknod /dev/ttyS0 c 4 64
mknod /dev/ttyS1 c 4 65
mknod /dev/ttyS2 c 4 66
mknod /dev/ttyS3 c 4 67
echo Setting up hotplug.
hotplug
echo Creating block device nodes.
mkblkdevs
echo "Loading ehci-hcd.ko module"
insmod /lib/ehci-hcd.ko
echo "Loading ohci-hcd.ko module"
insmod /lib/ohci-hcd.ko
echo "Loading uhci-hcd.ko module"
insmod /lib/uhci-hcd.ko
mount -t usbfs /proc/bus/usb /proc/bus/usb
echo "Loading jbd.ko module"
insmod /lib/jbd.ko
echo "Loading ext3.ko module"
insmod /lib/ext3.ko
echo "Loading scsi_mod.ko module"
insmod /lib/scsi_mod.ko
echo "Loading sd_mod.ko module"
insmod /lib/sd_mod.ko
echo "Loading scsi_transport_spi.ko module"
insmod /lib/scsi_transport_spi.ko
echo "Loading mptbase.ko module"
insmod /lib/mptbase.ko
echo "Loading mptscsih.ko module"
insmod /lib/mptscsih.ko
echo "Loading mptspi.ko module"
insmod /lib/mptspi.ko
echo "Loading libata.ko module"
insmod /lib/libata.ko
echo "Loading ata_piix.ko module"
insmod /lib/ata_piix.ko
echo "Loading dm-mem-cache.ko module"
insmod /lib/dm-mem-cache.ko
echo "Loading dm-mod.ko module"
insmod /lib/dm-mod.ko
echo "Loading dm-log.ko module"
insmod /lib/dm-log.ko
echo "Loading dm-region_hash.ko module"
insmod /lib/dm-region_hash.ko
echo "Loading dm-message.ko module"
insmod /lib/dm-message.ko
echo "Loading dm-raid45.ko module"
insmod /lib/dm-raid45.ko
echo "Loading vmxnet3.ko module"
insmod /lib/vmxnet3.ko
echo "Loading pvscsi.ko module"
insmod /lib/pvscsi.ko
echo "Loading vmxnet.ko module"
insmod /lib/vmxnet.ko
echo Waiting for driver initialization.
stabilized --hash --interval 1000 /proc/scsi/scsi
mkblkdevs
echo Scanning and configuring dmraid supported devices
resume LABEL=SWAP-sda2
echo Creating root device.
mkrootdev -t ext3 -o defaults,ro /dev/sda3 #定义root路径
echo Mounting root filesystem.
mount /sysroot #将实体根目录挂载到sysroot
echo Setting up other filesystems.
setuproot #将通过initrd的init建立的/proc /sys /dev目录中的资料转移到/sysroot
echo Switching to new root and running init.
switchroot #切入实体根目录,将原先系统的所有内容清空
切换完成后,首先执行的是实体系统中的init进程。
find . | cpio -o -H newc > ../initrd_new.img #将当前目录下所有文件打包成cpo
cd ..; gzip initrd_new.img
将得到的initrd_new.img.gz复制到/boot目录下。 在/boot/grub/grub.conf中增加新的启动项:
title initrd_new (2.6.18-308.el5)
root (hd0,0)
kernel /vmlinuz-2.6.18-308.el5 ro root=LABEL=/
initrd /initrd_new.img.gz
下载busybox源码: http://www.busybox.net/
解压,编译,安装
make menconfig #需要安装curses-devel,最好选用编译成static,不然需要添加依赖的动态库
make #如果内核版本还没直至ubi, 在配置中将ubi相关的选项关闭(.config中),ubi是新的flash文件系统
make CONFIG_PREFIX=/path/from/root install #安装到的目录
自己动手定制一个linux,可以解除很多的疑惑,破除种种神秘。首先, 要有一台Linux机器。分为安装grub、编译内核、制作initrd三步。
假设我们的目标磁盘的设备文件是sdb。首先sdb进行分区、格式化。假设分成sdb1、sdb2。然后将sdb1挂载到/mnt
执行grub安装
grub-install --root-directory=/mnt --no-floppy sdb
执行上述命令后, 在/mnt目录下会多出一个boot目录。将来内核、initrd都会存放在boot目录下。
在/mnt/boot/grub下配置grub.conf。
可以参照原有系统的grub配置文件(/boot/grub/grub.conf), 这时候从sdb启动时就会进入到grub。
新建一个目录initrd
mkdir initrd
将需要的东西复制到initrd目录中
例如将busybox安装到initrd中、创建必要的几个设备文件、init程序
可以查看当前使用的系统initrd,进行参照
解压方式: cpio -idmv < filename.img
生成img
find . | cpio -o -H newc |gzip -9 >initrd.img
特别注意这一步必须在initrd目录下进行!这样才能保证解压后的内容
当可以启动进入到initrd的时候, 就说明已经制作成功。接下来需要做的就是在其中添加各种新的程序。