最近学习泰山派有点乱,想学习下移植uboot和linux,苦于没思路,正好看到简说Linux的文章和视频,按照步骤操作了下,自己制作了1个根文件系统,并用QEMU成功启动。记录下制作过程:
先从简单的busybox开始
其实准确的说,是制作x86架构下的busybox根文件系统
因为不用准备交叉编译工具链了,比较简单
一. 准备下载文件:
1. Linux内核:版本4.9.229
https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/
2. Busybox 1.36.0
简说Linux使用的是1.30.0,我看错了,但是仍然能制作成功
https://busybox.net/
3. 安装QEMU
二. 编译环境:
Ubuntu 18.04
gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
三. 编译内核
1. 将内核压缩包解压
tar xvf linux-4.9.229.tar.gz
2. 进入内核根目录
cd linux-4.9.229/
3. 设置环境变量
这里有个点就是需要在编译时指明Linux移植的架构以及使用哪种工具链。
打开Linux内核的根目录中用vi打开顶层Makefile,从240行开始讲了这两个点。
带着下面两个问题读一遍
第一个问题,不指明ARCH和CROSS_COMPILE行不行?
第二个问题,ARCH和CROSS_COMPILE应该设置为什么呢?
240 # When performing cross compilation for other architectures ARCH shall be set
241 # to the target architecture. (See arch/* for the possibilities).
//当执行交叉编译时,ARCH可任意被设置为目标架构(具体可参考arch目录下的文件夹名称)
242 # ARCH can be set during invocation of make:
243 # make ARCH=ia64
//ARCH可以在调用make命令时设置(命令行中)
244 # Another way is to have ARCH set in the environment.
//另一种方法是将ARCH设置在环境变量中 (例如export ARCH=x86)
245 # The default ARCH is the host where make is executed.
//ARCH的默认值是编译主机的架构类型(虚拟机是X86_64)
246
247 # CROSS_COMPILE specify the prefix used for all executables used
248 # during compilation. Only gcc and related bin-utils executables
249 # are prefixed with $(CROSS_COMPILE).
CROSS_COMPILE指明了交叉编译的工具链的前缀,只有gcc和其相关的工具需要指明前缀。
250 # CROSS_COMPILE can be set on the command line
251 # make CROSS_COMPILE=ia64-linux-
CROSS_COMPULE可以在命令行中指明工具链例如 make CROSS_COMPILE=ia64-linux
252 # Alternatively CROSS_COMPILE can be set in the environment.
或者可以在环境变量中设置CROSS_COMPILE的值(export CROSS_COMPILE=ia64-linux)
253 # A third alternative is to store a setting in .config so that plain
254 # "make" in the configured kernel build directory always uses that.
第三种方法是在.config中设置CROSS_COMPILE的值
255 # Default value for CROSS_COMPILE is not to prefix executables
CROSS_COMPILE默认情况下是没有前缀的
256 # Note: Some architectures assign CROSS_COMPILE in their arch/*/Makefile
注意:一些架构将CROSS_COMPILE的值放在arch/对应架构文件夹/Makefile中
第一个问题,不指明ARCH和CROSS_COMPILE行不行?
行,但是要看你想要将内核按照什么架构编译。如果编译为主机对应架构,ARCH和CROSS_COMPILE应该都不需要指明;
但是你要编译为其他的架构,必须要指明ARCH和CROSS_COMPILE
第二个问题,ARCH和CROSS_COMPILE应该设置为什么呢?
要根据对应架构来设置;
例如要对应arm架构,那么ARCH=arm CROSS_COMPILE=arm-linux-gnueabi
既然本例是要编译x86架构的根文件系统,那么ARCH需要指明下,CROSS_COMPILE就不需要了。
#export ARCH=x86
4. 生成.config文件来配置内核
#make help
输入这个命令可以看到make构建内核镜像的使用帮助
使用make命令加x86_64的默认配置,生成配置文件
#make x86_64_defconfig
其中有提到 使用make x86_64_defconfig可以为x86_64架构构建内核,生成.config文件,即内核默认配置文件。
我们到linux-4.9.229/arch/x86/configs目录下看看x86_64_defconfig里有什么
里面有大量的配置,=y的就是被选中的配置,#注释掉的就是没有被选中的配置。
因为内核配置有大量的配置,如果一个一个手动选择很累,所以出现了默认的配置文件,我们只需要根据架构选择默认的即可,如果默认的不好用,我们可以在下一步微调配置。
#make menuconfig
1个可视化的config构建菜单出现了,现在选项都是根据我们刚刚make x86_64_defconfig设置好的,如果我们想要改什么,从这里改就好
4.1.:我们检查下是否启动了ram filesystem 和 ram disk, initramfs/initrd
在General Setup --->中,*表明已启用。
为什么要启用ram filesystem 和ram disk,我问了下文心一言:
Ramdisk(RAM Disk)和Ramfs(RAM File System)都是基于内存的文件系统技术,它们将内存中的一部分区域模拟成磁盘或文件系统,以提供快速的数据访问能力。以下是关于Ramdisk和Ramfs的详细解释:
Ramdisk(RAM Disk)
定义:
- Ramdisk是一种虚拟磁盘技术,它将内存中的一块区域作为物理磁盘来使用。数据实际上存储在RAM内存中,因此访问速度非常快。
特点:
- 固定大小:Ramdisk的大小在创建时就已经确定,不能动态增长。
- 易失性:由于数据存储在RAM中,当电源切断时,存储在Ramdisk上的数据会丢失。
- 快速访问:由于数据存储在内存中,Ramdisk的访问速度远快于传统的硬盘驱动器。
应用场景:
- 临时存储数据,如缓存文件、临时文件等。
- 加载引导文件,特别是在嵌入式系统或Linux启动过程中。
- 内存测试,用于测试内存的稳定性和性能。
Ramfs(RAM File System)
定义:
- Ramfs是一种基于内存的文件系统,它将内存中的一部分区域模拟成文件系统,而不是磁盘。
特点:
- 动态增长:与Ramdisk不同,Ramfs的大小是动态增长的,它可以根据需要自动调整大小。
- 易失性:同样,由于数据存储在RAM中,Ramfs也具有易失性。
- 快速访问:Ramfs也提供了快速的数据访问能力。
应用场景:
- 临时文件存储,特别是在需要频繁读写操作且对性能要求较高的场景中。
- 作为系统启动时的根文件系统,用于加载必要的程序和配置文件。
总结
Ramdisk和Ramfs都是基于内存的文件系统技术,它们提供了快速的数据访问能力。Ramdisk适合需要固定大小且对性能要求极高的场景,而Ramfs则更适合需要动态调整大小且对性能有较高要求的场景。在实际应用中,可以根据具体需求选择合适的技术。
需要注意的是,由于Ramdisk和Ramfs都使用内存作为存储介质,因此它们都具有易失性。在电源切断或系统重启后,存储在其中的数据会丢失。因此,在使用这些技术时,需要确保重要数据有可靠的备份和恢复机制。
可见两个都是用RAM模拟硬盘,区别是
Ramdisk是固定大小,分配好后就不变了;用于加载引导文件
Ramfs的大小是动态的;用于加载根文件系统
4.2 设置Ram disk的大小
因为是Ram disk是固定分配的,所以我们先把它的大小设定好,方便一会使用
找到Device Drivers
找到Block devices,输入y选中
[*] Block devices --->
<*> RAM block device support
(65536) Default RAM disk size (kbytes)
选中Default RAM disk size (kbytes),按Enter进入,输入65536,保存
按->箭头保存到.config中,退出
4.3 make编译内核
#make
编译了20分钟后,在arch/x86/boot中出现了bzImage的内核镜像压缩文件,一会我们加载使用
四. 编译Busybox
在此之前我们先看下busybox的启动流程
由init/init.c文件中的init_main()函数启动,解析inittab文件后,正式开始
1. 解压Busybox压缩包
# tar xvf busybox-1.36.0.tar.bz2
2. 配置Busybox 配置Busybox
# make menuconfig
这里和编译内核一样,也需要生成配置文件,不同的是没有使用mek defconfig的方式先生成默认的配置,而是直接从menuconfig中生成
选中Settings,回车
选中Build static binary (no sahred libs),按y,当前面出现※时,这项就被选中了
保存,退出
3. 编译Busybox
# make
4. 安装Busybox
# make install
输入make help,可以看到make install命令用于安装Busybox到某个目录中
目录默认是:Busybox根目录/_install 中
修改目录的方式:
1. 我们可以在使用make menuconfig中修改这个目录
2. 执行make install命令时,写入CONFIG_PREFIX=目录地址
这里我们使用默认的目录即可,执行下面命令安装Busybox
# make install
根目录下多了_install这个目录,目录下多了3个文件夹和一个可执行文件
发现make install命令就是将_install下的命令都链接到busybox下的命令中
例如我们进入bin目录看下:
为了构建完整的根文件系统,我们仍然需要一些文件,配置,所以手动创建6个空文件夹
# mkdir etc dev mnt proc sys tmp
5. 创建启动自动挂载文件fstab (filesystem tab)
fstab是内核在启动时用来挂载文件系统的文件系统表,执行 mount -a 时自动挂载
-a:将 /etc/fstab 中定义的所有文件系统挂上
进入etc目录,创建fstab
# vi fstab
将下面的内容复制进去
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0temps /dev tmpfs defaults 0 0
fstab的格式如下:
①device:要挂接的设备
②mount-point: 挂载点
③type:挂载的设备文件类型,例如proc, jffs2,yaffs,ext2,nfs等,可以用auto,自动检测类型
④options: 挂载参数,《嵌入式Linux应用开发》P353中有详细讲解,defaults是rw,auto等参数的组合,会自动挂载
dump备份文件程序,fsck磁盘检查程序
⑤dump:根据字段确认文件系统是否需要备份,0代表忽略
⑥fsck order: fsck根据这个字段决定检查顺序,一般根文件系统是1,其他是2,0代表忽略这个文件系统
6. 创建etc/init.d/rcS
在Linux系统中,
rcS
(有时也写作rc.S
,具体取决于发行版或配置)是一个非常重要的启动脚本文件,它位于/etc/init.d/
目录下(或者在某些旧的系统或特定的配置中可能位于其他位置)。rcS
文件的主要作用是在系统启动过程的早期阶段配置和初始化系统环境。以下是
rcS
文件的一些主要功能和作用:
设置系统环境:
rcS
脚本通常会设置一些基本的系统环境变量,如PATH
,这些变量对于系统执行程序时查找可执行文件非常重要。挂载文件系统:在系统启动的早期阶段,
rcS
会挂载一些必要的文件系统,这些文件系统对于系统的进一步初始化和运行至关重要。例如,它可能会挂载/proc
、/sys
、/dev
等虚拟文件系统,以及/dev/pts
(用于伪终端)、/dev/shm
(用于共享内存)等特殊的文件系统。设置系统运行级别:在某些系统中,
rcS
还负责设置系统的运行级别(runlevel),这决定了系统启动后将进入哪种操作模式(如单用户模式、多用户模式等)。启动基本服务:
rcS
脚本还可能会启动一些基本的系统服务,如网络服务(netd
)、系统日志服务(syslogd
)等,这些服务对于系统的正常运行和管理至关重要。初始化硬件:在一些嵌入式系统或特殊配置的Linux系统中,
rcS
还可能会包含一些用于初始化硬件设备的命令,如加载硬件驱动程序、配置网络接口等。设置系统时钟:
rcS
脚本还可能会从硬件实时时钟(RTC)读取时间,并将其设置为系统时间,以确保系统时间的准确性。需要注意的是,随着Linux系统的发展,许多现代Linux发行版已经不再使用传统的
init
系统(基于SysVinit
)和rcS
脚本进行系统初始化,而是采用了更先进的初始化系统,如systemd
。在使用systemd
的系统中,系统初始化的过程被大大简化和标准化,许多原本由rcS
脚本承担的任务现在由systemd
的服务单元(service units)来完成。然而,即使在使用
systemd
的系统中,有时仍然可以看到rcS
或类似的脚本文件存在,这可能是因为系统需要兼容旧的应用程序或配置,或者是因为系统管理员出于某种原因选择了继续使用传统的初始化方法。此外,在一些嵌入式系统或特殊用途的Linux发行版中,由于资源限制或其他原因,可能仍然会使用传统的init
系统和rcS
脚本来进行系统初始化。
1. 创建Init.d文件夹
# mkdir init.d
2. 进入init.d文件夹
# cd init.d
3. 用vim打开rcS
# vim rcS
把下面内容拷贝进去,保存退出
#!/bin/sh
echo -e "Welcome to Busybox 1.36.0 filesystem test\n"mount -a
echo -e "mount all filesystem specify in etc/fstab\n"mount -o remount,rw
echo "Remounting the root filesystem\n"mkdir -p /dev/pts
echo "create /dev/pts\n"mount -t devpts devpts /dev/pts
echo "mount devpts to dev/pts directory\n"echo /bin/mdev > /proc/sys/kernel/hotplug
echo "config kernal when hotplug device is invocated\n"mdev -s
echo "generating all file nodes under /dev directory\n"
rcS脚本解析
1. mount -a 挂载所有在etc/fstab中所有列出的目录
2. mount -o remount, rw //以可读可写的方式重新挂载所有文件;用于当系统以只读的形式挂载fstab中的目录,重新挂载可以确保这些目录是以可读可写的形式成功挂载
3. mkdir -p /dev/pts //在dev下创建pts文件夹,用于挂载伪终端设备,例如串口
4. mount -t devpts devpts /dev/pts //将devpts 目录挂载到/dev/pts下
mount -t
命令在Linux系统中用于挂载文件系统时指定文件系统的类型
例如 # 挂载一个ext4类型的分区到/mnt/mydisk目录
mount -t ext4 /dev/sda1 /mnt/mydisk
pts这个文件夹主要用于存放伪终端设备(pseudo-terminals)的特殊文件。在Linux系统中,devpts是devpts文件系统的挂载点,它提供了一种方法来访问和管理伪终端设备。伪终端设备在Unix和Linux系统中经常被用于实现终端仿真,如SSH会话、图形终端仿真器(如xterm、gnome-terminal等)和串行通信等场景。
具体来说,devpts文件系统为系统中的每个伪终端设备提供了一个对应的设备文件,这些文件位于/dev/pts目录下。这些设备文件允许用户程序以文件I/O操作的方式与伪终端设备进行交互,就好像它们是普通的文件一样。这种机制简化了伪终端设备的访问和管理,使得用户可以像操作文件一样来操作这些设备。
与传统的devfs文件系统相比,devpts文件系统提供了更好的安全性和灵活性。它允许系统管理员更精细地控制哪些用户或进程可以访问哪些伪终端设备,从而增强了系统的安全性。同时,devpts文件系统还支持动态创建和销毁伪终端设备文件,这使得系统能够更灵活地应对不断变化的负载和需求。
5. echo /bin/mdev >/proc/sys/kernal/hotplug //设置内核,当有热插拔设备时,调用mdev
6. mdev -s //在dev目录下生成内核支持的所有设备的节点
关于mdev的用法,请参考/busybox.1.36.0/docs/mdev.txt
惊喜的发现,在帮助文档里,给出了示例和我们写的是一样的,并且解释了用途,可跳过
[0] mount -t proc proc /proc
[1] mount -t sysfs sysfs /sys
[2] echo /sbin/mdev > /proc/sys/kernel/hotplug
[3] mdev -s
[4] mount -t tmpfs -o size=64k,mode=0755 tmpfs /dev
[5] mkdir /dev/pts
[6] mount -t devpts devpts /dev/pts
The simple explanation here is that [1] you need to have /sys mounted before
executing mdev. //在使用mdev之前,你需要将/sys成功挂载,fstab里有sysfs,rcS中mount -a就将 sysfs文件挂载到/sys下了Then you [2] instruct the kernel to execute /sbin/mdev whenever
a device is added or removed so that the device node can be created or
destroyed. //第二行,内核使用mdev命令时增加或删除设备时,去用/sbin/mdev,这样设备节点可以增加或删除(说的是热插拔)Then you [3] seed /dev with all the device nodes that were created
while the system was booting. //然后当设备启动时,所有设备节点都将被创建For the "full" setup, you want to [4] make sure /dev is a tmpfs filesystem
(assuming you're running out of flash). //为了完成所有步骤,第四步请确认 /dev已被挂载微tmpfs文件系统Then you want to [5] create the/dev/pts mount point and //创建/dev/pts这个文件夹作为挂载点,挂载伪终端设备
finally [6] mount the devpts filesystem on it. //挂载伪终端文件系统devpts到/dev/pts下
至此rcS启动脚本就结束了
7. 创建inittab启动程序解析文件
example文件夹下有inittab文件可参考,我们仿照这个格式写一个
Note, BusyBox init doesn't support runlevels. The runlevels field is
# completely ignored by BusyBox init. If you want runlevels, use sysvinit.
#
#
# Format for each entry: <id>:<runlevels>:<action>:<process>
# 文件格式<id>:<运行等级>:<动作>:<要执行的程序>
# <id>: WARNING: This field has a non-traditional meaning for BusyBox init!
# 警告:在Busybox init中,id这个字段和常规使用方法不同,主要和systeminit比较,在systeminit里,id是运行级别
# The id field is used by BusyBox init to specify the controlling tty for
# the specified process to run on. The contents of this field are
# appended to "/dev/" and used as-is. There is no need for this field to
# be unique, although if it isn't you may have strange results. If this
# field is left blank, then the init's stdin/out will be used.
#<id>: 控制台,id
字段通常用于指定启动进程的控制终端(tty),这个字段将被附加到./dev下,并原样引用;如果id空白,将使用标准输入输出 stdin/stdout
# <runlevels>: The runlevels field is completely ignored.
#<运行等级>没用到
# <action>: Valid actions include: sysinit, wait, once, respawn, askfirst,
# shutdown, restart and ctrlaltdel.
#<动作> 有好几种,sysinit, wait, once...
# sysinit actions are started first, and init waits for them to complete.
# wait actions are started next, and init waits for them to complete.
# once actions are started next (and not waited for).
# sysinit最先开始,sysinit结束后,init程序才会执行下一条指令wait在sysinit后开始,wait结束后,init程序才会执行下一条指令
once在waithou开始,但执行一次,init程序不会等once执行完毕后,就会执行下一条指令
# askfirst and respawn are started next.
# For askfirst, before running the specified process, init displays
# the line "Please press Enter to activate this console"
# and then waits for the user to press enter before starting it.askfirst和respawn接下来执行,区别是askfirst在执行前,会在终端中输出”请按下回车来激活终端。
shutdown actions are run on halt/reboot/poweroff, or on SIGQUIT.
# Then the machine is halted/rebooted/powered off, or for SIGQUIT,
# restart action is exec'ed (init process is replaced by that process).
# If no restart action specified, SIGQUIT has no effect.
# shutdown 动作,在当系统执行关机操作时执行,或者受到SIGQUIT信号时# restart动作,当执行
# ctrlaltdel actions are run when SIGINT is received
# (this might be initiated by Ctrl-Alt-Del key combination).
# After they complete, normal processing of askfirst / respawn resumes.
进入 etc/init.d目录下,然后创建inittab文件;虽然不创建inittab,也能按照默认配置启动,但是我们还是自己创建1个试试
# cd /etc/init.d
# vi inittab
输入下面的内容:
::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
1. sysinit首先执行的是之前写的rcS脚本
2. 接下来就执行/bin/sh,stdin/stdout标准输入输出作为终端,也可以指定id为串口
3. ctrlaltdel,在串口终端下,按ctrl + alt +delete,可以让系统重启
4. 关机 swapoff -a
5. 关机 umount -a -r 卸载所有目录
6. 重启,重启执行Init
8. 创建设备节点
进入dev目录
# cd dev
创建3个节点:
五. 制作根文件系统镜像
思路:
1. 制作空的镜像文件
2. 把镜像格式化为ext3格式
3. 挂载镜像,然后把根文件系统复制到挂载目录
4. 卸载镜像
5. 打包程gzip包
以上5步用bash脚本实现,用vim 新建1个文件叫makefs.sh
#!/bin/bash
rm -rf rootfs.ext3
rm -rf fs
dd if=/dev/zero of=./rootfs.ext3 bs=1M count=32
mkfs.ext3 rootfs.ext3
mkdir fs
mount -o loop rootfs.ext3 ./fs
cp -rf ./_install/* ./fs
umount ./fs
gzip --best -c rootfs.ext3 > rootfs.img.gz
保存后,输入,不加sudo 会提示只有root用户可以使用mount 和umount 命令
# sudo bash makefs.sh
六. 使用QEMU启动内核和根文件系统
在三.编译内核,我们在 /linux-4.9.229/arch/x86_64/boot/下得到了内核镜像压缩文件bzImage
在五.制作根文件系统镜像,在busybox1.36.0下得到了根文件系统镜像rooffs.img.gzip
我们用QEMU虚拟机启动试试
在linux-4.9.229目录下,执行下面的命令:
qemu-system-x86_64 \
-kernel ./linux-4.9.229/arch/x86_64/boot/bzImage \
-initrd ./busybox-1.36.0/rootfs.img.gz \
-append "root=/dev/ram init=/linuxrc" \
-serial file:output.txt
我们想看下console, null, tty1三个设备的设备号
我并没有按照教程手动在dev下mknod 为这些设备创建设备号,而是在rcS脚本中使用了mdev,为设备自动创建设备号
console 5,1
null, 1,3
tty1, cd 4,1
就这写了我好几天。。。我要缓缓。其实学习busybox的本意是了解根文件系统,然后移植一个根文件系统,例如busybox,ubuntu到泰山派上。看来还得再学学。。。