uboot源码分析uboot启动流程,uboot-CMD命令调用关系

news2024/9/20 6:07:55

uboot的最终目的是引导启动内核加载系统,根据这个线索我们可以首先找到uboot引导内核的main函数,查看系统引导的执行跳转的函数 main_loop。
下面对uboot函数的调用关系和主要调用函数进行分析。

一、uboot函数调用关系梳理

函数调用如下:
main.c common

void main_loop(void)
{
	cli_init();

	run_preboot_environment_command();
	s = bootdelay_process();
	autoboot_command(s);
}

当用户没有输入时,通常会在bootdealy延时时间到后通过autoboot_command(s)函数,自动执行uboot配置的bootcmd命令,引导启动内核。
这个main_loop(void)函数会循环执行,通过索引:run_main_loop调用main_loop()函数如下。
board_r.c (common)

static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
	sandbox_main_loop_init();
#endif
	/* main_loop() can return to retry autoboot, if so just run it again */
	for (;;)
		main_loop();
	return 0;
}

在执行 run_main_loop前,uboot的所有初始化工作都在 init_sequence_r[] 初始化函数数组中逐一列出。

static init_fnc_t init_sequence_r[] = {

initr_trace,
	initr_reloc,
initr_caches,
initr_reloc_global_data,
initr_barrier,
	initr_malloc,
	log_init,
	initr_bootstage,	/* Needs malloc() but has its own timer */
	initr_console_record,
	board_init,	/* Setup chipselects */
	set_cpu_clk_info, /* Setup clock information */
	efi_memory_init,
	stdio_init_tables,
	initr_serial,
	initr_announce,
	
	board_early_init_r,
	
	initr_env,
	
	initr_secondary_cpu,
	
	initr_pci,
	
	
	stdio_add_devices,
	initr_jumptable,
	
	console_init_r,		/* fully init console as a device */
	
	console_announce_r,
	show_board_info,
	misc_init_r,		/* miscellaneous platform-dependent init */
	interrupt_init,
	initr_enable_interrupts,
	initr_ethaddr,
	board_late_init,
	initr_scsi,
	initr_net,
	
	run_main_loop,
}
void board_init_r(gd_t *new_gd, ulong dest_addr)
{

if (initcall_run_list(init_sequence_r))

/* NOTREACHED - run_main_loop() does not return */
	hang();
}

uboot会根据对应的函数指针逐一执行初始化工作。

static inline int initcall_run_list(const init_fnc_t init_sequence[])
{
	const init_fnc_t *init_fnc_ptr;
	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)
		ret = (*init_fnc_ptr)();
}

在调用board_init_r之前,通常MCU会做一些与CPU密切相关的初始化工作,即Uboot第一阶段,通常由汇编语言完成。

64位的调用文件:

crt0_64.S (arch\arm\lib) line 152 : 	b	board_init_r			/* PC relative jump */

	b	board_init_r			/* PC relative jump */

	/* NOTREACHED - board_init_r() does not return */

efi_main in efi_app.c (lib\efi) : 	board_init_r(NULL, 0);



ENTRY(_main)
{


}

arm64为调用位置:

start.S	arch\arm\cpu\armv8	8533	2023/3/16
.globl	_start
_start:
bl	lowlevel_init
bl	_main

efi_status_t EFIAPI efi_main(efi_handle_t image,
			     struct efi_system_table *sys_table)
			{
			board_init_r(NULL, 0);	 
				 
}
//uboot已支持uefi
crt0_aarch64_efi.S	arch\arm\lib	3817	2023/3/16	
_start:

bl		efi_main

二、主要调用函数分析

uboot命令执行时的调用关系

static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
	sandbox_main_loop_init();
#endif
	/* main_loop() can return to retry autoboot, if so just run it again */
	for (;;)
	//当程序没有跳转运行kernel时(即按下空格按键时),系统将循环执行接收用户输入的命令。
		main_loop();
	return 0;
}
void main_loop(void)
{
	const char *s;

	bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

	if (IS_ENABLED(CONFIG_VERSION_VARIABLE))
		env_set("ver", version_string);  /* set version variable */
//初始化命令行接口(CLI)
	cli_init();
//执行预启动环境命令、主循环开始前从环境变量中读取并执行的。
	run_preboot_environment_command();

	if (IS_ENABLED(CONFIG_UPDATE_TFTP))
		update_tftp(0UL, NULL, NULL);
// 处理启动延迟。给用户时间来中断自动启动过程并进入命令行。
	s = bootdelay_process();
	if (cli_process_fdt(&s))
		cli_secure_boot_cmd(s);
//执行自动启动命令。这里s可能包含用户通过启动延迟中断选择的命令,或者是在没有中断时,从环境变量中读取的默认自动启动命令。
	autoboot_command(s);
   //进入CLI循环。这个函数通常会等待用户输入,解析输入,并执行相应的命令。
	cli_loop();
	panic("No CLI available");
}

延时函数的实现:

const char *bootdelay_process(void)
{
// 尝试从环境变量中获取bootdelay,否则使用配置的默认值 
	s = env_get("bootdelay");
	debug("### main_loop entered: bootdelay=%d\n\n", bootdelay);
//延时完成,从环境变量中读取bootcmd命令
	if (bootcount_error())
		s = env_get("altbootcmd");
	else
		s = env_get("bootcmd");
		
	s = env_get("bootcmd");
}
D2000#print
bootcmd=run distro_bootcmd
distro_bootcmd=run load_kernel; run load_initrd; run load_fdt; run boot_os

如果用户在2S中内输入了 回车/空格,则会根据用户输入的命令,来运行

void autoboot_command(const char *s)
{
//打印延时,等待命令
	debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

	if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
		int prev = disable_ctrlc(1);	/* disable Control C checking */
#endif
//解析和执行命令字符串中的命令列表,如果没有按下空格,则会执行默认的bootcmd命令
		run_command_list(s, -1, 0);

#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
		disable_ctrlc(prev);	/* restore Control C checking */
#endif
	}

}

如果按下了空格,输入了命令就会执行用户输入的命令

int run_command_list(const char *cmd, int len, int flag)
{
	//解析命令
	rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON);

	rcode = cli_simple_run_command_list(buff, flag);

}

解析输入的命令,当按下回车时,检查命令。

int cli_simple_run_command_list(char *cmd, int flag)
{
	if (*next == '\n')
	{
	/* run only non-empty commands */
	if (*line) 
		if (cli_simple_run_command(line, 0) < 0) {
		
					rcode = 1;
					break;
				}
	
	}


}

处理命令,查找uboot中是否包含该命令

int cli_simple_run_command(const char *cmd, int flag)
{
	if (cmd_process(flag, argc, argv, &repeatable, NULL))
			/* Look up command in command table */
			cmdtp = find_cmd(argv[0]);
				find_cmd_tbl(cmd, start, len);
				/* If OK so far, then do the command */
				rc = cmd_call(cmdtp, flag, argc, argv, &newrep);
	
}

解析命令参数,并执行相应的命令。

void cli_loop(void)
{
	bootstage_mark(BOOTSTAGE_ID_ENTER_CLI_LOOP);
#ifdef CONFIG_HUSH_PARSER
	parse_file_outer();
	/* This point is never reached */
	for (;;);
#elif defined(CONFIG_CMDLINE)
	cli_simple_loop();
#else
	printf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n");
#endif /*CONFIG_HUSH_PARSER*/
}

三、uboot命令的由来

在执行uboot命令时,可以看到uboot中有很多命令,那这些命令是怎么载入和实现的呢,下面逐一说明。
command.h include 11540 2023/3/16 6
在uboot中提供了一个cmd_tbl_s结构体,所有的命令都声明在 cmd_tbl_t 这个结构体对象中。

typedef struct cmd_tbl_s	cmd_tbl_t;

cmd_tbl_t 成员变量如下:

struct cmd_tbl_s {
	//命令的名称
	char		*name;		/* Command Name			*/
	//命令的参数个数
	int		maxargs;	/* maximum number of arguments	*/
					/*
					 * Same as ->cmd() except the command
					 * tells us if it can be repeated.
					 * Replaces the old ->repeatable field
					 * which was not able to make
					 * repeatable property different for
					 * the main command and sub-commands.
					 */
	int		(*cmd_rep)(struct cmd_tbl_s *cmd, int flags, int argc,
				   char * const argv[], int *repeatable);
					/* Implementation function	*/
	//命令对应的函数指针
	int		(*cmd)(struct cmd_tbl_s *, int, int, char * const []);
	//命令的使用方法
	char		*usage;		/* Usage message	(short)	*/
#ifdef	CONFIG_SYS_LONGHELP
    //定义命令的帮组信息  --help
	char		*help;		/* Help  message	(long)	*/
#endif
#ifdef CONFIG_AUTO_COMPLETE
	/* do auto completion on the arguments */
	int		(*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
#endif
};

在使用时,定义uboot命令如下:
reset命令,调用的函数为do_reset函数。

boot.c	cmd	1417	2023/3/16	16
U_BOOT_CMD(
	reset, 1, 0,	do_reset,
	"Perform RESET of the CPU",
	""
);

poweroff命令,调用的函数为do_poweroff函数。

#ifdef CONFIG_CMD_POWEROFF
U_BOOT_CMD(
	poweroff, 1, 0,	do_poweroff,
	"Perform POWEROFF of the device",
	""
);

定义使用命令示例:
doc/README.commands
在uboot中如果需要添加命令,首先需要包含command.h文件,然后使用U_BOOT_CMD()宏或者U_BOOT_CMD_COMPLETE宏添加命令到cmd_tbl_t 结构体中。

This is done by first including command.h, then using the U_BOOT_CMD() or the
Commands are added to U-Boot by creating a new command structure.
This is done by first including command.h, then using the U_BOOT_CMD() or the
U_BOOT_CMD_COMPLETE macro to fill in a cmd_tbl_t struct.
U_BOOT_CMD(name, maxargs, repeatable, command, "usage", "help")
U_BOOT_CMD_COMPLETE(name, maxargs, repeatable, command, "usage, "help", comp)

子命令定义,定义子命令可以使用 U_BOOT_CMD_MKENT或 U_BOOT_CMD_MKENTCOMPLETE宏
Sub-command definition

Likewise an array of cmd_tbl_t holding sub-commands can be created using either
of the following macros:

* U_BOOT_CMD_MKENT(name, maxargs, repeatable, command, "usage", "help")
* U_BOOT_CMD_MKENTCOMPLETE(name, maxargs, repeatable, command, "usage, "help",
  comp)
static cmd_tbl_t demo_commands[] = {
	U_BOOT_CMD_MKENT(list, 0, 1, do_demo_list, "", ""),
	U_BOOT_CMD_MKENT(hello, 2, 1, do_demo_hello, "", ""),
	U_BOOT_CMD_MKENT(light, 2, 1, do_demo_light, "", ""),
	U_BOOT_CMD_MKENT(status, 1, 1, do_demo_status, "", ""),
};

实际使用的NVME命令,调用函数为do_nvme函数。

nvme.c	cmd	1334	2023/3/16	21
static int do_nvme(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])

U_BOOT_CMD(
	nvme, 8, 1, do_nvme,
	"NVM Express sub-system",
	"scan - scan NVMe devices\n"
	"nvme detail - show details of current NVMe device\n"
	"nvme info - show all available NVMe devices\n"
	"nvme device [dev] - show or set current NVMe device\n"
	"nvme part [dev] - print partition table of one or all NVMe devices\n"
	"nvme read addr blk# cnt - read `cnt' blocks starting at block\n"
	"     `blk#' to memory address `addr'\n"
	"nvme write addr blk# cnt - write `cnt' blocks starting at block\n"
	"     `blk#' from memory address `addr'"
);

U_BOOT_CMD 命令是如何与 cmd_tbl_t 结构体关联起来的呢?

#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)

	U_BOOT_CMD宏定义来自U_BOOT_CMD_COMPLETE宏定义
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)		\
	U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)			
 U_BOOT_CMD_COMPLETE宏定义由ll_entry_declare函数和U_BOOT_CMD_MKENT_COMPLETE宏定义函数组成
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
	ll_entry_declare(cmd_tbl_t, _name, cmd) =			\
		U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,	\
						_usage, _help, _comp);		

#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help,	\
			    _comp)				\
	_CMD_REMOVE(sub_ ## _name, _cmd)
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,		\
				_usage, _help, _comp)			\
		{ #_name, _maxargs,					\
		 _rep ? cmd_always_repeatable : cmd_never_repeatable,	\
		 _cmd, _usage, _CMD_HELP(_help) _CMD_COMPLETE(_comp) }

#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, _usage,	\
				  _help, _comp)				\
		{ #_name, _maxargs, NULL, 0 ? _cmd : NULL, _usage,	\
			_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
	ll_entry_declare(cmd_tbl_t, _name, cmd) =			\
		U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,	\
						_usage, _help, _comp);


ll_entry_declare函数也是一个宏定义,使用该宏定义的函数会被存放在u_boot_list段(attribute)。

#define ll_entry_declare(_type, _name, _list)				\
	_type _u_boot_list_2_##_list##_2_##_name __aligned(4)		\
			__attribute__((unused,				\
			section(".u_boot_list_2_"#_list"_2_"#_name)))

uboot.lds

 .u_boot_list : {
  KEEP(*(SORT(.u_boot_list*)));
 }

这个宏 ll_entry_declare 是用于在链接时生成的数组中声明一个条目的工具。这种机制在嵌入式系统、特别是像U-Boot这样的引导加载程序中非常有用,因为它允许开发者在编译时注册一系列的条目(如命令、设备驱动程序等),然后在运行时通过遍历这个链接时生成的数组来访问它们。
这个宏的详细解释和用法。
_type:条目的数据类型。
_name:条目的名称,用于在生成的数组中唯一标识该条目。
_list:条目所属列表的名称,这个名称将用于构造最终的变量名和段名。
宏的作用是声明一个全局变量,这个变量被放置在特定的段(section)中,段名由宏参数动态生成。__aligned(4) 确保变量按4字节对齐,这通常是出于硬件访问效率或特定要求的考虑。attribute((unused)) 告诉编译器这个变量可能不会被直接使用,但请保留它,因为它将在其他地方(如链接时)被引用。section 属性指定了变量应该被放置在哪个段中,这个段名是通过宏参数动态构造的,确保了不同列表和名称的条目可以分别存储在不同的段中。

假设我们有一个结构体 struct my_sub_cmd,并希望声明一个名为 my_sub_cmd 的条目,该条目属于名为 cmd_sub 的列表。

struct my_sub_cmd {  
    int x;  
    int y;  
};  
  
ll_entry_declare(struct my_sub_cmd, my_sub_cmd, cmd_sub) = {  
    .x = 3,  
    .y = 4,  
};

这行代码会声明一个全局变量 _u_boot_list_2_cmd_sub_2_my_sub_cmd,
并将其放置在 .u_boot_list_2_cmd_sub_2_my_sub_cmd 段中。这个变量是 struct my_sub_cmd 类型的,并且被初始化为 { .x = 3, .y = 4 }。在U-Boot的初始化或运行时,可以编写代码来遍历 .u_boot_list_2_cmd_sub_2_my_sub_cmd 段中的所有条目,并执行相应的操作。

uboot启动时,是如何读取u_boot_list段中的参数的呢?
static cmd_tbl_t cmd_se_sub[] =
uboot启动时,会通过init函数加载所有的uboot命令

static int initr_manual_reloc_cmdtable(void)
{
	fixup_cmdtable(ll_entry_start(cmd_tbl_t, cmd),
		       ll_entry_count(cmd_tbl_t, cmd));
	return 0;
}

#if defined(CONFIG_NEEDS_MANUAL_RELOC)
void env_reloc(void)
{
	fixup_cmdtable(cmd_env_sub, ARRAY_SIZE(cmd_env_sub));
}
#endif
void fixup_cmdtable(cmd_tbl_t *cmdtp, int size)
{
	int	i;

	if (gd->reloc_off == 0)
		return;

	for (i = 0; i < size; i++) {
		ulong addr;

		addr = (ulong)(cmdtp->cmd) + gd->reloc_off;

		cmdtp->cmd =
			(int (*)(struct cmd_tbl_s *, int, int, char * const []))addr;
		addr = (ulong)(cmdtp->name) + gd->reloc_off;
		cmdtp->name = (char *)addr;
		if (cmdtp->usage) {
			addr = (ulong)(cmdtp->usage) + gd->reloc_off;
			cmdtp->usage = (char *)addr;
		}
#ifdef	CONFIG_SYS_LONGHELP
		if (cmdtp->help) {
			addr = (ulong)(cmdtp->help) + gd->reloc_off;
			cmdtp->help = (char *)addr;
		}
#endif
#ifdef CONFIG_AUTO_COMPLETE
		if (cmdtp->complete) {
			addr = (ulong)(cmdtp->complete) + gd->reloc_off;
			cmdtp->complete =
				(int (*)(int, char * const [], char, int, char * []))addr;
		}
#endif
		cmdtp++;
	}
}

se.c cmd 6734

static __maybe_unused void se_reloc(void)
{
	fixup_cmdtable(cmd_se_sub, ARRAY_SIZE(cmd_se_sub));
}
static int do_se(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{	
	c = find_cmd_tbl(argv[0], &cmd_se_sub[0], ARRAY_SIZE(cmd_se_sub));
}

uboot引导过程总结:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2119343.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ollama 本地运行大模型(LLM)完全指南

文章介绍了 Ollama 本地运行大模型&#xff08;LLM&#xff09;的方方面面&#xff0c; 包括安装运行、对话、自定义模型、系统提示配置、调试、开发、存储、如何作为服务、OpenAI 的兼容等。 这一年来&#xff0c;我已经习惯了使用线上大模型 API 来工作&#xff0c;只要网络…

2025毕业设计免费指导!!

本人专注于Android/java/PHP/Python/人工智能/数据库/微信小程序技术等领域的开发&#xff0c;以及有好几年的计算机毕业设计方面的实战开发经验和技术积累&#xff1b;尤其是在图像识别、网站开发和微信小程序的开发&#xff0c;很是熟悉和了解&#xff1b;本人也是多年的全栈…

DevOps实现CI/CD实战(六)- Jenkins集成k8s

十、 Jenkins集成k8s Jenkins在集成K8s之前&#xff0c;需要搭建k8s集群&#xff0c;具体搭建步骤&#xff0c;完整笔记 https://github.com/ITenderL/ITenderL.github.io/tree/main/docs/DevOps&#xff0c; 包括完整的DevOps的笔记。 1. 准备部署的yml文件 pipeline.yml …

祝福在茶礼丨酒茶香充满东方古韵特色的中秋礼盒,太惊艳了

中国是礼仪之邦&#xff0c;礼尚往来更是普通不过。象征东方古韵的茶礼成为现代送礼热门&#xff0c;尤其是逢年过节茶礼氛围更是浓郁&#xff0c;跃居礼单榜首。 中秋节作为团圆之节&#xff0c;送礼肯定少不了&#xff01;送茶礼的这几个理由你一定要知道&#xff01; 送茶即…

【动手学深度学习】06 矩阵计算(个人向笔记)

标量导数 这个比较简单&#xff0c;就是直接求导 亚导数 举了两个例子 梯度 下面是当 y 和 x 分别为向量和标量时求导的结果。一个为向量&#xff0c;一个为标量则结果为向量。两个都为向量结果为矩阵&#xff0c;两个都为标量结果为标量 当 y 为标量&#xff0c;x 为列…

关于腾讯IM消息ID不统一的问题?服务端的MsgKey和前端的msgID不一样

角色>前端&#xff1a;web、小程序、客户端&#xff08;ios、安卓&#xff09;&#xff1b;服务端&#xff1b;腾讯IM&#xff1b; 1、背景 IM消息ID不一致&#xff0c;本地没有缓存历史数据&#xff0c;导致在调用腾讯sdk方法时&#xff0c;id不一致报错问题 2、调研目的…

MySQL进阶篇4 - 锁

五、锁 5.1 概述 介绍 分类 5.2 全局锁 介绍 红色代表不可执行的操作&#xff0c;绿色代表可执行的操作 # mysqldump 是 MySQL 提供的数据备份的命令演示 # 如果想进行全库的逻辑备份&#xff0c;那么就需要在逻辑备份之前 # 手动的加上全局锁 flush tables with read …

flask下https教程

一、定义 linux 下flask https 协议 二、实现 linux 下flask https 协议 生成SSL证书和密钥文件。您可以使用工具如openssl来生成自签名SSL证书和密钥文件。运行以下命令生成证书和密钥文件&#xff1a; openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout…

HCIE和CCIE,哪个含金量更高点?

在现在内卷的大环境下&#xff0c;技术岗可谓人人自危&#xff0c;也因此各种认证的重视程度直线升高。 特别是华为认证的HCIE和思科认证的CCIE&#xff0c;它们都代表着网络技术领域的顶尖水平。 但面对这两个高含金量的认证&#xff0c;不得不让人问出这个问题&#xff1a;同…

找到办法了!一个站点,搞定所有访问权限需求

在知识库的构建与管理过程中&#xff0c;如何平衡信息的公开与私密性始终是一大挑战。传统方法往往需要建立多个具有不同访问权限的站点&#xff0c;操作繁琐且难以维护。尤其当企业需同时向公众和内部成员提供知识文章时&#xff0c;这一问题尤为突出。用户频繁反馈&#xff0…

振弦式渗压计安装流程全面指南

在大坝安全监测体系中&#xff0c;振弦式渗压计作为关键设备之一&#xff0c;承担着监测大坝内部渗流压力变化的重任。其安装质量直接关系到监测数据的准确性和大坝安全的评估。因此&#xff0c;制定一套科学、精细的安装流程&#xff0c;对于确保渗压计的正常运行和延长使用寿…

什么是生成式 AI?

人工智能 (AI) 通过使用机器学习与环境交互并执行任务来模仿人类行为&#xff0c;而无需明确指示要输出的内容。 生成式 AI 描述 AI 中用于创建原创内容的一类功能。 人员通常与聊天应用程序中内置的生成式 AI 交互。 此类应用程序的一个常见示例是 Microsoft Copilot&#xf…

Telephony STK 域选

在场测过程中,经常遇到STK功能不生效,点击STK会出现无响应的问题. 一般需要对比DUT和REF来确认问题所在。一般情况下,出现类似问题需要check是否域选是一致的。 测试机 对比机 对比机为展锐平台会出现弹框情况,从Log看,相关业务进行了报错回落,从IMS业务回落到了…

基于spring的博客系统(一)

通过前⾯课程的学习, 我们掌握了Spring框架和MyBatis的基本使⽤, 并完成了图书管理系统的常规功能 开发, 接下来我们系统的从0到1完成⼀个项⽬的开发&#xff1b; 1. 项⽬介绍 使⽤SSM框架实现⼀个简单的博客系统 共5个⻚⾯ 1. 用户登录 2. 博客发表⻚ 3. 博客编辑⻚ 4. 博客…

【Java算法】模拟

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【算法工作坊】算法实战揭秘 &#x1f9e3; 一.模拟算法 模拟算法和传统的算法有一些不同之处&#xff0c;更多的是对题目要求的理解&#xff0c;通过代码的方式去模拟实现一道题目在现实中的实现方法…

接口基础知识4概念纠正与补充说明

​接口基础知识4 1 请求方法的幂等性 幂等性的理解是没有问题的。但是请求方法和幂等性没有直接的关联。 在 REST API 设计中&#xff0c;幂等性非常重要&#xff0c;因为它保证了在重复请求的情况下&#xff0c;资源的状态始终保持一致&#xff0c;不会因为重复请求而发生副…

量化交易backtrader实践(一)_数据获取篇(1)_数据准备

我们需要使用backtrader来做量化交易回测&#xff0c;先解决一些前置问题&#xff1a; 需要回测哪几支股票&#xff0c;或者即时选取股票即时选取股票以什么形式&#xff0c;代码还是名称这些数据的格式是怎么样的&#xff0c;backtrader如何接收 在解决这些前置问题的过程中…

如何抽取一个特定页面从devExpress的例程集合中

https://download.csdn.net/download/haoyujie/89729356https://download.csdn.net/download/haoyujie/89729356相关资源。 devExpress界面库的例程的设计是有必要来吐槽的。其紧耦合和大捆绑&#xff0c;我认为是devExpress很重要的不友好&#xff0c;导致可用性差和学习曲线…

让AI算算国庆中秋,你究竟多休了几天

最近&#xff0c;2024年中秋和国庆的放假调休安排火速登上热搜&#xff1a; “上6休3&#xff0c;上3休2&#xff0c;上5休1&#xff0c;上2休7&#xff0c;再上5休1。” 是不是看着眼花缭乱&#xff1f;其实不仅你懵了&#xff0c;广大网友也集体抓狂&#xff1a;“这比高考…

Spring介绍和Log4j2

目录 一、模块组成二、基本用例-Spring框架基本使用、原理三、启用日志框架 Spring 有两个最核心模块&#xff1a; IoC 和 AOP。 IoC&#xff1a;Inverse of Control 的简写&#xff0c;译为“控制反转”&#xff0c;指把创建对象过程交给 Spring 进行管理。AOP&#xff1a;Asp…