根文件系统是内核启动过程中挂载的第一个文件系统,除了起到普通文件系统的作用以外,其他文件系统也必须依赖它才能挂载。根文件系统必须包含Linux操作系统的基本命令和配置文件,它们在系统启动过程中发挥作用。这里设计的根文件系统以BusyBox为基础。
BusyBox是一个嵌入式Linux的基本工具包,最初是为Debian发行版而设计,以GPLv2版权协议发布。它集成了上百个Linux操作系统的基本命令,可以根据具体应用的要求进行合理剪裁,以控制软件规模,素有“嵌入式Linux的瑞士军刀”之称,在嵌入式Linux系统中广受欢迎。
下面的命令从BusyBox的GIT源获取最新的源码:
$ git clone git://busybox.net/busybox
也可以从https://www.busybox.net/downloads下载指定版本的源码压缩包进行解压。
进入下载目录(或解压目录),执行make menuconfig,出现BusyBox的配置界面(见图1.11)。
图1.11 BusyBox配置界面
BusyBox的配置方法与配置内核类似。根据系统规模和实际需要,选择有用的功能,去掉用不到的命令(有些命令可以通过其他软件获得,功能更完备)。配置完成后,在命令行指定交叉编译器,用下面的命令编译:
$ CROSS_COMPILE=aarch64-linux- make ... LINK busybox_unstripped Trying libraries: crypt m Library crypt is not needed, excluding it Library m is needed, can't exclude it (yet) Final link with: m
注意最后的提示,它表示最后生成的可执行程序BusyBox链接了共享库 libm,移植BusyBox时要连同libm.so.6一起复制到目标系统。
用下面的命令安装:
$ CROSS_COMPILE=aarch64-linux- make install
BusyBox默认设置的安装目录是_install,安装后该目录下面会有bin、sbin、usr/bin、usr/sbin几个子目录,大量Linux的基本命令就分散在这些子目录中,它们都是指向bin/busybox的符号链接。这些子目录和文件是下面制作文件系统的基础。
在BusyBox Settings的Build Options选项中,如果没有选中Build BusyBox as a static binary,即表示是以动态链接的方式编译的,编译后需要把GLibc中的几个共享库以及它们的符号链接复制到目标系统的/lib和/lib64目录中。这里借助BusyBox的安装目录_install,将共享库复制在这个目录下。必须复制的库有下面几个,按如下目录结构组织(版本号取决于交叉编译器提供的GLibc的版本):
_install/ |-- bin/ |-- sbin/ |-- lib/ | `-- ld-linux-aarch64.so.1 -> ../lib64/ld-2.31.so `-- lib64/ |-- ld-2.31.so |-- libc-2.31.so |-- libc.so.6 -> libc-2.31.so |-- libcrypt-2.31.so |-- libcrypt.so.1 -> libcrypt-2.31.so |-- libm-2.31.so |-- libm.so.6 -> libm-2.31.so `-- ...
上面的ld-2.31.so、libc-2.31.so、libcrypt-2.31.so、libm-2.31.so等均来自交叉编译工具,下面是制作链接的过程。
在_install目录下执行:
$ cd lib $ ln -s ../lib64/ld-2.31.so ld-linux-aarch64.so.1 $ cd ../lib64 $ ln -s libc-2.31.so libc.so.6 $ ln -s libm-2.31.so libm.so.6 $ ln -s libcrypt-2.31.so libcrypt.so.1 $ ...
并非BusyBox中的所有命令都会用到数学函数库libm和加密算法库libcrypt。Busy-Box编译后会给出提示是否用到除libc以外的库。作为初始化RAMDisk,应尽可能精简;如果运行完整的文件系统,建议把GLibc的所有共享库及它们的链接都复制到目标系统/lib和/lib64目录(32位系统不使用lib64目录,所有库文件都在lib目录中)。因为这些库即使不被BusyBox用到,也会被以后移植的其他软件用到。此外,用aarch64-linux-strip命令去掉共享库中的导出符号,可以大幅度缩减它们的体积。程序和共享库中的符号主要用于代码分析和调试,正常运行的程序不会用到。
Linux系统的根文件系统可以直接来自只读存储设备,也可以由随机存储器(RAM)构成。这里的只读存储设备指的是磁盘分区或者闪存(Flash)这类的存储器,并非严格意义的“只读”。一些小型嵌入式设备倾向于使用随机存储器按磁盘文件系统的形式做成RAMDisk,作为根文件系统。因为RAMDisk直接在内存中运行,速度很快,并且没有物理损耗,不像Flash那样有擦写次数的限制。其缺点是不能长久保存数据,一旦断电,则数据全部丢失,必须要设计其他保存数据的方法。
多数Linux使用一个初始化RAMDisk作为操作系统的引导,它通常只做一些非常基础的工作,比如加载根文件系统所需要的驱动,并根据Bootloader提供的参数挂载实际运行的根文件系统,最后通过switch_root切换到实际根文件系统。这种方式便于系统升级,同时对提高系统的可靠性也有一定的帮助,不至于在系统软件被破坏时无法启动。
在PC上构造下面的目录和文件结构,它们是树莓派的初始化RAMDisk的基础。
ramdisk/ |-- bin/ | |-- [ -> busybox | |-- [[ -> busybox | |-- awk -> busybox | |-- base64 -> busybox | |-- basename -> busybox | |-- busybox | |-- cat -> busybox | |-- ... | `-- umount -> busybox | |-- dev/ (空目录) |-- init (初始化脚本, 可执行文件) | |-- sbin/ | |-- fbset -> ../bin/busybox | |-- reboot -> ../bin/busybox | `-- switch_root ../bin/busybox | |-- lib/ | `-- ld-linux-aarch64.so.1 -> ../lib64/ld-2.31.so | |-- lib64/ | |-- ld-2.31.so | |-- libc-2.31.so | |-- libc.so.6 -> libc-2.31.so | |-- libcrypt-2.31.so | |-- libcrypt.so.1 -> libcrypt-2.31.so | |-- libm-2.31.so | `-- libm.so.6 -> libm-2.31.so | |-- proc/ (空目录) | |-- splash/ | `-- splash.png (启动画面, 可选) | |-- sys/ (空目录) `-- sysroot/ (空目录)
内核支持初始化RAM文件系统和RAMDisk 的选项在General setup主菜单下,注意在配置内核时将此项支持编入内核,并选择一个压缩格式,之后制作文件系统时使用对应的格式压缩。压缩的目的是减少对BOOT分区的占用。
将之前编译的BusyBox及所需的共享库复制到bin、sbin、lib和lib64目录。制作初始化RAMDisk, BusyBox应尽可能简化,只需要保留脚本程序init中用到的命令,以减少RAMDisk占用空间。多余的选项并不影响功能,只是制作的映像文件较大。dev、proc、sys几个空目录用于挂载相应的伪文件系统DEVTMPFS、PROCFS和SYSFS。sysroot用于挂载系统运行的根文件系统。伪文件系统(pseudo filesystem)是Linux操作系统支持的一类文件系统,它将内核的信息以目录和文件的形式展示在用户空间,并允许用户通过这些文件与内核交换信息。伪文件系统在内存中实现。
在内核源文件init/main.c中,内核启动函数kernel_init()规划如下:
在以上程序的最后部分是当Bootloader传递了init参数时的启动过程,它逐一尝试运行/sbin/init、/etc/init、/bin/init和/bin/sh。其中如果有一个运行成功,则内核启动成功,操作系统1号进程开始进入操作系统正常工作状态;第一段则是RAMDisk方式的启动过程,其中的字符串指针ramdisk_execute_command指向/init。这个程序是留给开发者自己设计系统的初始化过程,它也可以用脚本程序实现。程序清单1.1就是一种初始化脚本的设计方案。需要注意的是,它应该是一个可执行文件。文本编辑器保存脚本文件时默认是没有可执行属性的,需要通过chmod+x init命令使其能够执行。
程序清单1.1 初始化脚本init
程序清单1.1中代码第3∼125行定义了一些函数,包括消息打印、调试信息打印、错误提示、挂载不同的文件系统、显示启动画面等。第127∼131行挂载伪文件系统,这些文件系统是Linux运行必需的;第143∼154行设置一些内核参数;第156∼159行留出一个接口,插入运行另一个脚本程序(如果这个脚本文件存在);第161∼165行清屏;第167∼231行从/proc/cmdline文件读取Bootloader传递给内核的参数,并根据这些参数进行相应的处理,或者设置变量留待后期处理;第244∼248行调用第121行的prepare_sysrootmmc()函数挂载可用的文件系统,对于树莓派来说,就是在VFAT分区cmdline.txt文件中由root参数指定的设备文件,将这个设备文件所对应的分区挂载到sysroot目录;第256∼259行将已挂载的伪文件系统目录dev、proc、sys移至sysroot对应的目录,这些目录应该在SD卡的Ext4分区事先创建;第252∼258行将根文件系统切换到SD卡的Ext4分区,并运行这个分区的/sbin/init程序。如果成功,Linux系统的第一级初始化即告结束,进入系统正常运行状态;否则脚本就会走到最后一行,输出一个错误提示。
将以上目录结构通过cpio命令制作成初始化RAMDisk映像文件,并通过gzip压缩。下面是这一操作的命令:
$ find . | cpio -H newc -o | gzip > ../initramfs.gz
注意新生成的文件initramfs.gz不要放在当前目录下,以免被find命令递归。
然后将这个映像文件复制到VFAT分区,通过config.txt文件中的参数initramfs告诉内核,这是初始化根文件系统。
如果需要修改这个映像文件,需要先用下面的命令对它进行解压:
$ gunzip -cd ../initramfs.gz | cpio -i
然后在解压目录里进行修改。压缩/解压方式应与内核配置初始化RAM文件系统所支持的方式一致。
构造Ext4分区的工作需要在PC上进行。Ext4分区基础部分涉及GLibc、BusyBox、目录结构和一些脚本文件。
GLibc(GNU C Library)是C语言标准库,又称为libc或libc6(第2版GNU C库,主版本号是6)。它是构成Linux操作系统的基础。除内核以外,Linux操作系统中的所有软件都直接或间接地依赖GLibc。
交叉编译工具已经包含了GLibc中库的部分。单独编译交叉编译器时,还会产生系统的一些基础工具软件,如ldconfig(动态链接库路径配置命令)、iconv(字符集转换工具)、字符集数据charmaps、locales等。下面是GLibc的结构(libgcc除外,它来自gcc)。为减少以后系统工作时的麻烦,最好将这些内容全部复制到树莓派的Ext4分区上。同样地,为了节省占用的存储空间,二进制程序和共享库也可以用aarch64-linux-strip命令瘦身。
/-- lib/ (共享库目录) | `-- ld-linux-aarch64.so.1 -> ../lib64/ld-2.31.so | |-- lib64/ | |-- ld-2.31.so | |-- libc-2.31.so | |-- libc.so.6 -> libc-2.31.so | |-- libcrypt-2.31.so | |-- libcrypt.so.1 -> libcrypt-2.31.so | |-- libm-2.31.so | |-- libm.so.6 -> libm-2.31.so | `-- ... | |-- sbin/ | `-- ldconfig (共享库路径配置命令) | `-- usr/ |-- bin/ | |-- iconv (字符集转换命令) | |-- locale (显示本地化环境变量) | |-- localedef (编译本地化定义文件) | `-- ... | |-- include/ (C 语言标准头文件目录) |-- lib64/ (libc 和 gcc 链接库目录) | |-- libc.a | |-- libc_noshared.a | |-- libgcc.so.1 | |-- libgcc.so -> libgcc.so.1 | |-- ... | `-- gconv/ (字符集转换模块目录) | |-- sbin/ | `-- iconvconfig (字符集转换缓冲设置) | `-- share/ |-- i18n/ | |-- charmaps/ (该目录存放各种语言字符集映射表) | `-- locales/ (该目录存放本地化环境变量脚本) `-- locale/ (该目录存放各软件不同语言消息一览表)
为了便于树莓派的本地程序编译,除了保留静态库、C语言标准头文件以外,还需要创建共享库的“.so”后缀链接,操作命令如下(在microSD卡挂载目录上执行,或在目标系统的根目录上执行):
# cd usr/lib64 # ln -s ../../lib64/libc-2.31.so libc.so # ln -s ../../lib64/libm-2.31.so libm.so # ln -s ../../lib64/libdl-2.31.so libdl.so # ln -s ../../lib64/librt-2.31.so librt.so # ...
在Linux操作系统中,每个共享库根据其用途的不同,通过构造符号链接,形成3个文件名。以数学函数库libm为例,这3个文件的作用如下。
(1)libm-2.31.so是原文件,提供链接文件的依据。大多数库文件名的版本号在“.so”之后,形如libfoo.so.xx.yy.zz。GLibc库的命名方式比较特殊,版本号在中间。
(2)libm.so是动态库链接文件,gcc用“-l”选项指定库名时,链接器会查找“.so”后缀的文件。这种形式的库文件用于开发阶段,通常放在/usr/lib目录中,这是gcc的默认库搜索路径。
(3)libm.so.6是运行时(run-time)共享库。动态链接的程序在运行时需要调用这个文件中的共享代码。文件名是在编译libm-2.31.so时通过链接参数soname指定的,因此程序虽然链接时用的是libm.so,但运行时要依赖libm.so.6。这些库通常在/lib、/usr/lib目录中,或者在/etc/ld.so.conf列出运行时共享库的路径,例如,可以用cat命令查看这个文件的内容:
# cat /etc/ld.so.conf /usr/lib /usr/local/lib
此时运行ldconfig命令时,该命令会生成ld.so.cache缓存文件。动态链接的程序通过缓存文件找到共享库。另外还有一种临时的方法:将共享库路径加入环境变量LD_LIBRARY_PATH,动态链接的程序会在这个变量列出的目录中查找共享库。
为构造Ext4分区而编译的BusyBox与构造初始化RAMDisk时的选取策略不同,安装在Ext4分区的软件应侧重考虑功能的完善而不是存储空间的占用,因为即使编译BusyBox的全部功能,占用空间也不过1MB左右,相对于分区容量微不足道。
安装了GLibc、BusyBox和一些重要脚本的目录结构如下:
/-- bin/ (BusyBox 普通用户程序) |-- dev/ (空目录, 用于挂载DEVTMPFS) |-- etc/ | |-- fstab (文件系统挂载) | |-- group (分组管理文件) | |-- hosts (主机名静态表) | |-- init.d/ | | |-- hostname | | |-- modules | | |-- network | | |-- telnetd | | `-- ... | | | |-- inittab (/sbin/init 脚本文件) | |-- issue (登录前的消息提示) | |-- issue.net (远程登录前的消息提示) | |-- ld.so.conf (共享库目录配置文件) | |-- mtab -> /proc/mounts | |-- passwd (用户管理文件) | |-- rc.d/ | | |-- rc.local | | `-- rcS (sysv-init 启动脚本) | | | `-- resolv.conf (域名解析配置文件) | |-- home/ | `-- pi/ (普通用户主目录) | |-- hostname (主机名称) |-- lib/ | |-- ld-linux-aarch64.so.1 -> ../lib64/ld-2.31.so | |-- firmware/ (设备驱动所需的固件) | `-- modules/ | `-- 5.4.35/ (来自内核编译的模块) | |-- lib64/ (GLibc共享库目录) |-- lost+found/ (用于文件系统故障时的数据恢复) |-- mnt/ (空目录, 挂载文件系统用) |-- proc/ (空目录, 用于挂载PROCFS) |-- root/ (超级用户主目录) |-- run/ (存放系统运行时数据) |-- sbin/ (BusyBox 系统用户程序) |-- sys/ (空目录, 用于挂载SYSFS) |-- tmp/ (临时数据目录) |-- usr/ (二级目录结构起点) | |-- bin/ (BusyBox 普通用户程序) | `-- sbin/ (BusyBox 系统用户程序) | `-- var/ (系统运行时变化数据)
以上/bin、/sbin、/usr/bin、/usr/sbin均来自BusyBox编译的内容,/lib、/lib64中的内容来自GLibc的共享库,内核模块在/lib/modules目录,/etc目录下是手工创建的一些系统配置文件,lost+found是Ext系列文件系统的固有目录,通常是空的。当文件系统发生故障时,可以使用文件系统检查工具fsck进行修复,不能正常恢复的文件会被归入该目录下,由用户手工处理。其余目录及功能如下。
(1)/dev目录用于挂载DEVTMPFS。如果内核支持自动管理devtmpfs,设备文件节点会自动地创建在这个目录下;否则,需要手工通过mknod命令创建设备文件。
(2)/mnt目录作为系统启动后动态挂载其他文件系统的节点。这个目录不是必需的。
(3)/proc目录用于挂载PROCFS伪文件系统,内核通过这个文件系统以文件的形式向用户空间提供进程和系统信息。
(4)/sys是另一个伪文件系统SYSFS的挂载点,SYSFS提供内核子系统、硬件设备及其驱动的用户空间访问途径。
(5)/tmp用于挂载临时文件系统TMPFS。这个目录的粘滞位(sticky bit)应该置位,以保证多个用户可以将运行数据写入这个公共目录,但不能修改别人的数据。设置粘滞位的方法:
$ chmod +t tmp
如果是单用户系统,粘滞位自然就没有意义了。
(6)/var用于存放系统运行中的一些变化数据,如软件包数据库、系统日志、字体缓存文件等。
这些目录在构建文件系统时都是空的,系统启动时通过相关的初始化过程一一挂载文件系统或随着系统的运行增加一些数据。
为了让系统能正常启动,需要编辑/etc目录中的一些脚本文件。
fstab描述如何静态挂载文件系统。每个分区(文件系统)由下面6个字段构成:
<分区名> <挂载目录> <分区类型> <选项> <转存方式> <启动检查>
在执行mount命令的-a选项时,系统会按这张表中给定的方式挂载文件系统。由于嵌入式系统的运行方式都是确定的,需要挂载的文件系统都已在启动时完成,用到这个文件的机会不多。
初装的Linux没有明确的用户。为了给不同用户提供各自独立的空间,也为了系统的安全,Linux有一套简单的分组策略,它通过/etc/passwd和/etc/group两个文件管理。/etc/passwd每一行按下面的格式记录一个用户的账户信息:
用户名:密码:用户ID:组ID:用户信息(姓名、电话等信息):主目录:用户环境
例如,可以用文本编辑器编辑passwd文件,按如下内容创建root和pi两个账户:
root::0:0:root:/root:/bin/sh pi::1000:1000:RaspberryPI user,,,:/home/pi:/bin/sh
这里的用户环境可暂时使用BusyBox提供的/bin/sh,待安装bash后可以用/usr/bin/bash替换。
密码字段如果是空的,表示没有密码。用户登录目标系统后可以使用passwd命令创建或修改密码,甚至也可以在目标系统上使用adduser创建用户。习惯上,超级用户(用户ID和组ID都是0)root的主目录在根目录下,普通用户的主目录在/home目录下。在桌面系统中,/home目录通常和根目录不在同一个磁盘分区。这样的设计可以使得在系统崩溃时用户数据仍然可以得到恢复。
/etc/group每一行记录一个组信息:
组名:密码:组ID:组员列表(用逗号分隔)
与用户管理文件passwd类似,我们为系统创建两个组:
root:x:0: pi:x:1000:
passwd和group文件的密码字段保存着密码的密文。由于不存在通过密码运算得到“x”的原字符串,因此在密码字段的“x”实际上起到的作用就是禁止使用密码。由于历史原因,文件/etc/passwd和/etc/group必须是对所有用户可读的。其中存放密码信息会对用户安全构成隐患,因为尽管这里存放的是密文,但仍有遭遇暴力破解的危险。因此在PC上,真正的密码会存放在另一个文件/etc/shadow中,这个文件通常只对超级用户可读。
BusyBox编译出的/sbin/init接管第一阶段初始化过程,它使用/etc/inittab作为执行脚本。文件inittab被设计成表格形式,每一项有4个字段,按下面的格式设置:
id:runlevels:action:process
BusyBox的初始化过程对inittab做了简化处理,它忽略了在通用计算机系统使用的运行级别的概念,所以runlevels这个字段是空的。action表示响应的动作,process是完成对应动作的进程命令。inittab的编写可参考BusyBox中的examples/inittab样板。一个简单的脚本见程序清单1.2。
程序清单1.2 init脚本/etc/inittab
其中第3行代码调用另一个启动脚本/etc/rc.d/rcS,该脚本完成创建操作系统的工作环境,包括加载驱动、设置环境变量、启动各种服务的守护进程、创建图形运行环境等。这种初始化方式被称为sysv-init,它源自UNIX System V。图1.12是系统启动过程框架。这种启动方式的优点是过程清晰、实现简单、代码规模小,但由于启动任务是顺序进行的,因此启动时间较长;而且所有列入启动项的服务不论是否使用都会启动,因此会占用一些不必要的资源。目前Linux的桌面发行版普遍采用Systemd的初始化方式,可以将启动任务并行执行,并且按需启动。但Systemd的代码规模比较大。
图1.12 sysv-init启动过程框架
系统启动后需要完成的初始化工作很零散,初始化任务全部放在/etc/rc.d/rcS文件中不方便管理。我们可以为每一项任务编写一个脚本文件,而rcS只负责管理这些脚本(见程序清单1.3)。习惯上,这些脚本放在/etc/init.d目录中。
程序清单1.3 启动管理脚本rcS
程序清单1.3中第5行列出了启动任务以及它们的顺序,每个任务交由对应的脚本文件负责,它们集中放在/etc/init.d目录下。在这个例子中,初始化过程包括设置主机名、加载额外的模块、挂载其他文件系统、设置网络接口、启动TELNET服务。通常这些脚本接受start、stop、restart、status等参数(程序清单1.3中第4行的mode),程序清单1.4是其中的一个脚本。其中start或stop参数可由rcS文件传递过去,其他参数在系统运行过程中手动操作。最后一行留出一个接口,用户可将一些额外的启动任务写进/etc/rc.d/rc.local脚本文件中,避免破坏rcS的结构。这些脚本文件必须具有可执行属性。
程序清单1.4 初始化网络接口脚本/etc/init.d/network
将程序清单1.4第4行的IPADDR的点分十进制形式IP地址改为dhcp,该脚本会调用udhcpc命令从DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)服务器获得动态IP地址(见程序清单1.4中代码第14∼15行),udhcpc来自BusyBox。为了避免查找设备的麻烦,建议用静态方式设置树莓派的IP地址,注意将IP地址设置在与主机相同的网段,并确保不要与局域网内的其他主机地址冲突。如果是跨网段设置,还需要设置正确的路由,让PC能访问到树莓派。动态获取IP地址可以避免地址冲突,但在没有显示界面的情况下,用户不知道它获得的IP地址是什么,如果想通过网络而不是串口调试器使用树莓派,可能会找不到这个机器 。
单独的udhcpc命令只能获得地址,本身不负责配置网络。用户可以根据得到的地址再使用ifconfig命令配置。BusyBox提供了一个自动配置的脚本文件模板simple.script,它在BusyBox源码的examples/udhcp/目录下。如果使用自动配置IP地址功能,可将其复制到目标系统的/usr/share/udhcpc目录,将其重新命名为default.script,并用chmod命令给它加上可执行属性。
一个简化的模块加载脚本modules见程序清单1.5,需要加载的模块列在变量MODLIST中。
程序清单1.5 加载模块脚本/etc/init.d/modules
其他脚本处理方法类似,不再赘述。
树莓派有两类UART串口:一类是PL011,它和16650 UART兼容,16650 UART是早期个人计算机的标准配置;另一类是mini UART(简化的UART)。树莓派3代之前有2个UART,其中UART0对应PL011,UART1对应mini UART;树莓派4代增加了4个PL011,对应UART2∼UART5。Linux会将其中的一个作为基本UART,连到树莓派引脚8、10(见图1.5);另一个作为第二UART,默认是禁用的,可以通过适当的设置使其用于蓝牙接口。表1.2是几个型号的树莓派串口缺省配置情况。
表1.2 UART缺省配置
为使串口调试器工作,除了内核支持,还需要在BOOT分区的启动配置文件config.txt中添加如下两行代码,加载对应的设备树文件:
enable_uart=1 dtoverlay=pi3-miniuart-bt
enable_uart为0表示mini UART,为1表示PL011(UART0),Linux启动后,它对应设备文件/dev/ttyAMA0。mini UART作为蓝牙使用。
如果串口设置正确,树莓派上电后,可以在串口监控器(minicom命令环境)看到如下Linux的启动过程:
[ 0.000000 ] Booting Linux on physical CPU 0x0000000000 [0x410fd083] [ 0.000000 ] Linux version 5.9.12-v8+ ... [ 0.000000 ] Machine model: Raspberry Pi 4 Model B Rev 1.1 ... [ 0.000000 ] Zone ranges: [ 0.000000 ] DMA [mem 0x0000000000000000-0x000000003fffffff] [ 0.000000 ] DMA32 [mem 0x0000000040000000-0x00000000fbffffff] [ 0.000000 ] Normal empty [ 0.000000 ] Movable zone start for each node [ 0.000000 ] Early memory node ranges [ 0.000000 ] node 0: [mem 0x0000000000000000-0x000000002fffffff] ... [ 1.585717 ] Key type ._fscrypt registered [ 1.587097 ] Key type .fscrypt registered [ 1.588503 ] Key type fscrypt-provisioning registered [ 1.599671 ] uart-pl011 fe201000.serial: ... Setting the hostname to RaspberryPI Mounting filesystems Setting up networking on loopback device: RaspberryPI:/ #
最后出现了Linux的提示符“#”,此时可以正常操作树莓派上的Linux了。
内核启动WiFi或加载模块brcmfmac时,需要对应的固件brcmfmac43455-sdio.bin。固件和配置文本brcmfmac43455-sdio.txt可在https://github.com/RPi-Distro/firmwarenonfree/brcm下载。大多数PC的Linux桌面发行版也会有常用的固件,因此也可以直接从PC的/lib/firmware/brcm目录下复制。如果没有固件,一些WiFi网卡不能驱动,启动后使用dmesg命令查看内核消息,会看到下面的错误提示:
下载的固件和配置文本应置于根文件系统的/lib/firmware/brcm目录下。对于树莓派4B,配置文本应重新命名为brcmfmac43455-sdio.raspberrypi,4-model-b.txt。
网络设备正常驱动后,通过ifconfig-a命令可以看到eth0和wlan0两个网络接口名,前者是有线网接口,后者是无线网接口。BusyBox的ifconfig命令可以配置有线网卡,程序清单1.4完成的就是有线网配置工作。无线网wlan0的配置需要另外的工具,在2.5.3节将会详细介绍。
完成网络配置后,主机就可以通过网络方式访问树莓派,不再需要串口调试器了。网络方式方便、快捷,比串口提供的功能更多、更灵活。BusyBox已经包含了TELNET服务。使用这个服务前需要先挂载DEVPTS文件系统。挂载DEVPTS可使用下面的命令:
# mkdir /dev/pts # mount -t devpts devpts /dev/pts
启动TELNET服务只需要一条简单的命令:
# /sbin/telnetd -l /bin/login
较为规范的做法是将TELNET服务写入/etc/init.d/telnetd脚本,在系统启动时由rcS管理TELNET服务(对应程序清单1.3中第5行services_cfg的最后一项)。完整的服务器脚本程序还应负责管理停止、重启服务等功能。
主机通过telnet命令连接树莓派的TELNET服务器,输入正确的用户名和密码,即可远程登录树莓派,并看到主机的问候语和提示符。这里所说的“远程”是网络上的概念,并非指地理距离。
$ telnet 192.168.2.100 Trying 192.168.2.100... Connected to 192.168.2.100. Escape character is '^]'. RaspberryPI login: root Password: ############################################# # Raspberry Pi4 # # Desktop # ############################################# root:~ #
问候语来自树莓派上的/etc/issue或/etc/issue.net文件,前者是本地登录时的回应,后者是通过网络登录时的回应。这两个文件可根据需要手动编辑。
/etc目录下还有其他一些配置文件,它们的功能如下:/etc/hosts存储了主机名的静态表;/etc/hostname存储主机名。主机名可出现在终端提示符上,在网络使用环境下便于用户识别当前所处的位置;ld.so.conf列出了共享库的路径,当执行ldconfig命令时,这些路径下的共享库文件路径会被缓存在/etc/ld.so.cache文件中。不在默认搜索路径中的共享库,应用程序需要通过这个文件才能动态链接到共享库;/etc/resolv.conf是域名解析列表,用于帮助计算机通过域名方式进行网络访问。下面是一个简单的形式(假设域名解析器地址8.8.8.8可以访问):
nameserver 8.8.8.8
树莓派本身的存储空间极其有限。为了扩展树莓派的存储空间,我们在主机上安装NFS(Network File System,网络文件系统)服务并启动。NFS服务器的配置文件是/etc/exports,可以将下面一行语句写进这个配置文件,用以指明服务器共享目录、开放范围及访问权限:
/srv/nfs 192.168.2.*(rw,sync,no_subtree_check)
修改配置后需要重启NFS服务:
# service nfs-kernel-server restart
树莓派用mount命令将网络文件系统共享出来的目录挂载到本地的/mnt目录(此处假设主机——也就是NFS服务器的IP地址是192.168.2.110),例如:
# mount 192.168.2.110:/srv/nfs /mnt -o nolock,proto=tcp
NFS提供TCP和UDP两种协议方式。上面的mount命令来自BusyBox。参数中带有冒号的会被mount命令自动识别为网络文件系统,不需要通过选项“-t nfs”明示。由Busy-Box编译出的mount命令需要明确指明使用TCP(即-o指定的选项nolock, proto=tcp),来自util-linux软件包中的mount命令缺省使用TCP,不需要这个选项。
建立网络文件系统后,在树莓派系统上访问/mnt目录和在PC上访问/srv/nfs目录是一致的,二者访问的是同一个资源,真正的存储设备在PC上。例如,我们可以在PC上编写一个C语言程序(假设文件名为hello.c),用下面的命令编译:
$ aarch64-linux-gcc -o hello hello.c
将编译后的可执行程序hello复制到NFS服务器目录/srv/nfs,再在树莓派的终端上执行:
# /mnt/hello
便可以看到程序运行的结果。
在嵌入式Linux的开发阶段,网络连接是一个非常有用的功能,它除了可以省下一套输入/输出设备(键盘、鼠标、显示器)以外,还可以借助主机的强大处理能力、灵活的软件配置和大量的存储空间,为嵌入式系统开发提供强有力的支持。