使用QEMU搭建基于aarch64架构的 U-boot+Linux+NFS开发环境

使用QEMU搭建基于aarch64架构的 U-boot+Linux+NFS开发环境 Hilbert 2023-11-29 09:30:26 2536

使用QEMU搭建基于aarch64架构的 U-boot+Linux+NFS开发环境



QEMU安装前的准备工作

QEMU 安装的两种方式

通过网络在线安装

优点:安装方便
缺点:linux发行版支持在线包的版本都比较低

sudo apt-get install qemu
sudo apt-get install qemu-arm
sudo apt-get install qemu
sudo apt-get install qemu-system
sudo apt-get install qemu-user
源码编译安装

优点:QEMU可以是最新稳定版本,根据自己的需求安装
缺点:编译时需要各种依赖,比较繁琐

源码获取
git clone https://gitlab.com/qemu-project/qemu.git
cd qemu
git submodule init
git submodule update --recursive
QEMU依赖库安装
sudo apt install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build
sudo apt install git-email
sudo apt install libaio-dev libbluetooth-dev libcapstone-dev libbrlapi-dev libbz2-dev
sudo apt install libcap-ng-dev libcurl4-gnutls-dev libgtk-3-dev
sudo apt install libibverbs-dev libjpeg8-dev libncurses5-dev libnuma-dev
sudo apt install librbd-dev librdmacm-dev
sudo apt install libsasl2-dev libsdl2-dev libseccomp-dev libsnappy-dev libssh-dev
sudo apt install libvde-dev libvdeplug-dev libvte-2.91-dev libxen-dev liblzo2-dev
sudo apt install valgrind xfslibs-dev
sudo apt install libslirp-dev

详见官方wiki:
qemu install

编译安装
cd qemu
mkdir build
cd build
../configure --enable-slirp
make -j16
sudo make install

qemu-system-aarch64 --version
QEMU emulator version 8.0.2 (v8.0.2)
Copyright (c) 2003-2022 Fabrice Bellard and the QEMU Project developers
emu-system-aarch64 -M ? help
Supported machines are:
akita                Sharp SL-C1000 (Akita) PDA (PXA270)
ast1030-evb          Aspeed AST1030 MiniBMC (Cortex-M4)
ast2500-evb          Aspeed AST2500 EVB (ARM1176)
ast2600-evb          Aspeed AST2600 EVB (Cortex-A7)
bletchley-bmc        Facebook Bletchley BMC (Cortex-A7)
borzoi               Sharp SL-C3100 (Borzoi) PDA (PXA270)
canon-a1100          Canon PowerShot A1100 IS (ARM946)
cheetah              Palm Tungsten|E aka. Cheetah PDA (OMAP310)
collie               Sharp SL-5500 (Collie) PDA (SA-1110)
connex               Gumstix Connex (PXA255)
cubieboard           cubietech cubieboard (Cortex-A8)
emcraft-sf2          SmartFusion2 SOM kit from Emcraft (M2S010)
fby35-bmc            Facebook fby35 BMC (Cortex-A7)
fby35                Meta Platforms fby35
fp5280g2-bmc         Inspur FP5280G2 BMC (ARM1176)
fuji-bmc             Facebook Fuji BMC (Cortex-A7)
g220a-bmc            Bytedance G220A BMC (ARM1176)
highbank             Calxeda Highbank (ECX-1000)
imx25-pdk            ARM i.MX25 PDK board (ARM926)
integratorcp         ARM Integrator/CP (ARM926EJ-S)
kudo-bmc             Kudo BMC (Cortex-A9)
kzm                  ARM KZM Emulation Baseboard (ARM1136)
lm3s6965evb          Stellaris LM3S6965EVB (Cortex-M3)
lm3s811evb           Stellaris LM3S811EVB (Cortex-M3)
mainstone            Mainstone II (PXA27x)
mcimx6ul-evk         Freescale i.MX6UL Evaluation Kit (Cortex-A7)
mcimx7d-sabre        Freescale i.MX7 DUAL SABRE (Cortex-A7)
microbit             BBC micro:bit (Cortex-M0)
midway               Calxeda Midway (ECX-2000)
mori-bmc             Mori BMC (Cortex-A9)
mps2-an385           ARM MPS2 with AN385 FPGA image for Cortex-M3
mps2-an386           ARM MPS2 with AN386 FPGA image for Cortex-M4
mps2-an500           ARM MPS2 with AN500 FPGA image for Cortex-M7
mps2-an505           ARM MPS2 with AN505 FPGA image for Cortex-M33
mps2-an511           ARM MPS2 with AN511 DesignStart FPGA image for Cortex-M3
mps2-an521           ARM MPS2 with AN521 FPGA image for dual Cortex-M33
mps3-an524           ARM MPS3 with AN524 FPGA image for dual Cortex-M33
mps3-an547           ARM MPS3 with AN547 FPGA image for Cortex-M55
musca-a              ARM Musca-A board (dual Cortex-M33)
musca-b1             ARM Musca-B1 board (dual Cortex-M33)
musicpal             Marvell 88w8618 / MusicPal (ARM926EJ-S)
n800                 Nokia N800 tablet aka. RX-34 (OMAP2420)
n810                 Nokia N810 tablet aka. RX-44 (OMAP2420)
netduino2            Netduino 2 Machine (Cortex-M3)
netduinoplus2        Netduino Plus 2 Machine (Cortex-M4)
none                 empty machine
npcm750-evb          Nuvoton NPCM750 Evaluation Board (Cortex-A9)
nuri                 Samsung NURI board (Exynos4210)
olimex-stm32-h405    Olimex STM32-H405 (Cortex-M4)
orangepi-pc          Orange Pi PC (Cortex-A7)
palmetto-bmc         OpenPOWER Palmetto BMC (ARM926EJ-S)
qcom-dc-scm-v1-bmc   Qualcomm DC-SCM V1 BMC (Cortex A7)
qcom-firework-bmc    Qualcomm DC-SCM V1/Firework BMC (Cortex A7)
quanta-gbs-bmc       Quanta GBS (Cortex-A9)
quanta-gsj           Quanta GSJ (Cortex-A9)
quanta-q71l-bmc      Quanta-Q71l BMC (ARM926EJ-S)
rainier-bmc          IBM Rainier BMC (Cortex-A7)
raspi0               Raspberry Pi Zero (revision 1.2)
raspi1ap             Raspberry Pi A+ (revision 1.1)
raspi2b              Raspberry Pi 2B (revision 1.1)
raspi3ap             Raspberry Pi 3A+ (revision 1.0)
raspi3b              Raspberry Pi 3B (revision 1.2)
realview-eb          ARM RealView Emulation Baseboard (ARM926EJ-S)
realview-eb-mpcore   ARM RealView Emulation Baseboard (ARM11MPCore)
realview-pb-a8       ARM RealView Platform Baseboard for Cortex-A8
realview-pbx-a9      ARM RealView Platform Baseboard Explore for Cortex-A9
romulus-bmc          OpenPOWER Romulus BMC (ARM1176)
sabrelite            Freescale i.MX6 Quad SABRE Lite Board (Cortex-A9)
sbsa-ref             QEMU 'SBSA Reference' ARM Virtual Machine
smdkc210             Samsung SMDKC210 board (Exynos4210)
sonorapass-bmc       OCP SonoraPass BMC (ARM1176)
spitz                Sharp SL-C3000 (Spitz) PDA (PXA270)
stm32vldiscovery     ST STM32VLDISCOVERY (Cortex-M3)
supermicro-x11spi-bmc Supermicro X11 SPI BMC (ARM1176)
supermicrox11-bmc    Supermicro X11 BMC (ARM926EJ-S)
sx1                  Siemens SX1 (OMAP310) V2
sx1-v1               Siemens SX1 (OMAP310) V1
tacoma-bmc           OpenPOWER Tacoma BMC (Cortex-A7)
terrier              Sharp SL-C3200 (Terrier) PDA (PXA270)
tiogapass-bmc        Facebook Tiogapass BMC (ARM1176)
tosa                 Sharp SL-6000 (Tosa) PDA (PXA255)
verdex               Gumstix Verdex Pro XL6P COMs (PXA270)
versatileab          ARM Versatile/AB (ARM926EJ-S)
versatilepb          ARM Versatile/PB (ARM926EJ-S)
vexpress-a15         ARM Versatile Express for Cortex-A15
vexpress-a9          ARM Versatile Express for Cortex-A9
virt-2.10            QEMU 2.10 ARM Virtual Machine
virt-2.11            QEMU 2.11 ARM Virtual Machine
virt-2.12            QEMU 2.12 ARM Virtual Machine
virt-2.6             QEMU 2.6 ARM Virtual Machine
virt-2.7             QEMU 2.7 ARM Virtual Machine
virt-2.8             QEMU 2.8 ARM Virtual Machine
virt-2.9             QEMU 2.9 ARM Virtual Machine
virt-3.0             QEMU 3.0 ARM Virtual Machine
virt-3.1             QEMU 3.1 ARM Virtual Machine
virt-4.0             QEMU 4.0 ARM Virtual Machine
virt-4.1             QEMU 4.1 ARM Virtual Machine
virt-4.2             QEMU 4.2 ARM Virtual Machine
virt-5.0             QEMU 5.0 ARM Virtual Machine
virt-5.1             QEMU 5.1 ARM Virtual Machine
virt-5.2             QEMU 5.2 ARM Virtual Machine
virt-6.0             QEMU 6.0 ARM Virtual Machine
virt-6.1             QEMU 6.1 ARM Virtual Machine
virt-6.2             QEMU 6.2 ARM Virtual Machine
virt-7.0             QEMU 7.0 ARM Virtual Machine
virt-7.1             QEMU 7.1 ARM Virtual Machine
virt-7.2             QEMU 7.2 ARM Virtual Machine
virt                 QEMU 8.0 ARM Virtual Machine (alias of virt-8.0)
virt-8.0             QEMU 8.0 ARM Virtual Machine
witherspoon-bmc      OpenPOWER Witherspoon BMC (ARM1176)
xilinx-zynq-a9       Xilinx Zynq Platform Baseboard for Cortex-A9
xlnx-versal-virt     Xilinx Versal Virtual development board
xlnx-zcu102          Xilinx ZynqMP ZCU102 board with 4xA53s and 2xR5Fs based on the value of smp
yosemitev2-bmc       Facebook YosemiteV2 BMC (ARM1176)
z2                   Zipit Z2 (PXA27x)

命令选项

qemu的标准选项

# qemu的标准选项主要涉及指定主机类型、CPU模式、NUMA、软驱设备、光驱设备及硬件设备等。
-name name              # 虚拟机名称
-M machine              # 指定要模拟的主机类型,如standard PC,ISA-only PC或Intel-Mac等,可以使用“qemu-kvm -M ?”获取所支持的所有类型
-m megs                 # 设定虚拟机的RAM大小
-cpu model              # 设定CPU模型,如coreduo、qemu64等,可以使用"qemu-kvm -cpu ?"获取所支持的所有模型
-smp n                  # 设定模拟的SMP架构中CPU的个数
    [,cores=cores]              # 每个CPU的核心数
    [,threads=threads]  # 线程数
    [,sockets=sockets]  # CPU的socket数目
    [,maxcpus=maxcpus]  # 用于指定热插入的CPU个数上限
-numa   非一致内存访问
-numa opts:指定模拟多节点的numa设备

-fda file:
-fdb file:使用指定文件(file)作为软盘镜像,file为/dev/fd0表示使用物理软驱
-hda file:
-hdb file:
-hdc file:
-hdd file:使用指定file作为硬盘镜像
-cdrom file:使用指定file作为CD-ROM镜像,需要注意的是-cdrom和-hdc不能同时使用:将file指定为/dev/cdrom可以直接使用物理光驱

-drive                                          # 定义一个硬盘设备:可用子选项有很多
    file=/path/to/somefile      # 硬盘映像文件
    if=interface                        # 硬盘设备接口类型 ide、scsi、sd、virtio(半虚拟化)
    index=index                         # 设定同一种控制器类型中不同设备的索引号,即标识号
    media=media                         # 定义介质类型为硬盘还是光盘disk、cdrom
    snapshot=snapshot           # 指定当前硬盘设备是否支持快照功能:on或off
    cache=cache                         # 定义如何使用物理机缓存来访问块数据,其可用值有none、writeback、unsafe和writethrough四个
    format=format                       # 指定映像文件的格式,具体格式可参见qemu-img命令

-boot [order=drives][,once=drives][,menu=on|off]        # 定义启动设备的引导次序,每种设备使用一个字符表示:不同的架构所支持的设备及其表示字符不尽相同,在x86 PC架构上,a、b表示软驱,c表示第一个光驱设备,n-p表示网络适配器,默认为硬盘设备。例如:-boot order=dc,once=d

示例:

qemu-system-x86_64 --name censtos -smp 2 -m 2048 -cpu host -drive file=/data/iso/CentOS-7-x86_64-Minimal-1804.iso,media=cdrom -drive file=centos.qcow2,media=disk -boot order=dc,once=d

qemu显示选项

显示选项用于定义虚拟机启动后的显示接口相关类型及属性等。

SDL 
 -sdl                   # 启用SDL

VNC
 -vnc display [option,option]   # 默认情况下,qemu使用SDL显示VGA输出;使用-vnc选项,可以让qemu监听在vnc上,并将VGA输出重定向至vnc会话,使用此选项时,必须使用-k选项指定键盘布局类型;其中有许多子选项,具体请参考qemu的手册
    display
        1、host:N                                # N为控制台号
            192.168.1.1:1               # 5900为起始端口
        2、unix:/path/to/socket_file                                     # 监听在套接字
        3、none                                  # 不显示
    option
        password                                # 连接时需要验证密码,设定密码通过monitor接口使用change
        reverse                                 # “反向”连接至某处于监听状态的vncview上

-vga type               # 指定要仿真的VGA接口类型,常见的类型有:
        cirrus: Cirrus Logic GD5446显示卡
        std:带有Bochs VBI扩展的标准VGA显示卡
        vmware:VMware SVGA-II兼容的显示适配器
        qxl:QXL半虚拟化显示卡:与VGA兼容,在Guest中安装qxl驱动后能以很好的方式工作,在使用spice协议时推荐使用此类型
        none:禁用VGA卡

-monitor stdio  # 在标准输入输出上显示monitor界面
-nographic              # 默认情况下,qemu使用SDL来显示VGA输出,而此选项用于禁止图形接口,此时,qemu类似一个简单的命令行程序,其仿真串口设备将被重定向到控制台
-curses                 # 禁止图形接口,并使用curses/ncurses作为交互接口
-alt-grab               # 使用Ctrl+Alt+Shift组合键释放鼠标
-ctrl-grab              # 使用右Ctrl键释放鼠标
-spice option[,option[,...]]    # 启用spice远程桌面协议:其中有许多子选项,具体请参照qemu-kvm手册。

网络属性相关选项

网络属性相关选项用于定义网络设备接口类型及其相关的各属性等信息。这里只介绍nic、tap和user三种类型网络接口的属性,其他类型请参考qemu手册9


nic     #定义网络接口
-net nic [,vlan=n,macaddr=n,model=type,name=name,addr=addr,vectors=v]           # 创建一个新的网卡设备并连接至vlan n中:PC架构上默认的NIC为e1000,macaddr用于为其制定mac地址,name用于指定一个在监控时显示的网上设备名称;qemu可以模拟多个类型的网卡设备,如virtio、i82557b、i82559er、ne2k_isa、pcnet、rtl8139、e1000、smc91c111、lance及mcf_fec等;不过,不同平台架构上,其支持的类型可能只包含前述列表中的一部分,可以使用qemu-system-x86_64 -net nic,model=?来获取当前平台支持的类型。
    vlan                # vlan号
    macaddr             # mac地址(mac 默认不变)
    model               # e1000 virtio
    name                # 设备名
    addr                # ip地址

tap     #nic管理虚拟机中的接口,tap就是管理宿主机上的对应接口
-net tap[,vlan=n][,name=name][,fd=h][,ifname=name][,script=file][,downscript=dfile]             # 通过物理机的TAP网络接口连接至vlan n中,使用script=file指定的脚本(默认为/etc/qemu-ifup)来配置当前网络接口,并使用downscript=file指定的脚本(默认为/etc/qemu-ifdown)来撤销接口配置;使用script=no和downscript=no可分别用来禁止执行脚本。

user
-net user[,option][,option][,...]:在用户模式配置网络栈,其不依赖于管理权限;有效选项有:
        vlan=n                  # 连接至vlan n,默认n=0
        name=name               # 指定接口的显示名称,常用于监控模式中
        net=addr[/mask] # 设定GuestOS中可见的IP网络,掩码可选,默认为10.0.2.0/8
        host=addr               # 指定GuestOS中看到的物理机的IP地址,默认为指定网络中的第二个,即x.x.x.2
        dhcpstart=addr  # 指定DHCP服务地址池中16个地址的起始IP,默认为第16个至第31个,即x.x.x.16-x.x.x.31
        dns=addr                # 指定GuestOS可见的dns服务器地址,默认为GuestOS网络中的第3个地址,即x.x.x.3
        tftp=dir                # 激活内置的tftp服务器,并使用指定的dir作为tftp服务器的默认根目录
        bootfile=file   # BOOTP文件名称,用于实现网络引导GuestOS,如:qemu -hda linux.img -boot n -net user,tftp=/tftpserver/pub,bootfile=/pexlinux.0

kvm的网络模型

1、隔离模型
    使用bridge连接各个虚拟机但不关联物理网卡
2、nat模型
    在路由模型上添加nat规则 iptables
3、路由模型
    在隔离模型的基础之上添加一个虚拟网卡,开启路由转发功能。
    需要虚拟机指定虚拟网卡的ip为网关
    需要在要通信的主机或路由添加回复报文的路由条目
4、桥接模型
    在隔离模型的bridge上添加物理网卡
    将物理网卡变为bridge,将原来的IP放到一张虚拟网卡并添加到桥上
dhcp        服务器
namespace   名称空间

Ubuntu 双网卡:qemu桥接+上网配置

添加网卡 NAT模式

添加好网卡后,重洗启动虚拟机,很多朋友使用 ifconfig 命令时可能发现,新添加的网卡无法识别出来,只能识别一个网卡ens33,需要更改配置文件,

sudo vim /etc/netplan/01-network-manager-all.yaml


当前网络信息如图:

# Let NetworkManager manage all devices on this system
network:
    version: 2
    renderer: NetworkManager
    ethernets:
        ens33:
            dhcp4: yes
        ens37:
            dhcp4: no
    bridges:
        br0:
            dhcp4: yes
            interfaces:
                - ens37

sudo netplan apply

查看IP:

起动u-boot

起动

sudo qemu-system-aarch64 \
-machine virt \
-cpu cortex-a57 \
-bios ./u-boot.bin \
-nographic \
-nic tap,model=e1000

报错处理

新建qemu-ifup

#!/bin/bash

switch=$(brctl show | sed -n 2p |awk '{print $1}')
/sbin/ifconfig $1 0.0.0.0 up
/usr/sbin/brctl addif ${switch} $1

新建qemu-ifdown

#!/bin/bash
#This is a qemu-ifdown script for bridging.
#You can use it when starting a KVM guest with bridge mode network.
#Don't use this script in most cases; QEMU will handle it automatically.

switch=$(brctl show| sed -n 2p |awk '{print $1}')
if [ -n "$1" ]; then
        # Delete the specified interfacename
        tunctl -d $1
        #release TAP interface from bridge
        brctl delif ${switch} $1
        #shutdown the TAP interface
        ip link set $1 down
        exit 0
else
        echo "Error: no interface specified"
        exit 1
fi
把qemu-ifup与qemu-ifdown两脚本放入配置文件径
sudo mv qemu-if* /usr/local/bin/../etc/
sudo chmod 777  /usr/local/bin/../etc/qemu-if*
重新起动u-boot

倒计时为0前按任意键

测试网络

在提示符处输入

setenv ipaddr   192.168.218.200   #设置u-boot这边的地址(和br0同一网段即可)
setenv serverip 192.168.218.129   #//设置服务器地址(br0网桥的地址


如上图所示QEMU与主机网络互通

在主机上搭建TFTP

简介

TFTP(Trivial File Transfer Protocol,简单文件传输协议),是一个基于 UDP 协议实现的用于在客户机和服务器之间进行简单文件传输的协议,适合于开销不大、不复杂的应用场合。TFTP协议专门为小文件传输而设计,只能从服务器上获取文件,或者向服务器写入文件,不能列出目录,也不能进行认证。

根据上面关于 TFTP 的介绍,实现TFTP, 搭建一个TFTP 的服务器,ARM开发板当做客户端。
使用虚拟机 Ubuntu来当做服务器,下面我们先讲解一下服务器端的配置。

1、安装依赖包

sudo apt-get install tftp-hpa tftpd-hpa
sudo apt-get install xinetd

2、第二步, 配置/etc/xinetd.conf

vi /etc/xinetd.conf

# Simple configuration file for xinetd
#
#Some defaults, and include /etc/xinetd.d/
defaults
{
#Please note that you need a log_type line to be able to use log_on_success
#and log_on_failure. The default is the following :
#log_type = SYSLOG daemon info
}
includedir /etc/xinetd.d

查看是否有 xinetd.conf 查看内容是否一致, 如果没有创建一个,并输入如下内容

3、第三步,配置/etc/default/tftpd-hpa

sudo vim /etc/default/tftpd-hpa

内容修改成 ,其中工作目录TFTP_DIRECTORY=”/tftpboot” 如果根目录下没有tftpboot,需要自己创建———>这个很关键,不然tfp会启动失败,这个根据自己的路径

#/etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/work/tftpboot/"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-l -c -s"    // 这里是 -L 的小写 -l

4.、第四步,配置/etc/xinetd.d/tftp

vim /etc/xinetd.d/tftp //pathname 不存在时自己创建
打开文件后,对比如下,保持一致

{
        socket_type     = dgram
        protocol                = udp
        wait                    = yes
        user                    = root
        server          = /usr/sbin/in.tftpd
        server_args     = -s /home/ema/tftpboot/ -c
        disable         = no
        per_source      = 11
        cps                     = 100 2
        flags                   = IPv4
}

5、创建文件夹 mkdir /tftpboot

6、给权限 chmod 777 /tftpboot -R

7、启动服务器 sudo service tftpd-hpa start

8、调试服务器 在/tftpboot  下 touch test.txt

9、核心板 使用tftp下载文件

在主机上搭建NFS

NFS(Network File System)是一种允许不同计算机之间共享文件的网络文件系统。

安装NFS服务器

sudo apt update
sudo apt install nfs-kernel-server

创建共享目录

mkdir /work/nfsroot  -p
chmod 777 /work/nfsroot -R

这将创建一个名为“shared”的目录,并将其权限设置为777,这样所有用户都可以访问它

配置NFS服务器

配置NFS服务器。在Ubuntu 22.04 LTS中,NFS服务器的配置文件位于“/etc/exports”文件中。您可以使用以下命令打开该文件进行编辑:

vim /etc/exports

在该文件中,添加要共享的目录及其权限设置。例如,要将“/work/nfsroot”目录共享给本地网络中的所有计算机,可以添加以下行:

/work/nfsroot  *(rw, sync, no_subtree_check)

在此示例中,“*”表示该目录将共享给本地网络中的所有计算机,“rw”表示该目录可读写,“sync”表示写操作将同步到磁盘上,“no_subtree_check”表示不进行子目录检查。保存并关闭该文件。

启动NFS服务器

配置NFS服务器后,您需要启动NFS服务器以开始共享目录。您可以使用以下命令启动NFS服务器:

sudo systemctl start nfs-kernel-server

如果您想要NFS服务器在系统启动时自动启动,则可以使用以下命令启用该服务:

sudo systemctl enable nfs-kernel-server

测试NFS服务器

可以使用其他计算机上的NFS客户端来测试您的NFS服务器是否正常工作。在NFS客户端上,您可以使用以下命令挂载NFS共享:

sudo mount server_ip:/shared /mnt

在该命令中,server_ip是您的NFS服务器的IP地址,/shared是您要共享的目录,/mnt是您要将共享目录挂载到的本地目录。如果一切正常,您应该能够访问共享目录并进行读写操作。

也可通过如下命令查看配置:

# sudo showmount -e
Export list for ubuntu22:
/work/nfsroot *

编译U-Boot 2023.07-rc3

ARCH = arm

ARCH = arm64

编译内核

设备树

设备树位置

制作根文件系统

根文件系统简介

根文件系统一般也叫做 rootfs,那么什么叫根文件系统?看到“文件系统”这四个字,很多人,包括我第一反应就是 FATFS、 FAT、 EXT4、 YAFFS 和 NTFS 等这样的文件系统。在这里,根文件系统并不是 FATFS 这样的文件系统代码, EXT4 这样的文件系统代码属于 Linux 内核的一部分。Linux 中的根文件系统更像是一个文件夹或者叫做目录(在我看来就是一个文件夹,只不过是特殊的文件夹),在这个目录里面会有很多的子目录。

根目录下和子目录中会有很多的文件,这些文件是 Linux 运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。以后我们说到文件系统,如果不特别指明,统一表示根文件系统。对于根文件系统专业的解释,百度百科上是这么说的。

根文件系统首先是内核启动时所 mount(挂载)的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。

百度百科上说内核代码镜像文件保存在根文件系统中,但是我们嵌入式 Linux 并没有将内核代码镜像保存在根文件系统中,而是保存到了其他地方。比如 NAND Flash 的指定存储地址、EMMC 专用分区中。根文件系统是 Linux 内核启动以后挂载(mount)的第一个文件系统,然后从根文件系统中读取初始化脚本,比如 rcS, inittab 等。根文件系统和 Linux 内核是分开的,单独的 Linux 内核是没法正常工作的,必须要搭配根文件系统。如果不提供根文件系统, Linux 内核在启动的时候就会提示内核崩溃(Kernel panic)的提示。

根文件系统的这个“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根”,其他的文件系统或者软件就别想工作。比如我们常用的 ls、 mv、 ifconfig 等命令其实就是一个个小软件,只是这些软件没有图形界面,而且需要输入命令来运行。这些小软件就保存在根文件系统中,这些小软件是怎么来的呢?这个就是我们本章教程的目的,教大家来构建自己的根文件系统,这个根文件系统是满足 Linux 运行的最小根文件系统,后续我们可以根据自己的实际工作需求不断的去填充这个最小根文件系统,最终使其成为一个相对完善的根文件系统。

根文件系统的选择和配置对于操作系统的性能、功能和可靠性都具有重要影响。不同的应用场景和需求可能需要定制化的根文件系统,以满足特定的要求。因此,根文件系统的设计和管理是操作系统开发和系统管理的重要方面之一。

根文件系统构建软件

构建根文件系统常用的软件有三种,分别是busybox,buildroot,yocto。busybox 仅仅只是帮我们构建好了一些常用的命令和文件,像 lib 库、/etc 目录下的一些文件都需要我们自己手动创建,而且 busybox 构建的根文件系统默认没有用户名和密码设置。在后续的实验中,我们还要自己去移植一些第三方软件和库,比如 alsa、iperf、mplayer 等等。那么有没有一种傻瓜式的方法或软件,它不仅包含了 busybox 的功能,而且里面还集成了各种软件,需要什么软件就选择什么软件,不需要我们去移植。

答案肯定是有的,buildroot 就是这样一种工具,buildroot比 busybox 更上一层楼,buildroot 不仅集成了 busybox,而且还集成了各种常见的第三方库和软件,需要什么就选择什么,就跟我们去吃自助餐一样,想吃什么就拿什么。buildroot 极大的方便了我们嵌入式 Linux 开发人员构建实用的根文件系统。

从 busybox 开始一步一步的构建根文件系统适合学习、了解根文件系统的组成,但是不适合做产品(主要是自己构建的话会有很多不完善、没有注意到的细节)。buildroot 会帮我们处理好各种细节,根文件系统也会更加的合理、有效。因此在做产品的时候推荐大家使用 buildroot 来构建自己的根文件系统,当然了,类似 buildroot 的软件还有很多,比如 yocto。

buildroot 和 uboot、Linux Kernel 很类似,我们需要到其官网上下载源码,然后对其进行配置,比如设置交叉编译器、设置目标 CPU 参数等,最主要的就是选择所需要的第三方库或软件。一切配置好以后就可以进行编译,编译完成了以后就会在一个文件夹里面存放好编译结果,也就是根文件系统。

busybox构建根文件系统

BusyBox 简介

其名字分为BusyBox,也就是忙碌的盒子。盒子是用来放东西的,忙碌的是因为它要提供根文件系统所需的文件,所以忙碌。BusyBox 是一个集成了大量的 Linux 命令和工具的软件,像 ls、 mv、 ifconfig 等命令 BusyBox 都会提供。BusyBox 就是一个大的工具箱,这个工具箱里面集成了 Linux 的许多工具和命令。一般下载 BusyBox 的源码,然后配置 BusyBox,选择自己想要的功能,最后编译即可。

busybox官网

登录官网可以看到,界面非常的简洁

在官网左侧的“Get BusyBox”栏有一行“Download Source”,点击“Download Source”即可打开 BusyBox 的下载页,如下所示:

编译 BusyBox 构建根文件系统

busybox的编译构建过程:

  1. 使用make menuconfig构建出图形配置界面。
  2. 通过配置图形配置界面的选项配置busybox的安装路径、编译工具、命令功能使能等。生成.config配置文件
  3. 使用make编译busybox
  4. 使用make install命令安装由busybox生成的根文件系统
  5. 完善根文件系统
  6. 使用和测试根文件系统

1、创建文件夹保存根文件系统

mkdir rootfs

2、解压BusyBox压缩包到rootfs目录

tar -jxvf busybox-1.36.1.tar.bz2

3、修改 Makefile,添加编译器

# Cross compiling and selecting different set of gcc/bin-utils
# ---------------------------------------------------------------------------
#
# When performing cross compilation for other architectures ARCH shall be set
# to the target architecture. (See arch/* for the possibilities).
# ARCH can be set during invocation of make:
# make ARCH=ia64
# Another way is to have ARCH set in the environment.
# The default ARCH is the host where make is executed.

# CROSS_COMPILE specify the prefix used for all executables used
# during compilation. Only gcc and related bin-utils executables
# are prefixed with $(CROSS_COMPILE).
# CROSS_COMPILE can be set on the command line
# make CROSS_COMPILE=ia64-linux-
# Alternatively CROSS_COMPILE can be set in the environment.
# Default value for CROSS_COMPILE is not to prefix executables
# Note: Some architectures assign CROSS_COMPILE in their arch/*/Makefile

CROSS_COMPILE ?= arm-linux-gnueabihf-
# bbox: we may have CONFIG_CROSS_COMPILER_PREFIX in .config,
# and it has not been included yet... thus using an awkward syntax.
ifeq ($(CROSS_COMPILE),)
CROSS_COMPILE := $(shell grep ^CONFIG_CROSS_COMPILER_PREFIX .config 2>/dev/null)
CROSS_COMPILE := $(subst CONFIG_CROSS_COMPILER_PREFIX=,,$(CROSS_COMPILE))
CROSS_COMPILE := $(subst ",,$(CROSS_COMPILE))
#")
endif

# SUBARCH tells the usermode build what the underlying arch is.  That is set
# first, and if a usermode build is happening, the "ARCH=um" on the command
# line overrides the setting of ARCH below.  If a native build is happening,
# then ARCH is assigned, getting whatever value it gets normally, and
# SUBARCH is subsequently ignored.

ifneq ($(CROSS_COMPILE),)
SUBARCH := $(shell echo $(CROSS_COMPILE) | cut -d- -f1 | sed 's:^.*/::g')
else
SUBARCH := $(shell uname -m)
endif
SUBARCH := $(shell echo $(SUBARCH) | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
                     -e s/arm.*/arm/ -e s/sa110/arm/ \
                     -e s/s390x/s390/ -e s/parisc64/parisc/ \
                     -e s/ppc.*/powerpc/ -e s/mips.*/mips/ )

ARCH ?= arm

修改CROSS_COMPILE ?=ARCH ?= arm 这两行内容,修改如上代码。

busybox中文字符支持

使用 busybox 默认配置直接编译的话,使用 SecureCRT 的时候中文字符是显示不正常的,中文字符会显示为“?”,比如你的中文目录,中文文件都显示为“?”。这是因为 busybox 中的 shell 命令对中文输入即显示做了限制,所以我们需要对 busybox 源码进行修改,取消 busybox 对中文显示的限制,下面我们来一步步修改busybox使其支持中文。

修改printable_string2函数

打开文件 busybox-1.36.1/libbb/printable_string.c,找到函数 printable_string2,函数具体内容如下:

const char* FAST_FUNC printable_string2(uni_stat_t *stats, const char *str)
{
    char *dst;
    const char *s;

    s = str;
    while (1) {
        unsigned char c = *s;
        if (c == '\0') {
            /* 99+% of inputs do not need conversion */
            if (stats) {
                stats->byte_count = (s - str);
                stats->unicode_count = (s - str);
                stats->unicode_width = (s - str);
            }
            return str;
        }
        if (c < ' ')
            break;
        if (c >= 0x7f)
            break;
        s++;
    }

#if ENABLE_UNICODE_SUPPORT
    dst = unicode_conv_to_printable(stats, str);
#else
    {
        char *d = dst = xstrdup(str);
        while (1) {
            unsigned char c = *d;
            if (c == '\0')
                break;
            if (c < ' ' || c >= 0x7f)
                *d = '?';
            d++;
        }
        if (stats) {
            stats->byte_count = (d - dst);
            stats->unicode_count = (d - dst);
            stats->unicode_width = (d - dst);
        }
    }
#endif
    return auto_string(dst);
}

从上面代码20和21行以及34和35行可以看出:大于0x7F的字符直接被break掉,或者直接被“?”代替了。所以就算是linux内核设置了支持中文,也是无法显示出来的,被“?”代替了。修改后的代码如下:

    const char* FAST_FUNC printable_string2(uni_stat_t *stats, const char *str)
    {
        char *dst;
        const char *s;

        s = str;
        while (1) {
            unsigned char c = *s;
            if (c == '\0') {
                /* 99+% of inputs do not need conversion */
                if (stats) {
                    stats->byte_count = (s - str);
                    stats->unicode_count = (s - str);
                    stats->unicode_width = (s - str);
                }
                return str;
            }
            if (c < ' ')
                break;
            /* 注释掉下面这个两行代码 */
    /*
            if (c >= 0x7f)
                break;
    */
            s++;
        }

    #if ENABLE_UNICODE_SUPPORT
        dst = unicode_conv_to_printable(stats, str);
    #else
        {
            char *d = dst = xstrdup(str);
            while (1) {
                unsigned char c = *d;
                if (c == '\0')
                    break;
                /* 修改下面代码 */    
                /* if (c < ' ' || c >= 0x7f) */
                if( c < ' ')
                    *d = '?';
                d++;
            }
            if (stats) {
                stats->byte_count = (d - dst);
                stats->unicode_count = (d - dst);
                stats->unicode_width = (d - dst);
            }
        }
    #endif
        return auto_string(dst);
    }
修改unicode_conv_to_printable2函数

打开文件 busybox-1.36.1/libbb/unicode.c,找到函数 unicode_conv_to_printable2,函数具体内容如下:

    static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t *stats, const char *src, unsigned width, int flags)
    {
        char *dst;
        unsigned dst_len;
        unsigned uni_count;
        unsigned uni_width;

        if (unicode_status != UNICODE_ON) {
            char *d;
            if (flags & UNI_FLAG_PAD) {
                d = dst = xmalloc(width + 1);
                while ((int)--width >= 0) {
                    unsigned char c = *src;
                    if (c == '\0') {
                        do
                            *d++ = ' ';
                        while ((int)--width >= 0);
                        break;
                    }
                    *d++ = (c >= ' ' && c < 0x7f) ? c : '?';
                    src++;
                }
                *d = '\0';
            } else {
                d = dst = xstrndup(src, width);
                while (*d) {
                    unsigned char c = *d;
                    if (c < ' ' || c >= 0x7f)
                        *d = '?';
                    d++;
                }
            }
            if (stats) {
                stats->byte_count = (d - dst);
                stats->unicode_count = (d - dst);
                stats->unicode_width = (d - dst);
            }
            return dst;
        }

        dst = NULL;
        uni_count = uni_width = 0;
        dst_len = 0;
        while (1) {
            int w;
            wchar_t wc;

    #if ENABLE_UNICODE_USING_LOCALE
            {
                mbstate_t mbst = { 0 };
                ssize_t rc = mbsrtowcs(&wc, &src, 1, &mbst);
                /* If invalid sequence is seen: -1 is returned,
                 * src points to the invalid sequence, errno = EILSEQ.
                 * Else number of wchars (excluding terminating L'\0')
                 * written to dest is returned.
                 * If len (here: 1) non-L'\0' wchars stored at dest,
                 * src points to the next char to be converted.
                 * If string is completely converted: src = NULL.
                 */
                if (rc == 0) /* end-of-string */
                    break;
                if (rc < 0) { /* error */
                    src++;
                    goto subst;
                }
                if (!iswprint(wc))
                    goto subst;
            }
    #else
            src = mbstowc_internal(&wc, src);
            /* src is advanced to next mb char
             * wc == ERROR_WCHAR: invalid sequence is seen
             * else: wc is set
             */
            if (wc == ERROR_WCHAR) /* error */
                goto subst;
            if (wc == 0) /* end-of-string */
                break;
    #endif
            if (CONFIG_LAST_SUPPORTED_WCHAR && wc > CONFIG_LAST_SUPPORTED_WCHAR)
                goto subst;
            w = wcwidth(wc);
            if ((ENABLE_UNICODE_COMBINING_WCHARS && w < 0) /* non-printable wchar */
             || (!ENABLE_UNICODE_COMBINING_WCHARS && w <= 0)
             || (!ENABLE_UNICODE_WIDE_WCHARS && w > 1)
            ) {
     subst:
                wc = CONFIG_SUBST_WCHAR;
                w = 1;
            }
            width -= w;
            /* Note: if width == 0, we still may add more chars,
             * they may be zero-width or combining ones */
            if ((int)width < 0) {
                /* can't add this wc, string would become longer than width */
                width += w;
                break;
            }

            uni_count++;
            uni_width += w;
            dst = xrealloc(dst, dst_len + MB_CUR_MAX);
     #if ENABLE_UNICODE_USING_LOCALE
            {
                mbstate_t mbst = { 0 };
                dst_len += wcrtomb(&dst[dst_len], wc, &mbst);
            }
     #else
            dst_len += wcrtomb_internal(&dst[dst_len], wc);
     #endif
        }

        /* Pad to remaining width */
        if (flags & UNI_FLAG_PAD) {
            dst = xrealloc(dst, dst_len + width + 1);
            uni_count += width;
            uni_width += width;
            while ((int)--width >= 0) {
                dst[dst_len++] = ' ';
            }
        }
        if (!dst) /* for example, if input was "" */
            dst = xzalloc(1);
        dst[dst_len] = '\0';
        if (stats) {
            stats->byte_count = dst_len;
            stats->unicode_count = uni_count;
            stats->unicode_width = uni_width;
        }

        return dst;
    }

从代码中可以看出,第 20行,当字符小于空格或者字符大于 0X7F, *d++就为‘?’。第 28 和 29 行,也是一样,当字符小于空格或者字符大于 0X7F, *d也为‘?’。修改后的代码如下:

    static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t *stats, const char *src, unsigned width, int flags)
    {
        char *dst;
        unsigned dst_len;
        unsigned uni_count;
        unsigned uni_width;

        if (unicode_status != UNICODE_ON) {
            char *d;
            if (flags & UNI_FLAG_PAD) {
                d = dst = xmalloc(width + 1);
                while ((int)--width >= 0) {
                    unsigned char c = *src;
                    if (c == '\0') {
                        do
                            *d++ = ' ';
                        while ((int)--width >= 0);
                        break;
                    }
                    /* 修改下面一行代码 */
                  /* *d++ = (c >= ' ' && c < 0x7f) ? c : '?'; */
                  *d++ = (c >= ' ') ? c : '?';
                    src++;
                }
                *d = '\0';
            } else {
                d = dst = xstrndup(src, width);
                while (*d) {
                    unsigned char c = *d;
                      /* 修改下面一行代码 */
                    /* if (c < ' ' || c >= 0x7f) */
                    if (c < ' ')
                        *d = '?';
                    d++;
                }
            }
            if (stats) {
                stats->byte_count = (d - dst);
                stats->unicode_count = (d - dst);
                stats->unicode_width = (d - dst);
            }
            return dst;
        }

        dst = NULL;
        uni_count = uni_width = 0;
        dst_len = 0;
        while (1) {
            int w;
            wchar_t wc;

     #if ENABLE_UNICODE_USING_LOCALE
            {
                mbstate_t mbst = { 0 };
                ssize_t rc = mbsrtowcs(&wc, &src, 1, &mbst);
                /* If invalid sequence is seen: -1 is returned,
                 * src points to the invalid sequence, errno = EILSEQ.
                 * Else number of wchars (excluding terminating L'\0')
                 * written to dest is returned.
                 * If len (here: 1) non-L'\0' wchars stored at dest,
                 * src points to the next char to be converted.
                 * If string is completely converted: src = NULL.
                 */
                if (rc == 0) /* end-of-string */
                    break;
                if (rc < 0) { /* error */
                    src++;
                    goto subst;
                }
                if (!iswprint(wc))
                    goto subst;
            }
     #else
            src = mbstowc_internal(&wc, src);
            /* src is advanced to next mb char
             * wc == ERROR_WCHAR: invalid sequence is seen
             * else: wc is set
             */
            if (wc == ERROR_WCHAR) /* error */
                goto subst;
            if (wc == 0) /* end-of-string */
                break;
     #endif
            if (CONFIG_LAST_SUPPORTED_WCHAR && wc > CONFIG_LAST_SUPPORTED_WCHAR)
                goto subst;
            w = wcwidth(wc);
            if ((ENABLE_UNICODE_COMBINING_WCHARS && w < 0) /* non-printable wchar */
             || (!ENABLE_UNICODE_COMBINING_WCHARS && w <= 0)
             || (!ENABLE_UNICODE_WIDE_WCHARS && w > 1)
            ) {
     subst:
                wc = CONFIG_SUBST_WCHAR;
                w = 1;
            }
            width -= w;
            /* Note: if width == 0, we still may add more chars,
             * they may be zero-width or combining ones */
            if ((int)width < 0) {
                /* can't add this wc, string would become longer than width */
                width += w;
                break;
            }

            uni_count++;
            uni_width += w;
            dst = xrealloc(dst, dst_len + MB_CUR_MAX);
     #if ENABLE_UNICODE_USING_LOCALE
            {
                mbstate_t mbst = { 0 };
                dst_len += wcrtomb(&dst[dst_len], wc, &mbst);
            }
     #else
            dst_len += wcrtomb_internal(&dst[dst_len], wc);
     #endif
        }

        /* Pad to remaining width */
        if (flags & UNI_FLAG_PAD) {
            dst = xrealloc(dst, dst_len + width + 1);
            uni_count += width;
            uni_width += width;
            while ((int)--width >= 0) {
                dst[dst_len++] = ' ';
            }
        }
        if (!dst) /* for example, if input was "" */
            dst = xzalloc(1);
        dst[dst_len] = '\0';
        if (stats) {
            stats->byte_count = dst_len;
            stats->unicode_count = uni_count;
            stats->unicode_width = uni_width;
        }

        return dst;
    }

busybox 中文字符支持跟代码修改有关的就改好了,但是最后还需要配置 busybox来使能 unicode 码,这样才能显示中文字符。

配置 busybox

编译 busybox的步骤跟编译 uboot和Linux kernel是一样的,首先都是进行默认配置,然后再进行编译。选择默认配置来配置 busybox 就行了:

make defconfig

busybox 也支持图形化配置,进入图形配置界面进行如下配置:

配置路径如下:

Location:    
    -> Settings        
        -> Build static binary (no shared libs)

选项“Build static binary (no shared libs)”用来决定是静态编译 busybox 还是动态编译,静态编译的话就不需要库文件,但是编译出来的库会很大。动态编译的话要求根文件系统中有库文件,但是编译出来的 busybox 会小很多。这里我们不能采用静态编译!因为采用静态编译的话 DNS 会出问题!无法进行域名解析,配置如下所示:

继续配置如下路径配置项:

Location:    
    -> Settings       
        -> vi-style line editing commands

如下图所示:

继续配置如下路径配置项:

Location:
    -> Linux Module Utilities       
            -> Simplified modutils

默认会选中“Simplified modutils”,这里我们要取消勾选!!如下图所示:

继续配置如下路径配置项:

Location:   
    -> Linux System Utilities        
            -> mdev (17 kb) //确保下面的全部选中,默认都是选中的

如下图所示:

最后就是使能 busybox 的 unicode 编码以支持中文,配置路径如下:

Location:    
    -> Settings        
        -> Support Unicode //选中            
                -> Check $LC_ALL, $LC_CTYPE and $LANG environment variables //选中

如下图所示:

至此,busybox 的默认配置就完成了,也可以根据自己的实际需求选择配置其他的选项,不过对于初学者笔者不建议再做其他的修改,可能会出现编译出错的情况发生。接下来就是对busybox进行编译。

编译 busybox

首先是指定编译结果的存放目录,当前是将编译结果存放到前面创建的 rootfs 目录中,输入如下命令:

make -j32

make install CONFIG_PREFIX=/work/rootfs

COFIG_PREFIX 指 定 编 译 结 果 的 存 放 目 录 , 比 如 我 存 放 到“/work/rootfs”目录中,等待编译完成。编译完成以后下图所示:

  /work/rootfs//usr/sbin/sendmail -> ../../bin/busybox
  /work/rootfs//usr/sbin/setfont -> ../../bin/busybox
  /work/rootfs//usr/sbin/setlogcons -> ../../bin/busybox
  /work/rootfs//usr/sbin/svlogd -> ../../bin/busybox
  /work/rootfs//usr/sbin/telnetd -> ../../bin/busybox
  /work/rootfs//usr/sbin/tftpd -> ../../bin/busybox
  /work/rootfs//usr/sbin/ubiattach -> ../../bin/busybox
  /work/rootfs//usr/sbin/ubidetach -> ../../bin/busybox
  /work/rootfs//usr/sbin/ubimkvol -> ../../bin/busybox
  /work/rootfs//usr/sbin/ubirename -> ../../bin/busybox
  /work/rootfs//usr/sbin/ubirmvol -> ../../bin/busybox
  /work/rootfs//usr/sbin/ubirsvol -> ../../bin/busybox
  /work/rootfs//usr/sbin/ubiupdatevol -> ../../bin/busybox
  /work/rootfs//usr/sbin/udhcpd -> ../../bin/busybox

--------------------------------------------------
You will probably need to make your busybox binary
setuid root to ensure all configured applets will
work properly.
--------------------------------------------------

编译完成以后会在 busybox 的所有工具和文件就会被安装到 rootfs 目录中, rootfs 目录内容如下所示:

original@ubuntu22:/work/rootfs$ ls 
bin  linuxrc  sbin  usr

从上面可以看出, rootfs 目录下有 bin、 sbin 和 usr 这三个目录,以及 linuxrc 这个文件。Linux 内核 init 进程最后会查找用户空间的 init 程序,找到以后就会运行这个用户空间的 init 程序,从而切换到用户态。如果 bootargs 设置 init=/linuxrc,那么 linuxrc 就是可以作为用户空间的 init 程序,所以用户态空间的 init 程序是 busybox 来生成的。

busybox 的工作就完成了,但是此时的根文件系统还不能使用,还需要一些其他的文件,继续来完善 rootfs。

根文件系统添加lib库
rootfs文件系统的“/lib”目录添加库文件

Linux 中的应用程序一般都是需要动态库的,当然你也可以编译成静态的,但是静态的可执行文件会很大。如果编译为动态的话就需要动态库,所以我们需要先根文件系统中添加动态库。

在 rootfs 中创建一个名为“lib”的文件夹,命令如下:

mkdir lib

lib 文件夹创建好了,库文件从哪里来呢?lib 库文件从交叉编译器中获取, 前面我们搭建交叉编译环境的时候将交叉编译器存放到了“/usr/local/arm/”目录中。

交叉编译器里面有很多的库文件,这些库文件具体是做什么的我们肯定并不清楚也先不管,把所有的库文件都放到我们的根文件系统中。这样做出来的根文件系统肯定很大,但处于学习阶段,就只管怎么讲rootfs移植成功就行。

进入如下路径对应的目录:

ls /usr/aarch64-linux-gnu/lib/
crt1.o                 libatomic.so.1.2.0      libg.a              libmemusage.so      libpthread.so.0      libubsan.so.1
crti.o                 libBrokenLocale.a       libgcc_s.so.1       libm.so             libresolv.a          libubsan.so.1.0.0
crtn.o                 libBrokenLocale.so      libgomp.so.1        libm.so.6           libresolv.so         libutil.a
gcrt1.o                libBrokenLocale.so.1    libgomp.so.1.0.0    libnsl.so.1         libresolv.so.2       libutil.so.1
grcrt1.o               libc.a                  libhwasan.so.0      libnss_compat.so    librt.a              Mcrt1.o
ld-linux-aarch64.so.1  libc_malloc_debug.so    libhwasan.so.0.0.0  libnss_compat.so.2  librt.so.1           rcrt1.o
libanl.a               libc_malloc_debug.so.0  libitm.so.1         libnss_dns.so.2     libstdc++.so.6       Scrt1.o
libanl.so              libc_nonshared.a        libitm.so.1.0.0     libnss_files.so.2   libstdc++.so.6.0.30
libanl.so.1            libc.so                 liblsan.so.0        libnss_hesiod.so    libthread_db.so
libasan.so.6           libc.so.6               liblsan.so.0.0.0    libnss_hesiod.so.2  libthread_db.so.1
libasan.so.6.0.0       libdl.a                 libm.a              libpcprofile.so     libtsan.so.0
libatomic.so.1         libdl.so.2              libmcheck.a         libpthread.a        libtsan.so.0.0.0

从上面可以看到,有很多的.so和.a的文件,这些就是库文件,将此目录下所有的.so和.a文件都拷贝到 rootfs/lib 目录中, 拷贝命令如下:

cp *so* *.a /work/rootfs/lib -rdf

-d:表示拷贝符号链接,这里有个比较特殊的库文件:ld-linux-armhf.so.3,此库文件也是个符号链接,相当于 Windows 下的快捷方式。会链接到库 ld-2.25.so 上,输入命令“ls libubsan.so.1 -l”查看此文件详细信息,如下所示:

ls libubsan.so.1 -l
lrwxrwxrwx 1 original original 17  6月 15 16:55 libubsan.so.1 -> libubsan.so.1.0.0
 mkdir -p dev   mnt   proc  root  sbin  sys  tmp

到这里,根文件系统差不多已经准备好了,但是能不能挂载成功并使用,下面就来上电测一下就知道了!

根文件系统挂载方式

根文件系统挂载有三种方式:

  • NFS挂载rootfs
  • SD卡挂载rootfs
  • eMMC挂载rootfs

由于为了模拟实际工作的情况下,在开始移植linux系统到开发板之前,就将eMMC中的分区信息和全部的系统信息通过u-boot下的mmc指令将其全部擦除了。因此本次只能将系统烧写到sd卡中,然后从sd卡启动系统,并且挂载sd卡中的根文件系统了。
下面将会对三种方式挂载根文件系统的方法进行介绍。

NFS挂载rootfs

使用 NFS 挂载rootfs,就是在uboot 里面的 bootargs 环境变量会设置“root”的值,所以我们将 root 的值改为 NFS 挂载即可。
在 Linux 内核源码里面有相应的文档讲解如何设置,文档为 Documentation/filesystems/nfs/nfsroot.txt,格式如下:

root=/dev/nfs nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] ip=<client-ip>:<server-ip>:<gwip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>

各参数含义如下:

  • [server-ip]:服务器 IP 地址,也就是存放根文件系统主机的 IP 地址,那就是 Ubuntu 的 IP地址,比如我的 Ubuntu 主机 IP 地址为 192.168.1.250
  • [root-dir]:根文件系统的存放路径,比如我的就是/home/toto/workspace/rootfs/rootfs/
  • [nfs-options]:NFS 的其他可选选项,一般不设置
  • [client-ip]:客户端 IP 地址,也就是我们开发板的 IP 地址, Linux 内核启动以后就会使用此 IP 地址来配置开发板。此地址一定要和 Ubuntu 主机在同一个网段内,并且没有被其他的设备使用,在 Ubuntu 中使用 ping 命令 ping 一下就知道要设置的 IP 地址有没有被使用,如果不能ping 通就说明没有被使用,那么就可以设置为开发板的 IP 地址,比如我就可以设置为192.168.1.251
  • [server-ip]:服务器 IP 地址,前面已经说了
  • [gw-ip]:网关地址,我的就是 192.168.1.1
  • [netmask]:子网掩码,我的就是 255.255.255.0
  • [hostname]:客户机的名字,一般不设置,此值可以空着
  • [device]:设备名,也就是网卡名,一般是 eth0, eth1….,正点原子的 I.MX6U-ALPHA 开发板的 ENET2 为 eth0, ENET1 为 eth1。如果你的电脑只有一个网卡,那么基本只能是 eth0。这里我们使用 ENET2,所以网卡名就是 eth0
  • [autoconf]:自动配置,一般不使用,所以设置为 off
  • [dns0-ip]:DNS0 服务器 IP 地址,不使用
  • [dns1-ip]:DNS1 服务器 IP 地址,不使用

根据上面的格式 bootargs 环境变量的 root 值如下:

root=/dev/nfs nfsroot=192.168.1.43:/home/toto/workspace/rootfs/rootfs,proto=tcp rwip=192.168.1.100:192.168.1.43:192.168.1.1:255.255.255.0::eth0:off

proto=tcp 表示使用 TCP 协议,rw 表示 nfs 挂载的根文件系统为可读可写。
启动开发板,进入 uboot 命令行模式,然后重新设置bootargs 环境变量,命令如下:

setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.250:/home/toto/workspace/rootfs/rootfs,proto=tcp rw ip=192.168.1.100:192.168.1.43:192.168.1.1:255.255.255.0::eth0:off' //设置 bootargssaveenv //保存环境变量

设置好以后使用“boot”命令启动 Linux 内核,测试根文件系统是否挂载成功了。

SD卡挂载rootfs
完善根文件系统

创建/etc/init.d/rcS文件

rcS 是个 shell 脚本, Linux 内核启动以后需要启动一些服务,而 rcS 就是规定启动哪些文件的脚本文件。在 rootfs 中创建/etc/init.d/rcS 文件,然后在 rcS 中输入如下所示内容:

#!/bin/sh PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATHLD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/libexport PATH LD_LIBRARY_PATH        mount -amkdir /dev/ptsmount -t devpts devpts /dev/pts echo /sbin/mdev > /proc/sys/kernel/hotplugmdev -s
  • 第 1 行,表示这是一个 shell 脚本。
  • 第 3 行, PATH 环境变量保存着可执行文件可能存在的目录,这样我们在执行一些命令或者可执行文件的时候就不会提示找不到文件这样的错误。
  • 第 4 行, LD_LIBRARY_PATH 环境变量保存着库文件所在的目录。
  • 第 5 行,使用 export 来导出上面这些环境变量,相当于声明一些“全局变量”。
  • 第 7 行,使用 mount 命令来挂载所有的文件系统,这些文件系统由文件/etc/fstab 来指定,所以我们一会还要创建/etc/fstab 文件。
  • 第 8 和 9 行,创建目录/dev/pts,然后将 devpts 挂载到/dev/pts 目录中。
  • 第 11 和 12 行,使用 mdev 来管理热插拔设备,通过这两行, Linux 内核就可以在/dev 目录下自动创建设备节点。关于 mdev 的详细内容可以参考 busybox 中的 docs/mdev.txt 文档。

上面示例 rcS 文件内容是最精简的,如果对 Ubuntu 或者其他大型 Linux操作系统中的 rcS 文件有一定了解的,就会发现其非常复杂。现在我们只是初次学习,所以不用搞这么复杂的,知道该文件的作用和位置就可以了,后面工作中像这么复杂的 rcS 文件也是借助其他工具创建的,比如 buildroot 等。

创建好文件/etc/init.d/rcS 以后一定要给其可执行权限!命令如下:

chmod 777 /etc/init.d/rcS

重新启动 Linux 内核,启动后打印信息如下所示:

mount: can't read '/etc/fstab': No such file or directory
/etc/init.d/rcS: line 11: can't create /proc/sys/kernel/hotplug: nonexistent directory
mdev: /sys/dev: No such file or directory

Please press Enter to activate this console.

提示找不到/etc/fstab 文件,还有一些其他的错误,我们先把/etc/fstab这个错误解决了。前面我们说了“mount -a”挂载所有根文件系统的时候需要读取/etc/fstab,因为/etc/fstab 里面定义了该挂载哪些文件,接下来就是创建/etc/fstab 文件。

创建/etc/fstab文件

创建/etc/fstab 文件,并在 fstab 文件中输入如下内容:


/ # vi /etc/fstab

#<file system>  <mount point>   <type>  <options>       <dump>  <pass>
proc            /proc           proc    defaults        0       0
tmpfs           /tmp            tmpfs   defaults        0       0
sysfs           /sys            sysfs   defaults        0       0

fstab格式如下:

<file system> <mount point> <type> <options> <dump> <pass>
  • [file system]:要挂载的特殊的设备,也可以是块设备,比如/dev/sda 等等。
  • [mount point]:挂载点。
  • [type]:文件系统类型,比如 ext2、 ext3、 proc、 romfs、 tmpfs 等等。
  • [options]:挂载选项,在 Ubuntu 中输入“man mount”命令可以查看具体的选项。一般使用 defaults,也就是默认选项, defaults 包含了 rw、 suid、 dev、 exec、 auto、 nouser 和 async。
  • [dump]:为 1 的话表示允许备份,为 0 不备份,一般不备份,因此设置为 0。
  • [pass]:磁盘检查设置,为 0 表示不检查。根目录‘/’设置为 1,其他的都不能设置为 1,其他的分区从 2 开始。一般不在 fstab 中挂载根目录,因此这里一般设置为 0。

fstab 文件创建完成以后,重新启动开发板,打印信息如下所示:

VFS: Mounted root (ext4 filesystem) on device 179:26.
[    2.556792] devtmpfs: mounted
[    2.561310] Freeing unused kernel image (initmem) memory: 1024K
[    2.567854] Run /sbin/init as init process
[    2.718088] usb 1-1: new high-speed USB device number 2 using ci_hdrc
/etc/init.d/rcS: line 11: can't create /proc/sys/kernel/hotplug: nonexistent directory

还是提示“/etc/init.d/rcS: line 11: can’t create /proc/sys/kernel/hotplug: nonexistent directory”,通过网上查询资料得知,是编译内核时没有选CONFIG_HOTPLUG=y,因此在/proc/sys/kernel下将不会创建hotplug文件.(参见kernel/sysctl.c)。修改内核的配置,选中宏CONFIG_HOTPLUG。继续添加其他文件。

创建/etc/inittab文件

创建/etc/inittab 文件,并在 inittab 文件中输入如下内容:

::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::restart:/sbin/init 
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a
  • 第 2 行,系统启动以后运行/etc/init.d/rcS 这个脚本文件
  • 第 3 行,将 console 作为控制台终端,也就是 ttymxc0
  • 第 4 行,重启的话运行/sbin/init
  • 第 5 行,按下 ctrl+alt+del 组合键的话就运行/sbin/reboot,看来 ctrl+alt+del 组合键用于重启系统
  • 第 6 行,关机的时候执行/bin/umount,也就是卸载各个文件系统
  • 第 7 行,关机的时候执行/sbin/swapoff,也就是关闭交换分区。

inittab 的详细内容可以参考 busybox 下的文件 examples/inittab。init 程序会读取/etc/inittab这个文件, inittab 由若干条指令组成。每条指令的结构都是一样的,由以“:”分隔的 4 个段组成,格式如下:

<id>:<runlevels>:<action>:<process>
  • [id]:每个指令的标识符,不能重复。但是对于 busybox 的 init 来说,有着特殊意义。对于 busybox 而言用来指定启动进程的控制 tty,一般我们将串口或者 LCD 屏幕设置为控制 tty
  • [runlevels]:对 busybox 来说此项完全没用,所以空着
  • [action]:动作,用于指定可能用到的动作
  • [process]:具体的动作,比如程序、脚本或命令等。

busybox 支持的动作如下所示:

动作 描述 备注
sysinit 在系统初始化的时候process才会执行一次
respawn 当 process 终止以后马上启动一个新的
askfirst 和 respawn 类似,在运行 process 之前在控制台上显示“Please press Enter to activatethis console.”。只要用户按下“Enter”键以后才会执行 process
wait 告诉 init,要等待相应的进程执行完以后才能继续执行
once 仅执行一次,而且不会等待 process 执行完成
restart 当 init 重启的时候才会执行 proceec
trlaltdel 当按下 ctrl+alt+del 组合键才会执行 process
shutdown 关机的时候执行 process

至此!根文件系统要创建的文件就已经全部完成了。接下来就要对根文件系统进行其他的测试,比如是编写的软件运行是否正常、是否支持软件开机自启动、中文支持是否正常以及能不能链接等。

配置

make menuconfig

Busybox Settings--->
    Build Options--->
        [*]Build BusyBox as a static binary(no shared libs)

Busybox Library Tuning--->
    [*]vi-style line editing commands
    [*]Fancy shell prompts

Linux Module Utilities--->
    [ ]Simplified modutils
    [*]insmod
    [*]rmmod
    [*]lsmod
    [*]modprobe
    [*]depmod

Linux System Utilities--->[*]mdev
    [*]Support /etc/mdev.conf
    [*]Support subdirs/symlinks
    [*]Support regular expressions substitutions when renaming dev
    [*]Support command execution at device addition/removal
    [*]Support loading of firmwares

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
Hilbert
红包 7 2 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
Hilbert
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区