Bootload U-Boot分析
Bootloader是在操作系统运行之前执行的一段小程序。通过这段小程序可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备。
对于嵌入式系统,Bootloader是基于特定硬件平台来实现的。因此,几乎不可能为所有的嵌入式系统建立一个通用的Bootloader,不同的处理器架构都有不同的Bootloader。Bootloader不但依赖于CPU的体系结构,而且依赖于嵌入式系统板级设备的配置。对于2块不同的嵌入式板而言,即使它们使用同一种处理器,要想让运行在一块板子上的Bootloader程序也能运行在另一块板子上,一般也都需要修改Bootloader的源程序。
反过来,大部分Bootloader仍然具有很多共性,某些Bootloader也能够支持多种体系结构的嵌入式系统。例如,U-Boot就同时支持PowerPC、ARM、MIPS和X86等体系结构,支持的板子有上百种。通常,它们都能够自动从存储介质上启动,都能够引导操作系统,并且大部分都可以支持串口和以太网接口。
1 Bootloader的引导方式
Linux系统是通过Bootloader引导启动的。加电后,就要执行Bootloader来初始化系统。
系统加电或复位后,所有CPU都会从某个地址开始执行,这是由处理器设计决定的。例如,X86的复位向量在高地址端,ARM处理器在复位时从地址0x00000000取第一条指令。嵌入式系统的开发板都要把板上ROM或Flash映射到这个地址。因此,必须把Bootloader程序存储在相应的Flash位置。系统加电后,CPU将首先执行它。
主机和目标机之间一般有串口可以连接,Bootloader软件通常会通过串口来输入输出。例如,输出出错或者执行结果信息到串口终端,从串口终端读取用户控制命令等。Bootloader启动过程通常是多阶段的,这样既能提供复杂的功能,又有很好的可移植性。例如,从Flash启动的Bootloader多数是两阶段的启动过程。从后面U-Boot的内容可以详细分析这个特性。
大多数Bootloader都包含2种不同的操作模式:本地加载模式和远程下载模式。这两种操作模式的区别仅对于开发人员才有意义,也就是不同启动方式的使用。从最终用户的角度看,Bootloader的作用就是用来加载操作系统,并不存在所谓的本地加载模式与远程下载模式的区别。
因为Bootloader的主要功能是引导操作系统启动,所以我们详细讨论一下各种启动方式的特点。
(1)网络启动方式
这种方式开发板不需要配置较大的存储介质,与无盘工作站有点类似。但是使用这种启动方式之前,需要把Bootloader安装到板上的EPROM或者Flash中。Bootloader通过以太网接口远程下载Linux内核映像或者文件系统。
使用这种方式也有前提条件,就是目标板需有串口、以太网接口或者其他连接方式。串口一般可以作为控制台,同时可以用来下载内核映像和RAMDISK文件系统。串口通信传输速率过低,不适合用来挂接NFS文件系统。所以以太网接口成为通用的互联设备,一般的开发板都可以配置10M以太网接口。
对于PDA等手持设备来说,以太网的RJ-45接口显得大了些,而USB接口,特别是USB的迷你接口,尺寸非常小。对于开发的嵌入式系统,可以把USB接口虚拟成以太网接口来通信。这种方式在开发主机和开发板两端都需要驱动程序。
另外,还要在服务器上配置启动相关网络服务。Bootloader下载文件一般都使用TFTP网络协议,还可以通过DHCP的方式动态配置IP地址。DHCP/BOOTP服务为Bootloader分配IP地址,配置网络参数,然后才能够支持网络传输功能。如果Bootloader可以直接设置网络参数,就可以不使用DHCP。TFTP服务为Bootloader客户端提供文件下载功能,把内核映像和其他文件放在/tftpboot目录下。这样Bootloader可以通过简单的TFTP协议远程下载内核映像到内存。大部分引导程序都能够支持网络启动方式。例如,BIOS的PXE(Preboot Execution Environment)功能就是网络启动方式;U-Boot也支持网络启动功能。
(2)磁盘启动方式
传统的Linux系统运行在台式机或者服务器上,这些计算机一般都使用BIOS引导,并且使用磁盘作为存储介质。如果进入BIOS设置菜单,可以探测处理器、内存、硬盘等设备,可以设置BIOS从软盘、光盘或者某块硬盘启动。也就是说,BIOS并不直接引导操作系统。那么在硬盘的主引导区,还需要一个Bootloader。这个Bootloader可以从磁盘文件系统中把操作系统引导起来。Linux传统上是通过LILO(LInux LOader)引导的,后来又出现了GNU的软件GRUB(GRand Unified Bootloader)。这2种Bootloader广泛应用在x86的Linux系统上。你的开发主机可能就使用了其中一种,熟悉它们有助于配置多种系统引导功能。
LILO软件工程是由Werner Almesberger创建,专门为引导Linux开发的。现在LILO的维护者是John Coffman(最新版本下载站点:http://lilo.go.dyndns.org)。LILO有详细的文档,如LILO套件中附带使用手册和参考手册。此外,还可以在LDP的“LILO mini-HOWTO”中找到LILO的使用指南。GRUB是GNU计划的主要Bootloader。GRUB最初是由Erich Boleyn为GNU Mach操作系统撰写的引导程序。后来由Gordon Matzigkeit和Okuji Yoshinori接替了Erich的工作,继续维护和开发GRUB。GRUB的网站(http://www.gnu.org/software/grub/)上有对套件使用的说明文件,叫做《GRUB manual》。GRUB能够使用TFTP和BOOTP或者DHCP通过网络启动,这种功能对于系统开发过程很有用。除了传统的Linux系统上的引导程序以外,还有其他一些引导程序,也可以支持磁盘引导启动。例如,LoadLin可以从DOS下启动Linux;还有ROLO、LinuxBIOS、U-Boot也支持这种功能。
(3)Flash启动方式
大多数嵌入式系统上都使用Flash存储介质。Flash有很多类型,包括NOR Flash、NAND Flash和其他半导体盘。其中,NOR Flash(也就是线性Flash)使用最为普遍。
NOR Flash可以支持随机访问,所以代码是可以直接在Flash上执行的。Bootloader一般是存储在Flash芯片上的。另外,Linux内核映像和RAMDISK也可以存储在Flash上。通常需要把Flash分区使用,每个区的大小应该是Flash擦除块大小的整数倍。Bootloader一般放在Flash的底端或者顶端,这要根据处理器的复位向量设置。首先,要使Bootloader的入口位于处理器加电执行第一条指令的位置。其次,分配参数区,这里可以作为Bootloader的参数保存区域。再次,内核映像区,Bootloader引导Linux内核,就是要从这个地方把内核映像解压到RAM中去,然后跳转到内核映像入口执行。最后,文件系统区,如果使用Ramdisk文件系统,则需要Bootloader把它解压到RAM中。如果使用JFFS2文件系统,将直接挂接为根文件系统。还可以分出一些数据区,这要根据实际需要和Flash大小来考虑了。这些分区是开发者定义的,Bootloader一般直接读写对应的偏移地址。到了Linux内核空间,可以配置成MTD设备来访问Flash分区。但是,有的Bootloader也支持分区的功能,例如,Redboot可以创建Flash分区表,并且内核MTD驱动可以解析出redboot的分区表。除了NOR Flash外,还有NAND Flash、Compact Flash、DiskOnChip等。这些Flash具有芯片价格低,存储容量大的特点。但是这些芯片一般通过专用控制器的I/O方式来访问,不能随机访问,因此引导方式跟NOR Flash也不同。在这些芯片上,需要配置专用的引导程序。通常,这种引导程序起始的一段代码就把整个引导程序复制到RAM中运行,从而实现自行启动,这与从磁盘上启动有些相似。
2 Bootloader的种类
嵌入式系统世界已经有各种各样的Bootloader,种类划分也有多种方式。除了按照处理器体系结构不同划分以外,还有功能复杂程度的不同。
首先区分一下“Bootloader”和“Monitor”的概念。严格来说,“Bootloader”只是引导设备并且执行主程序的固件;而“Monitor”还提供了更多的命令行接口,可以进行调试、读写内存、烧写Flash、配置环境变量等。“Monitor”在嵌入式系统开发过程中可以提供很好的调试功能,开发完成以后,就完全设置成了一个“Bootloader”。所以,习惯上大家把它们统称为Bootloader。
下列出了Linux的开放源码引导程序及其支持的体系结构。表中给出了x86 ARM PowerPC体系结构的常用引导程序,并且注明了每一种引导程序是不是“Monitor”。
对于每种体系结构,都有一系列开放源码Bootloader可以选用。
(1)x86
x86的工作站和服务器上一般使用LILO和GRUB。LILO是Linux发行版主流的Bootloader。不过Redhat Linux发行版已经使用了GRUB,GRUB比LILO有更友好的显示界面,使用配置也更加灵活方便。
在某些x86嵌入式单板机或者特殊设备上,会采用其他Bootloader,如ROLO。这些Bootloader可以取代BIOS的功能,能够从Flash中直接引导Linux启动。现在ROLO支持的开发板已经并入U-Boot,所以U-Boot也可以支持x86平台。
(2)ARM
ARM处理器的芯片商很多,所以每种芯片的开发板都有自己的Bootloader。结果ARM bootloader也变得多种多样。最早有为ARM720处理器的开发板的固件,又有了armboot和StrongARM平台的BLOB,还有S3C2410处理器开发板上的vivi等。现在armboot已经并入了U-Boot,所以U-Boot也支持ARM/XSCALE平台。U-Boot已经成为ARM平台事实上的标准Bootloader。
(3)PowerPC
PowerPC平台的处理器有标准的Bootloader,就是ppcboot。ppcboot在合并armboot等之后,创建了U-Boot,成为各种体系结构开发板的通用引导程序。U-Boot仍然是PowerPC平台的主要Bootloader。
(4)MIPS
MIPS公司开发的YAMON是标准的Bootloader,也有许多MIPS芯片商为自己的开发板写了Bootloader。现在,U-Boot也已经支持MIPS平台。
(5)SH
SH平台的标准Bootloader是sh-boot。Redboot在这种平台上也很好用。
(6)M68K
M68K平台没有标准的Bootloader。Redboot能够支持M68K系列的系统。
值得说明的是Redboot,它几乎能够支持所有的体系结构,包括MIPS、SH、M68K等体系结构。Redboot是以eCos为基础,采用GPL许可的开源软件工程。现在由core eCos的开发人员维护(源码下载网站:http://www.ecoscentric.com/snapshots)。Redboot的文档也相当完善,有详细的使用手册《RedBoot User’s Guide》。
3 U-Boot源码结构
最早,DENX软件工程中心的Wolfgang Denk基于8xxrom的源码创建了ppcboot工程,并且不断添加处理器的支持。后来,Sysgo Gmbh把ppcboot移植到ARM平台上,创建了armboot工程。然后以ppcboot工程和armboot工程为基础,创建了U-Boot工程。
现在U-Boot已经能够支持PowerPC、ARM、x86、MIPS体系结构的上百种开发板,已经成为功能最多、灵活性最强并且开发最积极的开放源码Bootloader。目前仍然由DENX的Wolfgang Denk维护(U-Boot软件包下载网站:http://sourceforge.net/project/u-boot。还可以订阅该网站活跃的U-Boot Users邮件论坛,这个邮件论坛对于U-Boot的开发和使用都很有帮助)。
从网站上下载得到U-Boot源码包,如U-Boot-1.1.6.tar.bz2。解压就可以得到全部U-Boot源程序。在顶层目录下有18个子目录,分别存放和管理不同的源程序。这些目录中所要存放的文件有一定规则,可以分为3类。
(1)第1类目录与处理器体系结构或者开发板硬件直接相关。
(2)第2类目录是一些通用的函数或者驱动程序。
(3)第3类目录是U-Boot的应用程序、工具或者文档。

U-Boot的源代码包含对几十种处理器、数百种开发板的支持。可是对于特定的开发板,配置编译过程只需要其中部分程序。这里具体以S3C2440 ARM920t处理器为例,具体分析S3C2440处理器和开发板所依赖的程序,以及U-Boot的通用函数和工具。
4 U-Boot的编译
U-Boot的源码是通过GCC和Makefile组织编译的。顶层目录下的Makefile首先可以设置开发板的定义,然后递归地调用各级子目录下的Makefile,最后把编译过的程序链接成U-Boot映像。
(1)顶层目录下的Makefile
它负责U-Boot整体配置编译。按照配置的顺序阅读其中关键的几行。
每一种开发板在Makefile都需要有板子配置的定义。例如,SMDK2440开发板的定义如下:
smdk2440_config : unconfig@$(MKCONFIG)$(@:_config=)arm s3c24xx smdk2440 samsung s3c2440
执行配置U-Boot的命令make smdk2440_config,通过./mkconfig脚本生成include/config. mk的配置文件。文件内容正是根据Makefile对开发板的配置生成的。
ARCH =armCPU =s3c24xxBOARD =smdk2440VENDOR= sumsangSOC =s3c2440
上面的include/config.mk文件定义了ARCH、CPU、BOARD、SOC这些变量。这样硬件平台依赖的目录文件可以根据这些定义来确定。SMDK2440平台相关目录如下。
board/samsung/smdk2440/cpu/arm920t/cpu/arm920t/s3c24x0/lib_arm/include/asm-arm/include/configs/smdk2440.h
再回到顶层目录的Makefile文件开始的部分,其中下列几行包含了这些变量的定义。
# load ARCH, BOARD, and CPU configurationinclude include/config.mkexport ARCH CPU BOARD VENDOR SOC
Makefile的编译选项和规则在顶层目录的config.mk文件中定义。各种体系结构通用的规则直接在这个文件中定义。通过ARCH、CPU、BOARD、SOC等变量为不同硬件平台定义不同选项。不同体系结构的规则分别包含在ppc_config.mk、arm_config.mk、mips_config.mk等文件中。
顶层目录的Makefile中还要定义交叉编译器,以及编译U-Boot所依赖的目标文件。
ifeq ($(ARCH),arm)
CROSS_COMPILE=arm-linux- //交叉编译器的前缀
#endif
export CROSS_COMPILE
…
# U-Boot objects....order is important (i.e. start must be first)
OBJS =cpu/$(CPU)/start.o //处理器相关的目标文件
…
LIBS =lib_generic/libgeneric.a //定义依赖的目录,每个目录下先把目标文件连接成*.a文件。
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
endif
LIBS += lib_$(ARCH)/lib$(ARCH).a
…
然后还有U-Boot映像编译的依赖关系。
ALL = u-boot.srec u-boot.bin System.map
all: $(ALL)
u-boot.srec: u-boot$(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
u-boot.bin: u-boot$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
…
u-boot: depend$(SUBDIRS)$(OBJS)$(LIBS)$(LDSCRIPT)UNDEF_SYM='$(OBJDUMP) -x $(LIBS) /|sed -n –e 's/.*/(__u_boot_cmd_.*/)/-u/1/p'|sort|
uniq`;/$(LD) $(LDFLAGS) $$UNDEF_SYM $(OBJS) /--start-group $(LIBS) $(PLATFORM_LIBS) --end-group /-Map u-boot.map -o u-boot
Makefile默认的编译目标为all,包括u-boot.srec、u-boot.bin、System.map。u-boot.srec和u-boot.bin又依赖于U-Boot。U-Boot就是通过ld命令按照u-boot.map地址表把目标文件组装成U-Boot。
其他Makefile内容就不再详细分析了,上述代码分析应该可以为阅读代码提供了一个线索。
(2)开发板配置头文件
除了编译过程Makefile以外,还要在程序中为开发板定义配置选项或者参数。这个头文件是include/configs/<board_name>.h。<board_name>用相应的BOARD定义代替。
这个头文件中主要定义了两类变量。
一类是选项,前缀是CONFIG_,用来选择处理器、设备接口、命令、属性等。例如:
ifndef __CONFIG_H
#define __CONFIG_H
/*
* *High Level Configuration Options
* *(easy to change)
* */
#define CONFIG_ARM920T 1 /*This is an ARM920EJS Core */
#define CONFIG_S3C2440 1 /*in a SAMSUNG S3C24A0 SoC */
#define CONFIG_S3C24XX 1 /*in a SAMSUNG S3C24XX Family */
#define CONFIG_SMDK2440 1 /*on a SAMSUNG SMDK2440 Board */
/* input clock of PLL */
#ifdef KINGFISH
#define CONFIG_SYS_CLK_FREQ 12000000
#else
#define CONFIG_SYS_CLK_FREQ 16934400
#endif
另一类是参数,前缀是CFG_,用来定义总线频率、串口波特率、Flash地址等参数。例如:
#define CFG_LONGHELP /*undef to save memory */
#define CFG_PROMPT "SMDK2440#" /*Monitor Command Prompt */
#define CFG_CBSIZE 256 /*Console I/O Buffer Size */
#define CFG_PBSIZE (CFG_CBSIZE+sizeof(CFG_PROMPT)+16) /* Print Buffer Size */
#define CFG_MAXARGS 16 /*max number of command args */
#define CFG_BARGSIZE CFG_CBSIZE /*Boot Argument Buffer Size */
#define CFG_MEMTEST_START 0x10000000 /*memtest works on*/
#define CFG_MEMTEST_END 0x13e00000 /*63 MB in DRAM */#endif
(3)编译结果
根据对Makefile的分析,编译分为两步。第1步配置,如make smdk2440_config;第2步编译,执行make就可以了。
编译完成后,可以得到U-Boot各种格式的映像文件和符号表

U-Boot的3种映像格式都可以烧写到Flash中,但需要看加载器能否识别这些格式。一般u-boot.bin最为常用,直接按照二进制格式下载,并且按照绝对地址烧写到Flash中就可以了。u-boot和u-boot.srec格式映像都自带定位信息。
(4)U-Boot工具
在tools目录下还有些U-Boot的工具,这些工具有的也经常用到,表6.4说明了几种工具的用途。

这些工具都有源代码,可以参考改写其他工具。其中mkimage是很常用的一个工具,Linux内核映像和ramdisk文件系统映像都可以转换成U-Boot的格式。
5 U-Boot的移植
U-Boot能够支持多种体系结构的处理器,支持的开发板也越来越多。因为Bootloader是完全依赖硬件平台的,所以在新电路板上需要移植U-Boot程序。开始移植U-Boot之前,先要熟悉硬件电路板和处理器。确认U-Boot是否已经支持新开发板的处理器和I/O设备。假如U-Boot已经支持一块非常相似的电路板,那么移植的过程将非常简单。移植U-Boot工作就是添加开发板硬件相关的文件、配置选项,然后配置编译。
开始移植之前,需要先分析一下U-Boot已经支持的开发板,比较出硬件配置最接近的开发板。选择的原则是:首先处理器相同,其次处理器体系结构相同,然后是以太网接口等外围接口。还要验证一下这个参考开发板的U-Boot,至少能够配置编译通过。
以S3C2440处理器的开发板为例,U-Boot-1.1.6版本已经支持SMDK2440开发板。我们可以基于SMDK2440移植,那么先把SMDK2440编译通过。以S3C2440开发板etc2410为例说明。移植的过程参考SMDK2440开发板,SMDK2440在U-Boot-1.1.6中已经支持。
移植U-Boot的基本步骤如下。
(1)在顶层Makefile中为开发板添加新的配置选项,使用已有的配置项目为例。
smdk2440_config : unconfig@$(MKCONFIG)$(@:_config=)arm s3c24xx smdk2440 samsung s3c2440
参考上面两行,添加下面两行。
etc2440_config : unconfig@$(MKCONFIG)$(@:_config=)arm s3c24xx smdk2440 samsung s3c2440
(2)创建一个新目录存放开发板相关的代码,并且添加文件。
board/etc2440/config.mk
board/etc2440/flash.c
board/etc2440/fs2410.c
board/etc2440/Makefile
board/etc2440/memsetup.S
board/etc2440/u-boot.lds
(3)为开发板添加新的配置文件
可以先复制参考开发板的配置文件再修改。例如:
$cp include/configs/smdk2440.h include/configs/etc2440.h
如果是为一个新的CPU移植,还要创建一个新的目录存放CPU相关的代码。
(4)配置开发板
$ make etc2410_config
(5)编译U-Boot
执行make命令,编译成功可以得到U-Boot映像。有些错误是与配置选项有关系的,通常打开某些功能选项会带来一些错误,一开始可以尽量参考板配置相同。
(6)添加驱动或者功能选项
在能够编译通过的基础上,还要实现U-Boot的以太网接口、Flash擦写等功能。
对于etc2410开发板的以太网驱动和smdk2440完全相同,所以可以直接使用。CS8900驱动程序文件如下。
drivers/cs8900.c
drivers/cs8900.h
对于Flash的选择就麻烦多了,Flash芯片价格或者采购方面的因素都有影响。多数开发板大小、型号都不相同。所以还需要移植Flash的驱动。每种开发板目录下一般都有flash.c文件,需要根据具体的Flash类型修改。例如:
board/etc2440/flash.c
(7)调试U-Boot源代码,直到U-Boot在开发板上能够正常启动。
调试的过程可能是很艰难的,需要借助工具,并且有些问题可能会困扰很长时间。
6 添加U-Boot命令
U-Boot的命令为用户提供了交互功能,并且已经实现了几十个常用的命令。如果开发板需要很特殊的操作,可以添加新的U-Boot命令。
U-Boot的每一个命令都是通过U_Boot_CMD宏定义的。这个宏在include/command.h头文件中定义,每一个命令定义一个cmd_tbl_t结构体。
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) /
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
这样每一个U-Boot命令用一个结构体来描述。结构体包含的成员变量:命令名称、最大参数个数、重复数、命令执行函数、用法、帮助。从控制台输入的命令是由common/command.c中的程序解释执行的。find_cmd()负责匹配输入的命令,从列表中找出对应的命令结构体。
基于U-Boot命令的基本框架,来分析一下简单的icache操作命令,就可以知道添加新命令的方法。
(1)定义CACHE命令。
在include/cmd_confdefs.h中定义了所有U-Boot命令的标志位。
#define CFG_CMD_CACHE 0x00000010ULL /*icache,dcache */
如果有更多的命令,也要在这里添加定义。
(2)实现CACHE命令的操作函数。
下面是common/cmd_cache.c文件中icache命令部分的代码。
#if (CONFIG_COMMANDS & CFG_CMD_CACHE)
static int on_off (const char *s)
{ //这个函数解析参数,判断是打开cache,还是关闭cacheif(strcmp(s,"on")==0){ //参数为“on”return (1);}else if(strcmp(s,"off")==0){ //参数为“off”return (0);}return (-1);
}
int do_icache ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{ //对指令cache的操作函数switch(argc){case 2: /* 参数个数为1,则执行打开或者关闭指令cache操作 */switch(on_off(argv[1])){case 0: icache_disable(); //打开指令cachebreak;case 1: icache_enable(); //关闭指令cachebreak;}/*FALL TROUGH*/case 1: /* 参数个数为0,则获取指令cache状态*/printf("Instruction Cache is %s/n",icache_status() ? "ON":"OFF");return 0;default: //其他默认情况下,打印命令使用说明printf("Usage:/n%s/n",cmdtp->usage);return 1;}return 0;
}
……
U_Boot_CMD( //通过宏定义命令icache, 2, 1, do_icache, //命令为icache,命令执行函数为do_icache()"icache -enable or disable instruction cache/n", //帮助信息"[on, off]/n"" -enable or disable instruction cache/n"
);
…
#endif
U-Boot的命令都是通过结构体__U_Boot_cmd_##name来描述的。根据U_Boot_CMD在include/command.h中的两行定义可以明白。
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) /
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
还有,不要忘了在common/Makefile中添加编译的目标文件。
(3)打开CONFIG_COMMANDS选项的命令标志位。
这个程序文件开头有#if语句需要预处理是否包含这个命令函数。CONFIG_COMMANDS选项在开发板的配置文件中定义。例如,SMDK2440平台在include/configs/smdk2440.h中有如下定义。
/************************************************************ Command definition***********************************************************/#define CONFIG_COMMANDS /(CONFIG_CMD_DFL |/CFG_CMD_CACHE |/CFG_CMD_REGINFO |/CFG_CMD_DATE |/CFG_CMD_ELF)
按照上面3个步骤,就可以添加新的U-Boot命令。
7 U-Boot的调试
新移植的U-Boot如果不能正常工作,这时就需要调试了。调试U-Boot离不开工具,只有理解U-Boot启动过程,才能正确地调试U-Boot源代码。
1.硬件调试器
硬件电路板制作完成以后,这时上面还没有任何程序,称为裸板。首要的工作是把程序或者固件加载到裸板上,这就要通过硬件工具来完成。习惯上把这种硬件工具称为仿真器。
仿真器可以通过处理器的JTAG等接口控制板子,直接把程序下载到目标板内存中,或者进行Flash编程。如果板上的Flash是可以插拔的,就可以通过专用的Flash烧写器来完成。其中JTAG等接口就是专门用来连接仿真器的。
仿真器还有一个重要的功能就是在线调试程序,这对于调试Bootloader和硬件测试程序很有用。从最简单的JTAG电缆,到ICE仿真器,再到可以调试Linux内核的仿真器。复杂的仿真器可以支持与计算机间的以太网或者USB接口通信,下载U-Boot到目标板内存中。通过BDI2000的下载命令LOAD,把程序加载到目标板内存中;然后跳转到U-Boot入口。
2.U-Boot启动过程
尽管有了调试跟踪手段,甚至也可以通过串口打印信息了,但是不一定能够准确判断出错原因。如果能够充分理解代码的启动流程,那么对准确地解决和分析问题很有帮助。
开发板加电后,执行U-Boot的第一条指令,然后按顺序执行U-Boot启动函数。看一下board/smsk2410/u-boot.lds这个链接脚本,可以知道目标程序的各部分链接顺序。第一个要链接的是cpu/arm920t/start.o,那么U-Boot的入口指令一定位于这个程序中。下面详细分析一下程序跳转和函数的调用关系,以及函数实现。
(1)cpu/arm920t/start.S
这个汇编程序是U-Boot的入口程序,开头就是复位向量的代码。
_start:b reset //复位向量ldr pc,_undefined_instructionldr pc,_software_interruptldr pc,_prefetch_abortldr pc,_data_abortldr pc,_not_usedldr pc,_irq //中断向量ldr pc,_fiq //中断向量
…
/*the actual reset code */
reset: //复位启动子程序/* 设置CPU为SVC32模式 */mrs r0,cpsrbic r0,r0,#0x1forr r0,r0,#0xd3msr cpsr,r0
/* 关闭看门狗 */
/* 这些初始化代码在系统重新启动时执行,运行时热复位从RAM中启动不执行 */
#ifdef CONFIG_INIT_CRITICALbl cpu_init_crit
#endif
relocate: /* 把U-Boot重新定位到RAM*/adr r0,_start /*r0是代码的当前位置 */ldr r1,_TEXT_BASE /* 测试判断是从Flash启动,还是从RAM启动*/cmp r0,r1 /* 比较r0和r1,调试的时候不要执行重定位 */beq stack_setup /* 如果r0等于r1,跳过重定位代码 *//* 准备重新定位代码 */ldr r2,_armboot_startldr r3,_bss_startsub r2,r3,r2 /*r2 得到armboot的大小 */add r2,r0,r2 /*r2 得到要复制代码的末尾地址 */
copy_loop:/* 重新定位代码 */ldmia r0!,{r3-r10} /*从源地址[r0]复制 */stmia r1!,{r3-r10} /* 复制到目的地址[r1]*/cmp r0,r2 /* 复制数据块直到源数据末尾地址[r2]*/ble copy_loop/* 初始化堆栈等 */
stack_setup:ldr r0,_TEXT_BASE /* 上面是128 KB重定位的u-boot*/sub r0,r0,#CFG_MALLOC_LEN /* 向下是内存分配空间 */sub r0,r0,#CFG_GBL_DATA_SIZE/* 然后是bdinfo结构体地址空间 */
#ifdef CONFIG_USE_IRQsub r0,r0,#(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endifsub sp,r0,#12 /* 为abort-stack预留3个字 */
clear_bss:ldr r0,_bss_start /* 找到bss段起始地址 */ldr r1,_bss_end /* bss段末尾地址 */mov r2,#0x00000000 /* 清零 */
clbss_l:str r2,[r0] /*bss段地址空间清零循环... */add r0,r0,#4cmp r0,r1bne clbss_l/* 跳转到start_armboot函数入口,_start_armboot字保存函数入口指针 */ldr pc,_start_armboot
_start_armboot:.word start_armboot //start_armboot函数在lib_arm/board.c中实现
/* 关键的初始化子程序 */
cpu_init_crit:
…… //初始化CACHE,关闭MMU等操作指令/* 初始化RAM时钟。* 因为内存时钟是依赖开发板硬件的,所以在board的相应目录下可以找到
memsetup.S文件。*/mov ip,lrbl memsetup //memsetup子程序在board/smdk2410/memsetup.S中实现mov lr,ipmov pc,lr
(2)lib_arm/board.c
start_armboot是U-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。
void start_armboot (void)
{DECLARE_GLOBAL_DATA_PTR;ulong size;init_fnc_t **init_fnc_ptr;char *s;/* Pointer is writable since we allocated a register for it */gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));/* compiler optimization barrier needed for GCC >= 3.4 */__asm__ __volatile__("": : :"memory");memset ((void*)gd, 0, sizeof (gd_t));gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));memset (gd->bd, 0, sizeof (bd_t));monitor_flash_len = _bss_start - _armboot_start;/* 顺序执行init_sequence数组中的初始化函数 */for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {if ((*init_fnc_ptr)() != 0) {hang ();}}/*配置可用的Flash */size = flash_init ();display_flash_config (size);/*_armboot_start在u-boot.lds链接脚本中定义 */mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);/* 配置环境变量,重新定位 */env_relocate ();/* 从环境变量中获取IP地址 */gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");/* 以太网接口MAC地址 */……devices_init(); /* 获取列表中的设备 */jumptable_init ();console_init_r(); /* 完整地初始化控制台设备 */enable_interrupts();/* 使能例外处理 *//* 通过环境变量初始化 */if ((s = getenv ("loadaddr")) != NULL) {load_addr = simple_strtoul (s, NULL, 16);}/*main_loop()总是试图自动启动,循环不断执行 */for (;;) {main_loop(); /* 主循环函数处理执行用户命令 --common/main.c*/}/* NOTREACHED - no way out of command loop except booting */
}
(3)init_sequence[]
init_sequence[]数组保存着基本的初始化函数指针。这些函数名称和实现的程序文件在下列注释中。
init_fnc_t *init_sequence[] = {cpu_init, /* 基本的处理器相关配置 --cpu/arm920t/cpu.c*/board_init, /* 基本的板级相关配置--board/smdk2410/smdk2410.c*/interrupt_init, /* 初始化例外处理--cpu/arm920t/s3c24x0/interrupt.c */env_init, /* 初始化环境变量 --common/cmd_flash.c*/init_baudrate, /* 初始化波特率设置 --lib_arm/board.c*/serial_init, /* 串口通信设置 --cpu/arm920t/s3c24x0/serial.c*/console_init_f, /* 控制台初始化阶段1--common/console.c*/display_banner, /* 打印u-boot信息 --lib_arm/board.c*/dram_init, /* 配置可用的RAM--board/smdk2410/smdk2410.c*/display_dram_config,/* 显示RAM的配置大小 --lib_arm/board.c*/NULL;
};
8 U-Boot与内核的关系
U-Boot作为Bootloader,具备多种引导内核启动的方式。常用的go和bootm命令可以直接引导内核映像启动。U-Boot与内核的关系主要是内核启动过程中参数的传递。
(1)go命令的实现
/*common/cmd_boot.c */
int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{ulong addr, rc;int rcode=0;if (argc < 2) {printf("Usage:/n%s/n",cmdtp->usage);return 1;}addr = simple_strtoul(argv[1], NULL, 16);printf ("## Starting application at 0x%08lX .../n", addr);/** pass address parameter as argv[0] (aka command name),* and all remaining args*/rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);if (rc != 0) rcode = 1;printf ("## Application terminated, rc = 0x%lX/n", rc);return rcode;
};
go命令调用do_go()函数,跳转到某个地址执行的。如果在这个地址准备好了自引导的内核映像,就可以启动了。尽管go命令可以带变参,但实际使用时一般不用来传递参数。
(2)bootm命令的实现
/* common/cmd_bootm.c */
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{ulong iflag;ulong addr;ulong data, len, checksum;ulong *len_ptr;uint unc_len=0x400000;int i,verify;char *name,*s;int (*appl)(int,char*[]);image_header_t *hdr = &header;s = getenv ("verify");verify=(s&&(*s=='n')) ? 0:1;if (argc < 2) {addr=load_addr;} else {addr=simple_strtoul(argv[1],NULL,16);}SHOW_BOOT_PROGRESS (1);printf ("## Booting image at %08lx .../n", addr);/* Copy header so we can blank CRC field for re-calculation */memmove (&header, (char *)addr, sizeof(image_header_t));if (ntohl(hdr->ih_magic) != IH_MAGIC){puts("Bad Magic Number/n");SHOW_BOOT_PROGRESS(-1);return 1;}SHOW_BOOT_PROGRESS (2);data = (ulong)&header;len =sizeof(image_header_t);checksum = ntohl(hdr->ih_hcrc);hdr->ih_hcrc = 0;if(crc32 (0, (char *)data, len) != checksum) {puts("Bad Header Checksum/n");SHOW_BOOT_PROGRESS(-2);return 1;}SHOW_BOOT_PROGRESS (3);/* for multi-file images we need the data part, too */print_image_hdr ((image_header_t *)addr);data = addr + sizeof(image_header_t);len =ntohl(hdr->ih_size);if(verify) {puts(" Verifying Checksum...");if(crc32(0,(char*)data,len)!=ntohl(hdr->ih_dcrc)){printf ("Bad Data CRC/n");SHOW_BOOT_PROGRESS (-3);return 1;}puts("OK/n");}SHOW_BOOT_PROGRESS (4);len_ptr = (ulong *)data;…switch (hdr->ih_os) {default: /*handled by(original)Linux case*/case IH_OS_LINUX:do_bootm_linux (cmdtp,flag,argc,argv,addr, len_ptr, verify);break;…
}
bootm命令调用do_bootm函数,这个函数专门用来引导各种操作系统映像,可以支持引导Linux、vxWorks、QNX等操作系统。引导Linux的时候,调用do_bootm_linux()函数。
(3)do_bootm_linux()函数的实现
/* lib_arm/armlinux.c */
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],ulong addr, ulong *len_ptr, int verify)
{DECLARE_GLOBAL_DATA_PTR;ulong len = 0, checksum;ulong initrd_start, initrd_end;ulong data;void (*theKernel)(int zero, int arch, uint params);image_header_t *hdr = &header;
…
}
do_bootm_linux()函数是专门引导Linux映像的函数,它还可以处理ramdisk文件系统的映像。这里引导的内核映像和ramdisk映像,必须是U-Boot格式的。U-Boot格式的映像可以通过mkimage工具来转换,其中包含了U-Boot可以识别的符号。
9 U-Boot的常用命令
U-Boot加电启动后,按任意键可以退出自动启动状态,进入命令行。
U-Boot 1.1.6 (Apr 262010-12:27:13)
U-Boot code:11080000->1109614C BSS:->1109A91C
RAM Configuration:
Bank #0: 10000000 32 MB
Micron StrataFlash MT28F128J3 device initialized
Flash: 32 MB
In: serial
Out: serial
Err: serial
Hit any key to stop autoboot: 0
U-Boot>
在命令行提示符下,可以输入U-Boot的命令并执行。U-Boot可以支持几十个常用命令,通过这些命令,可以对开发板进行调试,可以引导Linux内核,还可以擦写Flash完成系统部署等功能。掌握这些命令的使用,才能够顺利地进行嵌入式系统的开发。
输入help命令,可以得到当前U-Boot的所有命令列表。每一条命令后面是简单的命令说明。
=> help
? -alias for'help'
autoscr - run script from memory
base -print or set address offset
bdinfo -print Board Info structure
boot -boot default,i.e.,run'bootcmd'
bootd -boot default,i.e.,run'bootcmd'
bootm -boot application image from memory
…
U-Boot还提供了更加详细的命令帮助,通过help命令还可以查看每个命令的参数说明。由于开发过程的需要,有必要先把U-Boot命令的用法弄清楚。接下来,根据每一条命令的帮助信息,解释一下这些命令的功能和参数。
=> help bootm
bootm [addr [arg ...]]- boot application image stored in memorypassing arguments 'arg ...'; when booting a Linux kernel,'arg' can be the address of an initrd image
bootm命令可以引导启动存储在内存中的程序映像。这些内存包括RAM和可以永久保存的Flash。第1个参数addr是程序映像的地址,这个程序映像必须转换成U-Boot的格式。
第2个参数对于引导Linux内核有用,通常作为U-Boot格式的RAMDISK映像存储地址;也可以是传递给Linux内核的参数(默认情况下传递bootargs环境变量给内核)。
=> help bootpbootp [loadAddress] [bootfilename]
bootp命令通过bootp请求,要求DHCP服务器分配IP地址,然后通过TFTP协议下载指定的文件到内存。
第1个参数是下载文件存放的内存地址。
第2个参数是要下载的文件名称,这个文件应该在开发主机上准备好。
=> help cpcp [.b, .w, .l] source target count- copy memory
cp命令可以在内存中复制数据块,包括对Flash的读写操作。
第1个参数source是要复制的数据块起始地址。
第2个参数target是数据块要复制到的地址。这个地址如果在Flash中,那么会直接调用写Flash的函数操作,所以U-Boot写Flash就使用这个命令,当然需要先把对应Flash区域内容清除。
第3个参数count是要复制的数目,根据cp.b、cp.w、cp.l分别以字节、字、长字为单位。
=> help echoecho [args..]- echo args to console; /c suppresses newline
echo命令回显参数。
=> help eraseerase start end- erase FLASH from addr 'start' to addr 'end'erase N:SF[-SL]- erase sectors SF-SL in FLASH bank # Nerase bank N- erase FLASH bank # Nerase all- erase all FLASH banks
erase命令可以擦除Flash。
参数必须指定Flash擦除的范围,按照起始地址和结束地址,start必须是擦除块的起始地址;end必须是擦除末尾块的结束地址。这种方式最常用。例如,擦除0x20000 – 0x3ffff区域命令为erase 20000 3ffff。按照组和扇区,N表示Flash的组号,SF表示擦除起始扇区号,SL表示擦除结束扇区号。另外,还可以擦除整个组,擦除组号为N的整个Flash组。擦除全部Flash只要给出一个all的参数即可。
=> help gogo addr [arg ...]- start application at address 'addr'passing 'arg' as arguments
go命令可以执行应用程序。
第1个参数是要执行程序的入口地址。
第2个可选参数是传递给程序的参数,可以不用。
=> help iminfoiminfo addr [addr ...]- print header information for application image starting ataddress 'addr' in memory; this includes verification of theimage contents (magic number, header and payload checksums)
iminfo可以打印程序映像的开头信息,包含了映像内容的校验(序列号、头和校验和)。
第1个参数指定映像的起始地址。
第2个可选的参数是指定更多的映像地址。
=> help loadbloadb [ off ] [ baud ]- load binary file over serial line with offset 'off' and baudrate 'baud'
loadb命令可以通过串口线下载二进制格式文件。
=> help loadsloads [ off ]- load S-Record file over serial line with offset 'off'
nfs命令可以使用NFS网络协议通过网络启动映像。
=> help printenvprintenv- print values of all environment variablesprintenv name ...- print value of environment variable 'name'
printenv命令打印环境变量。
可以打印全部环境变量,也可以只打印参数中列出的环境变量。
=> help runrun var [...]- run the commands in the environment variable(s) 'var'
run命令可以执行环境变量中的命令,后面参数可以跟几个环境变量名。
=> help setenvsetenv name value ...- set environment variable 'name' to 'value ...'setenv name- delete environment variable 'name'
setenv命令可以设置环境变量。
第1个参数是环境变量的名称。
第2个参数是要设置的值,如果没有第2个参数,表示删除这个环境变量。
=> help sleepsleep N- delay execution for N seconds (N is _decimal_ !!!)
sleep命令可以延迟N秒钟执行,N为十进制数。
=> help tftpboottftpboot [loadAddress] [bootfilename]
tftpboot命令可以使用TFTP协议通过网络下载文件。按照二进制文件格式下载。另外使用这个命令,必须配置好相关的环境变量,如serverip和ipaddr。
第1个参数loadAddress是下载到的内存地址。
第2个参数是要下载的文件名称,必须放在TFTP服务器相应的目录下。
这些U-Boot命令为嵌入式系统提供了丰富的开发和调试功能。在Linux内核启动和调试过程中,都可以用到U-Boot的命令。但是一般情况下,不需要使用全部命令。例如,已经支持以太网接口,可以通过tftpboot命令来下载文件,那么还有必要使用串口下载的loadb吗?反过来,如果开发板需要特殊的调试功能,也可以添加新的命令。
10 U-Boot的环境变量
与Shell类似,U-Boot也使用环境变量。可以通过printenv命令查看环境变量的设置。
U-Boot> printenv
bootdelay=3
baudrate=115200
netmask=255.255.0.0
ethaddr=12:34:56:78:90:ab
bootfile=uImage
bootargs=console=ttyS0,115200 root=/dev/ram rw initrd=0x30800000,8M
bootcmd=tftp 0x30008000 zImage;go 0x30008000
serverip=192.168.1.1
ipaddr=192.168.1.100
stdin=serial
stdout=serial
stderr=serial
Environment size: 337/131068 bytes
U-Boot>
U-Boot环境变量的解释说明

U-Boot的环境变量都可以有默认值,也可以修改并且保存在参数区。U-Boot的参数区一般有EEPROM和Flash两种设备。
环境变量的设置命令为setenv,在此以NFS和Nandflash两种启动的方式来说明一下。
(1)在启动内核之前需要设置启动参数,可以使用setenv bootargs命令完成。例如,下面的启动参数是使用nfs方式挂载文件系统的内核启动参数:
# setenv bootargs noinitrd root=/dev/nfs console=ttySAC0 init=/linuxrc
nfsroot=192.168.1.10:/nfsboot
ip=192.168.1.20:192.168.1.10:192.168.1.1:255.255.255.0::eth0:on
这是以nfs作为根文件系统的启动参数,其中192.168.1.20是板子IP;192.168.1.10是服务器IP,192.168.1.1是网关IP,255.255.255.0是掩码。可以根据自己网络的实际情况更换合适的IP和nfs目录。使用nfs作为根文件系统对于程序的调试是非常有意义的,这样板子的根文件系统可以放在开发主机上(Red Hat或Ubuntu),不必每次文件系统更新后都要重新烧写Flash。
(2)如果程序调试已经结束,需要把文件系统烧写到板子的Nandflash中,并让内核到Nandflash中挂载文件系统,那么需要使用下面的启动参数:
#setenv bootargs noinitrd root=/dev/mtdblock2 console=ttySAC0 init=/linuxrc
这是以nand中的mtdblock2分区作为根文件系统的启动参数,这里默认情况下使用的是Yaffs2格式的文件系统。
上面定义的环境变量有serverip ipaddr rootpath bootargs kernel_addr。环境变量bootargs中还使用了环境变量,bootargs定义命令行参数,通过bootm命令传递给内核。环境变量nfscmd中也使用了环境变量,功能是把uImage下载到指定的地址并且引导起来。可以通过run命令执行nfscmd脚本。
(3)U-Boot自支运行命令
U-Boot在板子复位后,如果用户没有在命令界面中按下任意键,那么U-Boot会自动运行某些命令。比如下图中,如果用户在倒计时结束之前没有按下按键那么U-Boot自动从Nandflash中复制数据到内存,并跳转到内存中启动内核。
对于这个复位后自动运行的动作实际上是可以自定义的。比如说,要实现这样的动作:让U-Boot在复位后自动通过TFTP从主机下载内核文件zImage到内存中并启动内核的,那么可以这样设置:
# setenv bootcmd "tftp c0008000 zImage;bootm c0008000"
个人比较喜欢这样设置,因为这样设置在内核改动后不需要重新烧写到Nandflash中,直接放到Red Hat或Ubuntu的tftp共享目录下,由uboot下载到内存中直接启动,不需要经过nand烧写。设置完毕后输入saveenv保存一下,重新启动板子后设置生效。
当内核调试完毕,内核需要烧写到Nandflash中,这时U-Boot的自动运行动作就变成了从Nandflash中读取内核到内存,并跳转到内存启动内核。这样就需要如下设置:
# setenv bootcmd "nand read c000800040000 3c0000;bootm c0008000"
11 使用U-Boot
U-Boot是“Monitor”。除了Bootloader的系统引导功能,它还有用户命令接口,提供了一些复杂的调试、读写内存、烧写Flash、配置环境变量等功能。掌握U-Boot的使用,将极大地方便嵌入式系统的开发。
新开发的电路板没有任何程序可以执行,也就不能启动,需要先将U-Boot烧写到Flash中。
如果主板上的EPROM或者Flash能够取下来,就可以通过编程器烧写。例如,计算机BIOS就存储在一块256KB的Flash上,通过插座与主板连接。但是多数嵌入式单板使用贴片的Flash,不能取下来烧写。这种情况可以通过处理器的调试接口,直接对板上的Flash编程。
处理器调试接口是为处理器芯片设计的标准调试接口,包含BDM、JTAG和EJTAG 3种接口标准。JTAG接口在第4章已经介绍过;BDM(Background Debug Mode)主要应用在PowerPC8xx系列处理器上;EJTAG主要应用在MIPS处理器上。这3种硬件接口标准定义有所不同,但是功能基本相同,下面都统称为JTAG接口。
JTAG接口需要专用的硬件工具来连接,无论从功能、性能角度,还是从价格角度,这些工具都有很大差异。最简单方式就是通过JTAG电缆,转接到计算机并口连接。这需要在主机端开发烧写程序,还需要有并口设备驱动程序。开发板加电或者复位的时候,烧写程序探测到处理器并且开始通信,然后把Bootloader下载并烧写到Flash中。这种方式速率很慢,可是价格非常便宜。一般来说,平均每秒钟可以烧写100~200个字节。烧写完成后,复位实验板,串口终端应该显示U-Boot的启动信息。
相关文章:
Bootload U-Boot分析
Bootloader是在操作系统运行之前执行的一段小程序。通过这段小程序可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备。 对于嵌入式系统,Bootloader是基于特定硬件平台来实现的。因此…...
以公益之行,筑责任之心——2023年中创算力爱心公益助学活动
捐资助学是一项功在当代、利在千秋的义举。 高考录取工作已经开始,一张张高校录取通知书也陆续送达各位准大学生手中。当他们怀揣着对大学的好奇与憧憬,准备迈进理想的大学时,还有一群人,他们渴望知识,却因经济困难而…...
【机器学习】处理样本不平衡的问题
文章目录 样本不均衡的概念及影响样本不均衡的解决方法样本层面欠采样 (undersampling)过采样数据增强 损失函数层面模型层面采样集成学习 决策及评估指标 样本不均衡的概念及影响 机器学习中,样本不均衡问题经常遇到,比如在金融…...
Android前沿技术?Jetpack如何?
Jetpack Compose是Android开发领域的一项前沿技术,它提供了一种全新的方式来构建用户界面。近年来,Jetpack Compose在各大招聘等网站上的招聘岗位逐渐增多,薪资待遇也相应提高。本文将从招聘岗位的薪资与技术要求入手,分析Jetpack…...
为react项目添加开发/提交规范(前端工程化、eslint、prettier、husky、commitlint、stylelint)
因历史遗留原因,接手的项目没有代码提醒/格式化,包括 eslint、pretttier,也没有 commit 提交校验,如 husky、commitlint、stylelint,与其期待自己或者同事的代码写得完美无缺,不如通过一些工具来进行规范和…...
小研究 - MySQL 数据库安全加固技术的研究(一)
随着信息系统的日益普及,后台数据库的安全问题逐步被人们重视起来。以当下热门的MySQL 数据库为例,通过分析数据库的安全机制以及总结数据库面临的安全风险,针对性地提出了相应的加固策略,为数据库的安全加固工作提供了技术支撑。…...
linux安装redis带图详细
如何在Linux系统中卸载Redis 一、使用apt-get卸载Redis sudo apt-get purge redis-server如果使用apt-get安装Redis,可以使用apt-get purge命令完全卸载Redis。其中,purge命令会不仅仅删除Redis二进制文件,还会删除配置文件、数据文件和日志…...
MySql——数据库常用命令
一、关于数据库的操作 查看mysql中有哪些数据库 show databases;显示创建指定数据库MySQL语句 SHOW CREATE DATABASE 数据库名:使用指定数据库 use 数据库名;查看当前使用的是哪个数据库 select database();查看指定数据库下有哪些表 use 数据库名; -- 先选择…...
如何通过 WordPress 数据库启用插件?【进不去后台可用】
如果您无法访问 WordPress 后台并需要激活插件以恢复访问权限,则可以通过 WordPress 数据库来实现。本文将向您展示如何使用数据库轻松激活 WordPress 插件。 何时使用数据库激活 WordPress 插件? 许多常见的 WordPress 错误会阻止网站所有者访问 WordP…...
芯片热处理设备 HTR-4立式4寸快速退火炉
HTR-4立式4寸快速退火炉 HTR-4立式4寸快速退火炉(芯片热处理设备)广泛应用在IC晶圆、LED晶圆、MEMS、化合物半导体和功率器件等多种芯片产品的生产,和欧姆接触快速合金、离子注入退火、氧化物生长、消除应力和致密化等工艺当中,通…...
小研究 - 基于 MySQL 数据库的数据安全应用设计(一)
信息系统工程领域对数据安全的要求比较高,MySQL 数据库管理系统普遍应用于各种信息系统应用软件的开发之中,而角色与权限设计不仅关乎数据库中数据保密性的性能高低,也关系到用户使用数据库的最低要求。在对数据库的安全性进行设计时…...
mysql转sqlite3
在项目中需要将mysql迁移到sqlite3中,此时需要作数据转换 准备工作 下载mysql2sqlite转换工具 https://github.com/dumblob/mysql2sqlite/archive/refs/heads/master.zip 下载sqlite3 https://www.sqlite.org/download.html 转换 命令行中输入如下命令 1、cd …...
在linux中使用 ./configure 时报错
./configure --build编译平台 --host运行平台 --target目标平台 1. configure: error: cannot guess build type; you must specify one 解决办法:指定编译平台 ./configure --buildarm 2. configure: error: Either a previously installed…...
【LeetCode 算法】Reverse String 反转字符串
文章目录 Reverse String 反转字符串问题描述:分析代码双指针 Tag Reverse String 反转字符串 问题描述: 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间,…...
linux sysctl.conf 常用参数配置
kernel.sysrq 0 kernel.core_uses_pid 1 kernel.msgmnb 65536#默认为16384,调高 kernel.msgmax 65536#默认为16384,调高 kernel.shmmax 68719476736#以sctl -a实际查询出的为准 kernel.shmall 4294967296#以sctl -a实际查询出的为准 net.ipv4.ip_f…...
【stm32】初识stm32—stm32环境的搭建
文章目录 🛸stm32资料分享🍔stm32是什么🎄具体过程🏳️🌈安装驱动🎈1🎈2 🏳️🌈建立Start文件夹 🛸stm32资料分享 我用夸克网盘分享了「STM32入门教程资料…...
Spring Boot3.0基础篇(二):Web 开发
Web 开发 Spring Boot Web 开发非常的简单,其中包括常用的 json 输出、filters、property、log 等 json 接口开发 在以前使用 Spring 开发项目,需要提供 json 接口时需要做哪些配置呢 添加 jackjson 等相关 jar 包配置 Spring Controller 扫描对接的方…...
【WebRTC---源码篇】(三:一)音频轨
音频轨的创建时序在Conductor::AddTracks()中 rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(peer_connection_factory_->CreateAudioTrack(kAudioLabel, peer_connection_factory_->CreateAudioSource(cricket::AudioOptions()))); 通过代码我们…...
POM文件总体配置详细说明
今天跑一个项目,报错显示他的pom文件有问题,需要一个标准的pom文件对比,希望能帮到各位! <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" …...
【项目 计网3】Socket介绍 4.9字节序 4.10字节序转换函数
文章目录 4.8 Socket介绍4.9字节序简介字节序举例 4.10字节序转换函数 4.8 Socket介绍 所谓 socket(套接字),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
