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

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_SANDBOXsandbox_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	_mainefi_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_SANDBOXsandbox_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");elses = 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_PARSERparse_file_outer();/* This point is never reached */for (;;);
#elif defined(CONFIG_CMDLINE)cli_simple_loop();
#elseprintf("## 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//定义命令的帮组信息  --helpchar		*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_LONGHELPif (cmdtp->help) {addr = (ulong)(cmdtp->help) + gd->reloc_off;cmdtp->help = (char *)addr;}
#endif
#ifdef CONFIG_AUTO_COMPLETEif (cmdtp->complete) {addr = (ulong)(cmdtp->complete) + gd->reloc_off;cmdtp->complete =(int (*)(int, char * const [], char, int, char * []))addr;}
#endifcmdtp++;}
}

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.rhkb.cn/news/419899.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Oracle Linux 8.10安装Oracle19c(19.3.0)完整教程

安装前请仔细将文档通读一遍&#xff0c;安装过程中根据安装命令仔细核对&#xff0c;特别留意一些字体加粗或标红的字样&#xff0c;遇到问题请及时咨询公司 1、基础环境 1.1、操作系统 cat /etc/redhat-release 1.2、主机名 医院默认分配的主机名可能跟其他主机会有重复&a…

Idea配置 阿里云 Spring Initializr URL

Idea默认Strart services url Idea中默认使用为https://start.spring.io/&#xff0c;国内网络如果不稳定创建工程会很慢修改为阿里云地址 https://start.aliyun.com/

局域网文件分发如何实现?掌握这4个秘籍,文件一键分发破次元!

局域网文件分发是许多企业和组织在日常工作中常见的需求&#xff0c; 有效的文件分发可以显著提高工作效率。 以下是四种实现局域网文件一键分发的秘籍&#xff1a; 1.使用终端监控软件的文件分发功能 软件示例&#xff1a;安企神等。 步骤简述&#xff1a; 安装软件&…

IP学习——oneday

1.什么是网络&#xff1f;为什么需要网络&#xff1f; 空间&#xff0c;时间&#xff1b;传统的邮件传输要考虑到距离&#xff0c;网络解决了空间距离&#xff08;太远&#xff09;、解决了时间问题&#xff08;旧音乐等&#xff09; 云:面向客户的虚拟化服务 运营商公司主营…

麒麟信安重庆渠道伙伴行业研讨会,共探国产化发展机遇

9月5日下午&#xff0c;麒麟信安举办重庆渠道伙伴行业研讨会。研讨会旨在探讨国产化浪潮下操作系统相关产业的发展机遇与挑战&#xff0c;以及如何在各关键领域实现市场拓展与应用&#xff0c;共商合作、共创未来。 会议伊始&#xff0c;麒麟信安详细阐述了公司以国产自主操作系…

攻防世界 unseping

unseping 攻防世界web新手练习 -unseping_攻防世界web新手题unseping-CSDN博客 这道题对我来说还是有点难&#xff0c;什么oct绕过命令执行第一次遇到捏&#xff0c;所以基本是跟着别人的wp写的&#xff0c;一点点记录吧 先对源码进行分析 <?php highlight_file(__FILE…

10款国民级企业文件加密系统介绍,究竟哪一个是你的菜?

A: “你知道为什么文件加密系统对企业至关重要吗&#xff1f;” B: “当然&#xff0c;随着数据泄露风险增加&#xff0c;文件加密成了保护敏感信息的必要手段。” A: “没错&#xff0c;它能确保即使文件被窃取&#xff0c;未授权者也无法轻易访问内容。” B: “而且&#…

解决SRS流媒体服务服务器无法接收客户端ipv6 RTMP推流的思路

这篇短文我不介绍SRS是什么&#xff0c;主要介绍一个场景问题&#xff0c;场景是你使用服务器并且部署了SRS服务配置成一个媒体流转发服务&#xff0c;也就是客户端往SRS流媒体服务器推流&#xff0c;然后SRS把流转推出去&#xff0c;但是会涉及到一个问题是&#xff1a;用户客…

java后端保存的本地图片通过ip+端口直接访问

直接上代码吧 package com.ydx.emms.datapro.controller;import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.…

网络基础入门指南(一)

前言 在这个高度互联的世界里&#xff0c;互联网已成为日常生活不可或缺的一部分。然而&#xff0c;对于许多人来说&#xff0c;网络是如何工作的仍然是个谜。本文旨在为那些对网络基础知识感兴趣的朋友提供一个简单的介绍&#xff0c;帮助大家更好地理解互联网的基本原理和技…

AI人工智能如何重塑我们的世界,引领无限可能

作者简介&#xff1a;我是团团儿&#xff0c;是一名专注于云计算领域的专业创作者&#xff0c;感谢大家的关注 座右铭&#xff1a; 云端筑梦&#xff0c;数据为翼&#xff0c;探索无限可能&#xff0c;引领云计算新纪元 个人主页&#xff1a;团儿.-CSDN博客 目录 前言&#…

Simulink:循环计数器 Counter Free-Running

原文&#xff1a;Matlab生成stm32代码_matlab stm32-CSDN博客 使用“Counter Free-Running”模块进行计数&#xff0c;参数配置如下 此配置的意思为每0.5秒计数一次&#xff0c;计数的最大值为2^Nbits – 1&#xff0c;其中Nbits为所填的2&#xff0c;所以这里最大值为3。 示波…

【C语言】使用VSCode编译C语言程序

目录 1. 安装MinGW环境2.添加C/C扩展3. 新建工程文件夹3.1 建立test文件夹3.2 建立.vscode文件夹3.3 建立test.c文件 4. 使用VSCode打开工程文件夹5. 编写JSON文件5.1 手动编写5.1.1 创建tasks.json文件。5.1.2 创建launch.json文件5.1.3.编写单个C语言程序5.1.4. 运行调试 5.2…

29个横幅广告及详细点评,帮您优化广告效果

在过去30年里&#xff0c;横幅广告是为数不多的在每个阶段都得以存活的网络元素之一&#xff0c;至今仍是许多企业展示广告战略的支柱。但随着互联网内容的不断增加&#xff0c;吸引潜在客户的注意力变得越来越困难。这时候&#xff0c;一些智能化的解决方案&#xff0c;例如光…

多目标遗传算法(NSGAⅢ)的原理和matlab实现

参考文献&#xff1a; [1] Deb K , Jain H .An Evolutionary Many-Objective Optimization Algorithm Using Reference-Point-Based Nondominated Sorting Approach, Part I: Solving Problems With Box Constraints[J].IEEE Transactions on Evolutionary Computation, 2014,…

最高1000万 各地模型和算法备案补贴政策一览

最高1000万 各地模型和算法备案补贴政策一览 2024年7月31日&#xff0c;成都市的人工智能产业再度引起关注。通过国家大模型备案的三家企业——海艺互娱、晓多科技和明途科技&#xff0c;获得了成都市经信局市新经济委的百万奖励。这一奖励源自成都发布的《成都市进一步促进人工…

【算法思想·二叉树】后续篇

本文参考labuladong算法笔记[二叉树心法&#xff08;后序篇 | labuladong 的算法笔记] 前序位置的代码只能从函数参数中获取父节点传递来的数据&#xff0c;而后序位置的代码不仅可以获取参数数据&#xff0c;还可以获取到子树通过函数返回值传递回来的数据。 那么换句话说&am…

加密货币市场持有与价格波动:CFI调查揭示的趋势与未来展望

自2022年1月以来&#xff0c;消费者金融协会&#xff08;CFI&#xff09;通过六项不同的调查收集了有关加密货币所有权的数据。这些调查旨在了解加密货币的当前持有量和未来购买兴趣&#xff0c;并将这些数据与加密货币市场表现进行对比。结果显示&#xff0c;市场价格与持有量…

【MySQL】MySQL操作介绍

MySQL操作 认识 MySQL什么是 MySQL关系型数据库的组成结构"客户端-服务器"结构 数据库的基本操作创建数据库查看数据库删除数据库使用数据库 数据类型整型浮点类型字符串类型日期类型总结 表的操作创建表查看表查看表的信息删除表 数据的基础操作插入数据指定列插入全…

计算机网络:http协议

计算机网络&#xff1a;http协议 一、本文内容与前置知识点1. 本文内容2. 前置知识点 二、HTTP协议工作简介1. 特点2. 传输时间分析3. http报文结构 三、HTTP版本迭代1. HTTP1.0和HTTP1.1主要区别2. HTTP1.1和HTTP2主要区别3. HTTPS与HTTP的主要区别 四、参考文献 一、本文内容…