1. 引言
根文件系统作为类unix系统至关重要的一部分,服务于用户层和操作系统,相信很多接触这类操作系统的工程师知道这个概念的存在,并且随时在和它打交道,但相当一部分人对它知之甚少。本文通过目录说明、busybox工具集、初始化脚本、镜像制作等几部分简单说明一下根文件系统的组成及挂载后的系统初始化过程。
实验环境:
主机系统--ubuntu 12.04
busybox版本—1.25.0
arm交叉编译工具链—arm-linux-gnueabi
目标平台--qemu模拟arm平台vexpress-a9开发板
2. linux根文件系统目录概述
linux文件系统中每个目录都有自己的使命,对它们的定义和使用规范来自于FHS(filesystem hierarchy standard),该文档是根文件系统基本指导思想,这里不做过多描述。本文主要是应用方面的说明,我在linux的用户手册里摘抄并翻译了这些目录的描述,部分常见目录的描述如下:
/
整个目录树的起点,根目录
/boot
包含bootloader需要的静态文件。该目录只包含在系统引导过程中需要的文件,映射安装工具和配置文件应该在/sbin和/etc目录中。
/dev
特定文件或设备文件,比如物理设备文件
/etc
包含本机的配置文件。比较大的软件包,例如X11,也可以在该目录下创建单独的子目录。
所有的配置文件可能在该目录或是/usr/etc目录。但是程序应该在该目录定位配置文件,你可以链接/usr/etc中的文件到该目录中。
/home
直接或间接的存放不同机器不同用户的家目录。该目录结构取决于本地系统管理员的决定。
/lib
该目录下存放共享库和可加载驱动程序,共享库用于启动系统。运行根文件系统中的可执行程序
/media
该目录包含可移动存储设备的挂载点,例如CD、DVD或USB设备
/mnt
该目录是一个用于临时挂载的文件系统的挂载点,在一些linux发行版中,含有若干子目录,目的是为不同的临时文件系统提供挂载点。
/opt/
该目录应该存放包含静态文件的软件包。
/proc
proc文件系统的挂载点,proc文件系统提供了运行中的进程和内核的信息。
/root
root用户的家目录。
/sbin
类似于/bin,包含系统引导所需要的命令,这些命令普通用户不能执行。
/tmp
该目录用于存储临时性的文件,可能是不需要通知用户所删除的文件,常规性处理或系统引导时所产生的文件。
/usr
该目录通常挂载一个单独的分区,他应该只包含可分享的,只读的的数据,这样才能让运行linux的不同机器挂载。
/usr/bin
该目录是可执行程序的主目录。大多数程序用于普通用户执行,这些程序不用于引导系统或是修复系统,而且也不是本地安装的。
/usr/etc
分享到全站的配置文件可能存储在这个目录。然后,所有命令都应该在/etc目录中索引这些配置文件,在/etc/中的链接文件应该指向该目录中的配置文件
/usr/include
C编译器所用的头文件
/usr/lib
该目录存放目标库,包括动态库或不直接调用的增强程序。复杂程序可能有单独的子目录。
/usr/local
用于系统管理员安装软件的目录
/usr/local/bin
存储本地的二进制程序
/usr/local/doc
存储本地文档
/usr/local/etc
存储本地安装程序的配置文件
/usr/local/lib
存储本地安装程序的相关库文件
/usr/local/include
本地C编译器的头文件
/usr/local/info
本地安装程序的信息页
/usr/local/man
本地安装程序的用户手册
/usr/local/sbin
系统管理员安装系统程序到这个目录
/usr/local/share
本地应用数据,用于不同架构相同系统共享数据
/usr/sbin
该目录包含用于系统管理的二进制可执行程序,但不是用于引导、修复系统的基础程序
/usr/share
该目录存储特定应用程序数据的子目录,可用于不同架构相同os的机器之前实现分享。
/var
用于存储可能会有大小变化的文件,例如spool或log文件
/var/cache
程序数据缓存
/var/lib
程序的可变的状态信息
/var/lock
用于存储lock文件
/var/log
存储各种各样的log
/var/run
运行时可变文件,像存储进程PID或者是记录用户信息的文件
/var/spool
存储不同程序的spooled文件
更详细的说明请参考https://linux.die.net/man/7/hier
3. 创建文件系统必要的目录
创建根目录
mkdir rootfs
cd rootfs
创建系统目录
mkdir bin boot dev etc lib mnt proc root sbin sys tmp usr var
创建一些常用二级目录
mkdir etc/init.d etc/rc.d
mkdir usr/sbin usr/bin usr/lib usr/modules
mkdir var/lib var/lock var/run /tmp
4. busybox工具集
busybox下载
wgethttp://www.busybox.Net/downloads/busybox-1.25.0.tar.bz2 –no-check-certificate
编译器安装
sudo apt-get install gcc-arm-linux-gnueabi
环境变量设置
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabi-
另外还可以使用下面的方法设置,只针对busybox设置,不影响其他的编译环境,在busybox根目录的Makefile中做如下修改:
CROSS_COMPILE ?= arm-linux-gnueabi-
#ARCH ?= $(SUBARCH)
ARCH ?= arm
配置busybox
make defconfig
执行make menuconfig定制自己的busybox,然而,可能会遇到的下面的问题:
图 4.1 busybox的menuconfig问题
这是你的主机系统没安装过ncurses软件包导致的,该软件包用于终端显示,menuconfig必须用到的,我们这里安装下好了:
sudo apt-get install libncurses5-dev libncursesw5-dev
尽管这里说明了可以通过menuconfig定制busybox,但是我们的实验中不需要做任何修改,默认配置已经满足需求。
在配置busybox的同时,有一个重要的部分需要说明,就是根文件系统下/dev目录下设备节点的创建,通过互联万网罗一下常见方法,其中有一篇文章写的比较清楚,基本有三种方法可以胜任这个工作,原文摘抄如下:
1. 手动创建:在制作根文件系统的时候,就在dev目录下创建好要使用的设备文件,系统挂接根文件系统后,就可以使用dev目录下的设备文件了。
2. 使用devfs文件系统:这种方法已经过时,具有不确定的设备映射、没有足够的主/次设备号、devfs消耗大量的内存。
3. udev:它是个用户程序,能根据系统中硬件设备的状态动态的更新设备文件,包括设备文件的创建、删除等。它的操作相对复杂,但灵活性很高。
而busybox支持mdev,mdev 是busybox自带的一个简化版的udev,适合于嵌入式的应用场景。其具有使用简单的特点。它的作用是在系统启动和热插拔或动态加载驱动程序时,自动产生驱动程序所需的节点文件。在以busybox为基础构建嵌入式linux的根文件系统时,使用它是最优的选择。
这里选择mdev的方式,busybox默认已经选择了,可以进menuconfig确认下:
Linux System Utilities --->
[*] Support /etc/mdev.conf
[*] Support subdirs/symlinks
[*] Support regular expressions substitutions when renaming device
[*] Support command execution at device addition/removal
配置完成后进行编译安装
make
make install
由于并没有制定安装目录,所以编译好的文件都安装在busybox-x.xx.x/_install目录下
也可以设置自己的安装路径,通常两种方法:
1. 设置安装前缀
make CONFIG_PREFIX={your path} install
2. 配置menuconfig
Busybox Settings --->
Installation Options ("make install"behavior) --->
(./_install)BusyBox installation prefix
安装后*目录如下:
图 4.2 busybox编译完成后生成的目录和文件
拷贝安装好的文件
然后拷贝Install目录下所有文件到已经建立好的根目录下
cp命令加上-a参数可以copy软链接
copy交叉工具链的lib到已经建立的*/lib目录下
cp -a /usr/arm-linux-gnueabi/lib/* lib/
5. 创建必要的初始化文件
inittab—init进程调用
# /etc/inittab
#
# Copyright (C) 2016-12-06 songxiongwei
#
# Note: BusyBox init doesn't supportrunlevels. The runlevels field is
# completely ignored by BusyBox init. Ifyou want runlevels, use sysvinit.
#
# Format for each entry:<id>:<RUnlevels>:<action>:<process>
#
# id == tty to run on, or empty for/dev/console.
# If specified, then /dev/$id device mustexist
# runlevels == ignored, busybox doesn'tsupport it
# action == one of sysinit, respawn,askfirst, wait, and once
# process == program to run
# Startup the system
# mount all the file systems specified in/etc/fstab
#挂载fstab指定的文件系统
::sysinit:/bin/mount -a
# now run any rc scripts
#执行初始化系统的脚本rcS
::sysinit:/etc/init.d/rcS
#Set hostname
#设置主机名
::sysinit:/bin/hostname -F /etc/hostname
#Enable console logon
#设置登录串口,下面的命令表示需要密码验证
#null::respawn:/sbin/getty -L ttyAMA0 115200 vt100
#也可以通过指定的串口直接登录系统
#ttyAMA0::respawn:-/bin/sh
ttyAMA0::askfirst:-/bin/sh
#askfirst类似respawn,不过它的主要用途是减少系统上执行的终端应用程序的数量。它将会促使init在控制台上显示“Please press Enter to active this console”的信息,并在重新启动之前等待用户按下enter键。
# system daemon
打开system log和kernel log
#null::respawn:/sbin/syslogd -n
#null::respawn:/sbin/klogd -n
# Stuff to do before rebooting
#关闭system log和kernel log
#null::shutdown:/bin/killall klogd
#null::shutdown:/bin/killall syslogd
#卸载所有已挂载的挂载点
null::shutdown:/bin/umount -a -r
#null::shutdown:/sbin/swapoff –a
inittab脚本也可以是没有的,如果没有busybox init按照下面的步骤进行初始化:
::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
tty2::askfirst:/bin/sh
tty3::askfirst:/bin/sh
tty4::askfirst:/bin/sh
这时候只要把挂载命令写在rcS脚本或是单独写脚本,让rcS调用,两种方法都可以。
/etc/init.d/rcS
服务启动脚本,执行S开头的脚本,可以自定义。本文中设备文件的创建通过/etc/rc.d/S10mdev完成。
#!/bin/sh
# Start all init scripts in /etc/init.d
# executing them in numerical order.
#
echo "#####################callrcS#####################"
for i in /etc/rc.d/S??* ;do
# Ignore dangling symlinks (if any).
[ ! -f "$i" ] && continue
case "$i" in
*.sh)
# Sourceshell script for speed.
(
trap -INT QUIT TSTP
setstart
. $i
)
;;
*)
# No shextension, so fork subprocess.
$i start
;;
esac
done
/etc/rc.d/S10mdev
用于创建设备文件,供rcS调用。
#!/bin/sh
echo "call S10mdev"
if [ ! -x /sbin/mdev ]
then
exit 0
fi
echo "mdev ok"
case "$1" in
start)
echo "/sbin/mdev" >/proc/sys/kernel/hotplug
# put /dev in a tmpfs
mount -n -o mode=0755 -t tmpfs mdev /dev
# Create static device nodes in /dev
mknod /dev/console c 5 1
chmod 600 /dev/console
mknod /dev/null c 1 3
chmod 666 /dev/null
# make and mount devpts
mkdir /dev/pts
mount -n -t devpts devpts /dev/pts
echo "Starting the hotplug eventsdispatcher mdev"
/sbin/mdev -s
mkdir /dev/shm
;;
stop)
;;
*)
echo "Usage: /etc/rc.d/init.d/mdev{start|stop}"
echo
exit 1
;;
esac
exit 0
/etc/profile
用来设置环境变量等
export PATH=\
/bin:\
/sbin:\
/usr/bin:\
/usr/sbin:\
/usr/local/bin
# If running interactively, then:
if [ "$PS1" ]; then
export PS1="[\u@\h \W]\\$ "
export USER=`id -un`
export LOGNAME=$USER
export HOSTNAME=`/bin/hostname`
export HISTSIZE=1000
export HISTFILESIZE=1000
export PAGER='/bin/more '
export EDITOR='/bin/vi'
export INPUTRC=/etc/inputrc
exportDMALLOC_OPTIONS=debug=0x34f47d83,inter=100,log=logfile
export DISPLAY=:0
export LD_LIBRARY_PATH=/lib:/usr/lib/
### Some aliases
alias ll='ls -l'
alias ps2='ps facux '
alias ps1='ps faxo "%U %t %p %a" '
alias af='ps af'
alias cls='clear'
alias df='df -h'
alias indent='indent -bad -bap -bbo -nbc -br-brs -c33 -cd33 -ncdb -ce -ci4 -cli0 -cp33 -cs -d0 -di1 -nfc1 -nfca -hnl -i4-ip0 -l75 -lp -npcs -npsl -nsc -nsob -nss -ts4 '
#alias bc='bc -l'
alias minicom='minicom -c on'
alias calc='calc -Cd '
alias bc='calc -Cd '
fi;
/etc/fstab
文件系统挂载配置,mount-a命令执行的时候读取,可以把这个命令加入到inittab中,也可以自己写脚本,通过rcS调用,为了简便我选择第一种做法,具体做法参考/etc/inittab。
# /etc/fstab: static file systeminformation.
#
#<File system> <mount pt><type> <options> <dump> <pass>
#/dev/root / ext3 rw,noauto 0 1
proc /proc proc defaults 0 0
#devpts /dev/pts devpts defaults 0 0
sysfs /sys sysfs defaults 0 0
#tmpfs /dev/shm tmpfs mode=0777 0 0
tmpfs /tmp tmpfs mode=1777 0 0
其中proc和sys目录mdev需要,详情请看/etc/rc.d/S10mdev
/etc/protocols
该文件是网络协议定义文件,里面记录了TCP/IP协议族的所有协议类型。文件中的每一行对应一个协议类型,它有3个字段,中间用TAB或空格分隔,分别表示“协议名称”、“协议号”和“协议别名”。
# Internet (IP) protocols
#
# Updated fromhttp://www.iana.org/assignments/protocol-numbers and other
# sources.
ip 0 IP # internet protocol, pseudoprotocol number
hopopt 0 HOPOPT # IPv6 Hop-by-Hop Option [RFC1883]
icmp 1 ICMP # internet control message protocol
igmp 2 IGMP # Internet Group Management
ggp 3 GGP # gateway-gateway protocol
ipencap 4 IP-ENCAP # IP encapsulated in IP (officially``IP'')
st 5 ST # ST datagram mode
tcp 6 TCP # transmission control protocol
egp 8 EGP # exterior gateway protocol
igp 9 IGP # any private interior gateway(Cisco)
pup 12 PUP # PARC universal packet protocol
udp 17 UDP # user datagram protocol
hmp 20 HMP # host monitoring protocol
xns-idp 22 XNS-IDP # Xerox NS IDP
rdp 27 RDP # "reliable datagram"protocol
iso-tp4 29 ISO-TP4 # ISO Transport Protocol class 4[RFC905]
dccp 33 DCCP # Datagram Congestion Control Prot.[RFC4340]
xtp 36 XTP # Xpress Transfer Protocol
ddp 37 DDP # Datagram Delivery Protocol
idpr-cmtp 38 IDPR-CMTP # IDPR Control Message Transport
ipv6 41 IPv6 # Internet Protocol, version 6
ipv6-route 43 IPv6-Route # Routing Header for IPv6
ipv6-frag 44 IPv6-Frag # Fragment Header for IPv6
idrp 45 IDRP # Inter-Domain Routing Protocol
rsvp 46 RSVP # Reservation Protocol
gre 47 GRE # General Routing Encapsulation
esp 50 IPSEC-ESP # Encap Security Payload [RFC2406]
ah 51 IPSEC-AH # Authentication Header [RFC2402]
skip 57 SKIP # SKIP
ipv6-icmp 58 IPv6-ICMP # ICMP for IPv6
ipv6-nonxt 59 IPv6-NoNxt # No Next Header for IPv6
ipv6-opts 60 IPv6-Opts # Destination Options for IPv6
rspf 73 RSPF CPHB # Radio Shortest Path First (officiallyCPHB)
vmtp 81 VMTP # Versatile Message Transport
eigrp 88 EIGRP # Enhanced Interior Routing Protocol(Cisco)
ospf 89 OSPFIGP # Open Shortest Path First IGP
ax.25 93 AX.25 # AX.25 frames
ipip 94 IPIP # IP-within-IP EncapsulationProtocol
etherip 97 ETHERIP # Ethernet-within-IP Encapsulation[RFC3378]
encap 98 ENCAP # Yet Another IP encapsulation[RFC1241]
# 99 # any private encryption scheme
pim 103 PIM # Protocol Independent Multicast
ipcomp 108 IPCOMP # IP Payload Compression Protocol
vrrp 112 VRRP # Virtual Router RedundancyProtocol [RFC5798]
l2tp 115 L2TP # Layer Two Tunneling Protocol[RFC2661]
isis 124 ISIS # IS-IS over IPv4
sctp 132 SCTP # Stream Control TransmissionProtocol
fc 133 FC # Fibre Channel
mobility-header 135 Mobility-Header #Mobility Support for IPv6 [RFC3775]
udplite 136 UDPLite # UDP-Lite [RFC3828]
mpls-in-ip 137 MPLS-in-IP # MPLS-in-IP [RFC4023]
manet 138 # MANETProtocols [RFC5498]
hip 139 HIP # Host Identity Protocol
shim6 140 Shim6 # Shim6 Protocol [RFC5533]
wesp 141 WESP # Wrapped Encapsulating SecurityPayload
rohc 142 ROHC # Robust Header Compression
/etc/mdev.conf用于动态创建设备文件的配置文件
# system all-writable devices
full 0:0 0666
null 0:0 0666
ptmx 0:0 0666
random 0:0 0666
tty 0:0 0666
zero 0:0 0666
# console devices
tty[0-9]* 0:5 0660
ttyS[0-9]* root:root 660
# loop devices
loop[0-9]* 0:0 0660 =loop/
# i2c devices
i2c-0 0:0 0666 =i2c/0
i2c-1 0:0 0666 =i2c/1
# frame buffer devices
fb[0-9] 0:0 0666
# input devices
mice 0:0 0660 =input/
mouse.* 0:0 0660 =input/
event.* 0:0 0660 =input/
ts.* 0:0 0660 =input/
# rtc devices
rtc0 0:0 0644 >rtc
rtc[1-9] 0:0 0644
# misc devices
mmcblk0p1 0:0 0600 =sdcard */bin/hotplug.sh
sda1 0:0 0600 =udisk * /bin/hotplug.sh
/etc/hostname文件
myroot
/etc/hosts文件
127.0.0.1 localhost
/etc/group
内容格式:
组名:口令:组标识号:组内用户列表
(1)“组名”是用户组的名称,由字母或数字构成。与/etc/passwd中的登录名一样,组名不应重复。
(2)“口令”字段存放的是用户组加密后的口令字。一般Linux系统的用户组都没有口令,即这个字段一般为空,或者是*。
(3)“组标识号”与用户标识号类似,也是一个整数,被系统内部用来标识组。
(4)“组内用户列表”是属于这个组的所有用户的列表/b],不同用户之间用逗号(,)分隔。这个用户组可能是用户的主组,也可能是附加组。
root::0:root
bin::1:root,bin,daemon
daemon::2:root,bin,daemon
sys::3:root,bin,adm
adm::4:root,adm,daemon
tty::5:
disk::6:root
lp::7:daemon,lp
mem::8:
kmem::9:
wheel::10:root
mail::12:mail
news::13:news
uucp::14:uucp
man::15:
games::20:
gopher::30:
dip::40:
ftp::50:
lock::54:
nobody::99:
users::100:
slocate:x:21:
tape:x:16:
audio:x:17:
video:x:18:
cdrom:x:25:
floppy:x:19:
utmp:x:22:
mailnull:x:47:
xfs:x:43:
ntp:x:38:
rpc:x:32:
gdm:x:42:
rpcuser:x:29:
nfsnobody:x:65534:
nscd:x:28:
ident:x:98:
radvd:x:75:
postgres:x:26:
apache:x:48:seh
squid:x:23:
named:x:70:
pcap:x:77:
junkbust:x:73:
pppusers:x:44:
popusers:x:45:
slipusers:x:46:
mailman:x:41:
mysql:x:27:
ldap:x:55:
pvm:x:24:
nogroup:x:65534:
user:x:500:
messagebus:x:1000:
haldaemon:x:1001:
/etc/passwd
该文件存放的是用户账户信息,由6个分号组成的7个信息,解释如下:
(1) 用户名。
(2) 密码(已经加密)
(3) UID(用户标识),操作系统自己用的
(4) GID组标识。
(5) 用户全名或本地帐号
(6) 开始目录
(7) 登录使用的Shell,就是对登录命令进行解析的工具。
root:x:0:0:root:/root:/bin/sh
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:100:sync:/bin:/bin/sync
mail:x:8:8:mail:/var/spool/mail:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
operator:x:37:37:Operator:/var:/bin/sh
haldaemon:x:68:68:hald:/:/bin/sh
dbus:x:81:81:dbus:/var/run/dbus:/bin/sh
ftp:x:83:83:ftp:/home/ftp:/bin/sh
nobody:x:99:99:nobody:/home:/bin/sh
sshd:x:103:99:Operator:/var:/bin/sh
default:x:1000:1000:Default non-rootuser:/home/default:/bin/sh
/etc/shadow
用超级权限建立,解释如下:
(1) 帐号名称
(2) 密码:这里是加密过的,但高手也可以解密的。要主要安全问题(代!符号标识该帐号不能用
来登录)
(3) 上次修改密码的日期
(4) 密码不可被变更的天数
(5) 密码需要被重新变更的天数(99999表示不需要变更)
(6) 密码变更前提前几天警告
(7) 帐号失效日期
(8) 帐号取消日期
(9) 保留条目,目前没用
root::10933:0:99999:7:::
bin:*:10933:0:99999:7:::
daemon:*:10933:0:99999:7:::
adm:*:10933:0:99999:7:::
lp:*:10933:0:99999:7:::
sync:*:10933:0:99999:7:::
shutdown:*:10933:0:99999:7:::
halt:*:10933:0:99999:7:::
uucp:*:10933:0:99999:7:::
operator:*:10933:0:99999:7:::
ftp:*:10933:0:99999:7:::
nobody:*:10933:0:99999:7:::
default::10933:0:99999:7:::
6. 镜像制作
上面所介绍的目录和配置文件,基本可以保证根文件系统的正常运行,是时候制作自己的镜像了,具体方法如下:
生成镜像
dd if=/dev/zero of=rootfs.ext3 bs=1Mcount=128
格式化成ext3格式
mkfs.ext3 rootfs.ext3
创建临时挂载点并挂载
mkdir tmpfs
mount -t ext3 rootfs.ext3 tmpfs/ -o loop
copy已经准备好的目录和文件
cp -r rootfs/* tmpfs/
卸载
sudo umount tmpfs
最后,把制作好的根文件系统镜像在qemu上实验一把:
图 6.1 系统初始化完成之后,登录串口状态
当整个系统正常运行起来,你的成就感是否随之而来了呢?更理性的问题是,你对linux系统的初始化基本流程是否掌握了呢?如果让你在系统启动过程中加入其他配置,比如以太网的初始化,应该怎么做?
参考资料:
文件系统的制作流程
http://blog.chinaunix.net/uid-26524139-id-3051743.html
http://www.cnblogs.com/thinkinglife/p/3931497.html
FHS的规范文档
http://refspecs.linuxfoundation.org/fhs.shtml
linux系统目录说明
https://linux.die.net/man/7/hier
对busybox初始化调用inittab的说明
http://blog.csdn.net/lqrensn/article/details/5955740
mdev的用法来自busybox文档的中文翻译
http://www.cnblogs.com/hnrainll/archive/2011/06/25/2090182.html
/etc/group /etc/passwd /etc/shadow文件格式说明
http://www.cnblogs.com/suger/p/3831905.html
扩展阅读:
linux根文件系统挂载过程
http://www.cnblogs.com/armlinux/archive/2011/03/30/2396825.html
fstab的介绍
https://wiki.archlinux.org/index.php/Fstab_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)
有关fstab配置的介绍也可参考mount的man手册,手册中也有对mtab和/proc/mounts的说明