前言
前段时间在学习韦东山的Linux课程过程中,特地去补了一下开发板的开机原理相关知识,之后才解决关闭GUI的问题,目前网上关闭GUI的方案都是删掉或移除某个文件,但这种方式太简单粗暴,而且如果想找回来,会比较麻烦。我使用的是IMX6ULL开发板,这篇文章会从以IMX6ULL的角度来讲解IMX6ULL的启动过程原理,以及如何优雅的进行关闭GUI和启动动画,下面进入正文。
当给开发板通电到Linux系统启动,这个过程发生了什么?
1. 上电复位(Power-on Reset)
当i.MX6ULL处理器上电时,处理器的复位引脚被拉高,触发上电复位。此时,处理器开始执行内部ROM代码。
2. ROM代码执行(ROM Code Execution)
i.MX6ULL内部的ROM代码负责初始化基本硬件并确定启动设备。ROM代码会按照预设的启动设备顺序(如eMMC、NAND Flash、SD卡等)尝试加载启动加载器。
3. 加载并执行SPL(Secondary Program Loader)
如果启动设备中包含有效的SPL镜像,ROM代码将其加载到片上RAM(如OCRAM)中,并跳转执行。SPL的主要任务包括:
- 初始化时钟和电源管理。
- 初始化DDR内存。
- 配置必要的外设。
4. 加载并执行U-Boot(主要引导程序)
SPL完成基本硬件初始化后,会加载U-Boot(通常存储在同一存储设备中)的镜像到DDR内存中,并跳转到U-Boot的入口点执行。U-Boot的主要功能包括:
- 初始化更多的硬件外设。
- 提供一个命令行接口,用于调试和配置。
- 加载操作系统内核和设备树文件(Device Tree Blob, DTB)。
5. 加载操作系统内核和设备树
在U-Boot的环境中,通常会有一系列的自动化脚本(如bootcmd)配置加载Linux内核和设备树文件。具体步骤如下:
- 加载内核镜像:U-Boot从存储设备中加载Linux内核镜像(如zImage或uImage)到DDR内存中。
- 加载设备树文件:U-Boot加载与硬件配置匹配的设备树文件到内存中。
- 启动内核:U-Boot将控制权交给内核,内核开始执行。
6. 内核启动(Kernel Boot)
Linux内核接管系统控制后,开始进行一系列初始化,包括:
- 设置中断向量表。
- 初始化内存管理。
- 初始化设备驱动。
- 挂载根文件系统。
内核初始化完成后,会启动用户空间的第一个进程,即init
进程。
7. 启动init进程
内核启动后,会查找并执行init
进程。通常情况下,init
进程是用户空间的第一个进程,其路径通常为/sbin/init
或/bin/init
。init
进程负责进一步的系统初始化,包括:
- 读取初始化脚本(如
/etc/inittab
或系统的init配置)。 - 启动系统服务和守护进程。
- 设置用户登录环境。
分析inittab文件和init.d目录
inittab 文件
了解了开发板的启动过程之后,我们来看看inittab脚本里面含有什么(inittab文件是用于定义系统启动和关闭过程中需要执行的任务。它主要用于BusyBox init系统,当代更流行是systemd系统),IMX6ULL开发板上的inittab文件内容如下:
# /etc/inittab
#
# Copyright (C) 2001 Erik Andersen <andersen@codepoet.org>
#
# 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 == tty to run on, or empty for /dev/console
# runlevels == ignored
# action == one of sysinit, respawn, askfirst, wait, and once
# process == program to run
# Startup the system
::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -o remount,rw /
::sysinit:/bin/mkdir -p /dev/pts /dev/shm
::sysinit:/bin/mount -a
::sysinit:/sbin/swapon -a
null::sysinit:/bin/ln -sf /proc/self/fd /dev/fd
null::sysinit:/bin/ln -sf /proc/self/fd/0 /dev/stdin
null::sysinit:/bin/ln -sf /proc/self/fd/1 /dev/stdout
null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr
::sysinit:/bin/hostname -F /etc/hostname
# now run any rc scripts
::sysinit:/etc/init.d/rcS
# Put a getty on the serial port
ttymxc0::respawn:/sbin/getty -L ttymxc0 0 vt100 # GENERIC_SERIAL
# Stuff to do for the 3-finger salute
#::ctrlaltdel:/sbin/reboot
# Stuff to do before rebooting
::shutdown:/etc/init.d/rcK
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
此文件中条目的通用格式是:
id:运行级别:动作:进程
- id: 终端设备ID(如tty1、tty2等),如果为空,则表示默认控制台(/dev/console)。
- runlevels: 运行级别。在BusyBox init中,这个字段被忽略。
- action: 定义如何处理这个条目。可以是sysinit、respawn、askfirst、wait和once。
- process: 要运行的程序或命令。
从文件中我们可以得到如下信息:
在系统初始化阶段(sysinit):
# 挂载proc文件系统到/proc。
::sysinit:/bin/mount -t proc proc /proc
# 将根文件系统重新挂载为读写模式。
::sysinit:/bin/mount -o remount,rw /
# 创建必要的目录,如/dev/pts和/dev/shm。
::sysinit:/bin/mkdir -p /dev/pts /dev/shm
# 根据/etc/fstab文件中的配置,挂载所有文件系统。
::sysinit:/bin/mount -a
# 启用所有的交换分区。
::sysinit:/sbin/swapon -a
# 创建标准输入、输出和错误的符号链接。
null::sysinit:/bin/ln -sf /proc/self/fd /dev/fd
null::sysinit:/bin/ln -sf /proc/self/fd/0 /dev/stdin
null::sysinit:/bin/ln -sf /proc/self/fd/1 /dev/stdout
null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr
# 设置系统主机名,主机名来自/etc/hostname文件。
::sysinit:/bin/hostname -F /etc/hostname
# 执行初始化脚本/etc/init.d/rcS
::sysinit:/etc/init.d/rcS
运行级别(respawn):
# 以下条目定义了一个getty进程,以便提供一个登录提示符:
ttymxc0::respawn:/sbin/getty -L ttymxc0 0 vt100 # GENERIC_SERIAL
在串口ttymxc0上运行getty进程,以便通过串口终端登录。respawn表示进程如果退出会被重新启动。
系统关闭(shutdown):
# 执行关闭脚本/etc/init.d/rcK,通常用于停止各种系统服务。
::shutdown:/etc/init.d/rcK
# 禁用所有的交换分区。
::shutdown:/sbin/swapoff -a
# 卸载所有文件系统。
::shutdown:/bin/umount -a -r
init.d 目录
在系统初始化阶段,init去执行了/etc/init.d/rcS
脚本,先来看下init.d目录下有什么:
[root@100ask:/etc/init.d]# ls -l /etc/init.d
total 84
-rwxr-xr-x 1 root root 1012 Jul 23 2021 S01syslogd
-rwxr-xr-x 1 root root 1004 Jul 23 2021 S02klogd
-rwxr-xr-x 1 root root 1876 Jul 23 2021 S02sysctl
-rwxr-xr-x 1 root root 620 Jul 23 2021 S05lvgl
-rwxr-xr-x 1 root root 559 Jul 23 2021 S09modload
-rwxr-xr-x 1 root root 1634 Jan 1 1970 S10udev
-rwxr-xr-x 1 root root 1684 Jul 23 2021 S20urandom
-rwxr-xr-x 1 root root 1635 Jul 23 2021 S30dbus
-rwxr-xr-x 1 root root 438 Jul 23 2021 S40network
-rwxr-xr-x 1 root root 707 Jul 23 2021 S44modem-manager
-rwxr-xr-x 1 root root 751 Jul 23 2021 S45network-manager
-rwxr-xr-x 1 root root 581 Jul 23 2021 S49ntp
-rwxr-xr-x 1 root root 531 Jul 23 2021 S50mosquitto
-rwxr-xr-x 1 root root 557 Jul 23 2021 S50pulseaudio
-rwxr-xr-x 1 root root 591 Jul 23 2021 S50sshd
-rwxr-xr-x 1 root root 614 Jul 23 2021 S50telnet
-rwxr-xr-x 1 root root 427 Jul 23 2021 S80dnsmasq
-rwxr-xr-x 1 root root 1343 Jul 23 2021 S98swupdate
-rwxr-xr-x 1 root root 1403 Jul 23 2021 bluetooth
-rwxr-xr-x 1 root root 423 Jul 23 2021 rcK
-rwxr-xr-x 1 root root 455 Jan 1 00:06 rcS
如下是rcS脚本的内容:
#!/bin/sh
# Start all init scripts in /etc/init.d
# executing them in numerical order.
#
psplash -n &
for i in /etc/init.d/S??* ;do
# Ignore dangling symlinks (if any).
[ ! -f "$i" ] && continue
case "$i" in
*.sh)
# Source shell script for speed.
(
trap - INT QUIT TSTP
set start
. $i
)
;;
*)
# No sh extension, so fork subprocess.
$i start
;;
esac
done
/bin/hostname -F /etc/hostname
rcS脚本很简单,它使用了psplash
程序开启一个启动动画,这里介绍下什么是psplash
:
psplash(Plymouth Splash)是一个轻量级的、适用于嵌入式Linux系统的启动画面程序。它在系统启动时显示一个简单的图形界面,通常是一个logo或进度条,以掩盖启动过程中出现的详细文本输出,从而为用户提供更友好的视觉体验。
接着,rcS脚本遍历/etc/init.d/
目录下的所有以 S 开头的脚本文件,并且忽略任何悬挂的符号链接(指向不存在文件的链接)。如果当前文件$i
不存在(不是一个普通文件),则跳过执行,继续下一轮循环。
接着使用case
语句处理文件,根据文件扩展名不同采取不同的执行方式:
- 对于以
.sh
结尾的脚本:- 使用子shell执行,以加快执行速度。
trap - INT QUIT TSTP
:忽略中断、退出和挂起信号。set start
:将start作为参数传递给脚本。. $i
:源执行脚本(即在当前shell环境中执行)。
- 对于其他文件:
- 直接以子进程的方式执行,并传递start参数。
可以看出,每个S开头的文件都会被执行,我们打开其中一个文件,以S05lvgl
文件为例,从它的命名可以看出它是一个启动LVGL GUI的脚本文件,它的内容如下:
#!/bin/sh
#
# Start lvgl....
#
start() {
printf "Starting 100ask lvgl: "
echo -n -e '\e[?17;14;224c'
echo 0 > /sys/class/graphics/fbcon/cursor_blink
start-stop-daemon -S -q -m -b -p /var/run/lvgl.pid \
-x /usr/share/lvgl/lvgl_100ask_demo
[ $? = 0 ] && echo "OK" || echo "FAIL"
}
stop() {
printf "Stopping lvgl: "
start-stop-daemon -K -q -p /var/run/lvgl.pid \
-x /usr/share/lvgl/lvgl_100ask_demo
[ $? = 0 ] && echo "OK" || echo "FAIL"
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
stop
start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit $?
这是一个典型的init脚本,包含start
、stop
和restart
函数,这个脚本做了如下事情:
start
函数
start() {
printf "Starting 100ask lvgl: "
echo -n -e '\e[?17;14;224c'
echo 0 > /sys/class/graphics/fbcon/cursor_blink
start-stop-daemon -S -q -m -b -p /var/run/lvgl.pid \
-x /usr/share/lvgl/lvgl_100ask_demo
[ $? = 0 ] && echo "OK" || echo "FAIL"
}
printf "Starting 100ask lvgl: "
:打印启动消息。echo -n -e '\e[?17;14;224c'
:设置控制台属性。这个转义序列用于配置终端(可能是设置光标形状或其他属性)。echo 0 > /sys/class/graphics/fbcon/cursor_blink
:禁用帧缓冲控制台的光标闪烁。start-stop-daemon -S -q -m -b -p /var/run/lvgl.pid -x /usr/share/lvgl/lvgl_100ask_demo
:-S
:启动一个新进程。-q
:安静模式,不输出信息。-m
:创建一个pid文件。-b
:在后台运行。-p /var/run/lvgl.pid
:指定pid文件路径。-x /usr/share/lvgl/lvgl_100ask_demo
:要执行的程序路径。
[ $? = 0 ] && echo "OK" || echo "FAIL"
:根据上一个命令的返回值($?
)判断启动是否成功,成功则打印"OK",失败则打印"FAIL"。
stop
函数
stop() {
printf "Stopping lvgl: "
start-stop-daemon -K -q -p /var/run/lvgl.pid \
-x /usr/share/lvgl/lvgl_100ask_demo
[ $? = 0 ] && echo "OK" || echo "FAIL"
}
printf "Stopping lvgl: "
:打印停止消息。start-stop-daemon -K -q -p /var/run/lvgl.pid -x /usr/share/lvgl/lvgl_100ask_demo
:-K
:停止一个进程。-q
:安静模式,不输出信息。-p /var/run/lvgl.pid
:指定pid文件路径。-x /usr/share/lvgl/lvgl_100ask_demo
:要停止的程序路径。
[ $? = 0 ] && echo "OK" || echo "FAIL"
:根据上一个命令的返回值($?
)判断停止是否成功,成功则打印"OK",失败则打印"FAIL"。
case
语句
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
stop
start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit $?
case "$1" in ... esac
:根据传入的参数决定执行哪个函数。start
:调用start
函数启动服务。stop
:调用stop
函数停止服务。restart|reload
:先调用stop
函数停止服务,再调用start
函数启动服务。*
:对于任何其他参数,打印使用说明并退出(exit 1
表示错误退出)。
exit $?
:以最后一个命令的退出状态结束脚本。
优雅关闭GUI和启动动画
至此,我们已经知道了IMX6ULL的整个启动原理,并了解到GUI和启动动画是如何启动的,那么我们就没必要粗暴的对这些文件进行删除或移动了。
我们可以使用/etc/init.d/S05lvgl stop
命令,当然这种方式的缺点是每次开机都要运行一遍,如果你希望每次开机启动的时候就关闭,那么可以注释掉S05lvgl
文件里面的运行LVGL脚本命令即可,或者使用service
命令,service
命令用于管理init启动的服务。
关闭启动动画也很简单,直接将/etc/init.d/rcS
脚本的psplash -n &
这一行注释掉即可,这样,每当我们启动开发板,就不会有启动动画和GUI出现了。
结语
这篇文章介绍了IMX6ULL的启动原理,但其实它也适用于其他类型开发板,原理都差不多。我觉得只有了解了原理,再来进行我们想要的操作,就会感觉尽在掌控之中,和不了解原理带来茫然的感觉相比,效果还是不一样的。如果你觉得这篇文章不错,请帮忙点赞、转发、收藏,谢谢支持~