U-Boot分析【学习笔记】(12)

11. board_init_r

U-Boot分析【学习笔记】(11)文章最后,进入了 board_init_r 函数,在 common/ 路径下可以找到 board_init_r.c 文件。

c 复制代码
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
	/*
	 * Set up the new global data pointer. So far only x86 does this
	 * here.
	 * TODO(sjg@chromium.org): Consider doing this for all archs, or
	 * dropping the new_gd parameter.
	 */
#if CONFIG_IS_ENABLED(X86_64)
	arch_setup_gd(new_gd);
#endif

#ifdef CONFIG_NEEDS_MANUAL_RELOC
	int i;
#endif

#ifdef CONFIG_AVR32
	mmu_init_r(dest_addr);
#endif

#if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
	gd = new_gd;
#endif

#ifdef CONFIG_NEEDS_MANUAL_RELOC
	for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
		init_sequence_r[i] += gd->reloc_off;
#endif

	if (initcall_run_list(init_sequence_r))
		hang();

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

r0 → \rightarrow → new_gd(拷贝到DDR的GD指针地址)汇编执行 mov r0, r9,将锁存在 r9 寄存器中、位于外部 DDR 顶端的全新 global_data 结构体绝对物理首地址注入 r0,为 C 语言函数的第一个入参。

r1 → \rightarrow → dest_addr(代码段重定位基地址)汇编执行 ldr r1, [r9, #GD_RELOCADDR],通过变址寻址从新 GD 中精准剥离出 U-Boot 自身代码段在 DDR 中的基地址,将其压入 r1,为 C 语言函数的第二个入参。

主要操作:

1.手动重定位

c 复制代码
#ifdef CONFIG_NEEDS_MANUAL_RELOC
	for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
		init_sequence_r[i] += gd->reloc_off;
#endif

代码意图:

遍历庞大的第二阶段初始化函数指针数组 init_sequence_r,将数组内部存储的每一个子初始化函数的绝对物理基地址,强行手动加上重定位偏移量 gd->reloc_off。
架构幕后:

现代标准的 ARM 架构拥有极为强大的 .rel.dyn(动态链接重定位表)技术。在汇编的 relocate_code 循环中,系统早已利用硬件级和底层的循环把文字池、全局符号表内的地址指针全部自动修正到了 DDR 高地址。

这个宏(CONFIG_NEEDS_MANUAL_RELOC)仅仅是为了兼容某些不具备动态链接自动修正能力的古老或特殊芯片架构(如早期部分 MIPS/AVR32),对其在软件层面实施的手动地址打补丁。对于你的 ARM 平台,此段直接跳过。

2.依次执行回调函数

c 复制代码
if (initcall_run_list(init_sequence_r))
		hang();

代码意图:

正式开始第二阶段初始化的系列初始化操作。通用核心框架函数 initcall_run_list 顺序接收 init_sequence_r 函数指针数组。
执行机制:

它内部包含一个严密的滚轮式循环,会像剥洋葱一样,从数组的第一项(如 initr_trace)开始,顺次回调执行针对堆内存(initr_malloc)、驱动模型(initr_dm)、串口(initr_serial)及存储介质(initr_mmc)的 C 语言点亮函数。
防御性编程:

根据 U-Boot 严格的底层开发规范,初始化数组中的任何一个子函数,成功必须返回 0,失败必须返回非 0 错误码。一旦中途任何一个外设点亮卡死(如网卡复位失败),initcall_run_list 就会捕捉到非 0 恶果,返回 true,从而直接触发 hang()。

board_init_f也有类似的操作:

init_sequence_f(这里的 _f 代表 Forward/Front,即重定位前的拓荒阶段);

init_sequence_r(这里的 _r 代表 Relocation,即重定位后的稳定阶段)。

核心维度 前期拓荒:init_sequence_f 后期覆盖:init_sequence_r
物理战场 芯片内部受限的 SRAM 外部大容量的 DDR RAM
核心使命 点亮内存,规划新空间(探卡、算内存大小、圈地) 构建完整 C 语言运行业务(加载内核、打通网络、处理业务)
内存池(Malloc) 极其简陋的 malloc_f,容量只有几 KB,只准分配不准释放 真正的 initr_malloc 全功能堆内存,支持完整的 malloc/free
设备驱动模型(DM) 临时的简易驱动模型(Pre-Relocation DM),只点亮最迫切的外设 全功能驱动模型(initr_dm),扫描全量设备树并绑定所有外设驱动
终局归宿 执行 relocate_code发起物理搬家,自毁低地址控制流 执行 run_main_loop陷入死循环倒计时,拉起 Linux 内核

init_sequence_r:

删除其他框架的内容,保留 imx6ull 相关的代码后,可以得到裁剪后的代码:

c 复制代码
static init_fnc_t init_sequence_r[] = {
	initr_trace,                /* 跟踪功能初始化 */
	initr_reloc,                /* 盖章宣告重定位法理合法性,设置 flags */
	initr_caches,               /* ARM特有:使能并配置 Cortex-A7 的 I-Cache/D-Cache 与 MMU */
	initr_reloc_global_data,    /* 刷新并重新对齐重定位后的 Global Data 全局账本 */
	initr_barrier,              /* 内存屏障,确保前面的内存配置彻底生效 */
	initr_malloc,               /* 物理圈定并初始化外部 DDR 中的全功能大容量堆内存池(Malloc Pool) */
	initr_console_record,       /* 控制台记录功能初始化 */
	bootstage_relocate,         /* 启动阶段时间戳记录统计重定位 */
#ifdef CONFIG_DM
	initr_dm,                   /* 驱动模型:全量扫描设备树(DTB)并重新匹配绑定节点 */
#endif
	initr_bootstage,            /* 启动阶段状态初始化 */
	board_init,                 /* 板级硬件底座初始化:i.MX6ULL 具体的 GPIO 复用、网络 PHY 芯片物理复位 */
#ifdef CONFIG_CLOCKS
	set_cpu_clk_info,           /* 建立并设置当前 i.MX6ULL 的 CPU/内核时钟账本信息 */
#endif
	stdio_init_tables,          /* 标准输入输出设备表初始化 */
	initr_serial,               /* 串口驱动正式向 DDR 变轨寻址,接管物理寄存器 */
	initr_announce,             /* 核心宣告:串口吐出 "Now running in RAM - U-Boot at..." 确立统治权 */
	INIT_FUNC_WATCHDOG_RESET    /* 看门狗定时喂狗占位符,防止外设握手卡死引发硬件复位 */
#if defined(CONFIG_BOARD_EARLY_INIT_R)
	board_early_init_r,         /* 早期板级 C 语言微调初始化 */
#endif
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_SYS_DELAYED_ICACHE
	initr_icache_enable,        /* 延迟使能指令缓存 */
#endif
	power_init_board,           /* i.MX6ULL 板载电源管理芯片(如 PMIC)或电压配置初始化 */
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_GENERIC_MMC
	initr_mmc,                  /* 彻底点亮 EMMC / SD卡驱动,完成块设备物理握手与时钟配置 */
#endif
	initr_env,                  /* 核心分水岭:去 EMMC 存储介质中迎回用户保存的"正统"环境变量(bootcmd/bootargs) */
	INIT_FUNC_WATCHDOG_RESET
	stdio_add_devices,          /* 将串口、LCD等虚拟化为标准 I/O 系统设备 */
	initr_jumptable,            /* 跳转表初始化(API函数指针注册) */
	console_init_r,             /* 控制台完全体点亮:从此 printf 的 Log 真正通过标准 I/O 链路发送 */
#ifdef CONFIG_DISPLAY_BOARDINFO_LATE
	show_board_info,            /* 屏幕或串口打印当前 i.MX6ULL 开发板的硬件/厂商信息 */
#endif
	INIT_FUNC_WATCHDOG_RESET
	interrupt_init,             /* 硬件异常向量表与中断基底初始化 */
	initr_enable_interrupts,    /* ARM特有:改写 CPSR 寄存器,全盘开启 ARM 核心的硬件中断(IRQ)捕获探针 */
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_CMD_NET
	initr_ethaddr,              /* 从环境或 EEPROM 中提取、校验 i.MX6ULL 双网口的物理 MAC 地址 */
#endif
#ifdef CONFIG_BOARD_LATE_INIT
	board_late_init,            /* 板级后期初始化:执行 i.MX6ULL 特异性的最后环境微调 */
#endif
#ifdef CONFIG_CMD_NET
	INIT_FUNC_WATCHDOG_RESET
	initr_net,                  /* 探测并点亮以太网 PHY 芯片(如 LAN8720A),全盘激活 U-Boot 网络协议栈 */
#endif
	run_main_loop,              /* 初始化序列的结尾:结束线性逻辑,进入 main_loop 倒计时命令侦听 */
};

核心内容:

  1. 性能与空间解封:initr_caches 与 initr_malloc

    这两个函数紧密相连,是系统在外部 DDR 里运行的基石。
    initr_caches(硬件加速):

    i.MX6ULL 是单核 Cortex-A7 架构。在重定位前,代码在受限的 SRAM 里跑,Cache 映射极其保守。到了这里,系统重新配置 ARM 的 CP15 协处理器,正式开启 D-Cache(数据缓存)和 MMU(内存管理单元)页表视图。没有这一步,DDR 访问就会极其缓慢,后面的 EMMC 加载内核会非常慢。
    initr_malloc(软件松绑):

    第一阶段的 malloc_f 容量极其有限(只有几 KB)且只准分配不准释放。在这里,系统在 DDR 的安全区域物理圈出一片几 MB 到几十 MB 的连续空间,构建了全功能堆内存池。从此,标准的 malloc() 和 free() 机制在 C 语言世界里正式合法化,后续复杂的设备树解析、网络协议栈才有空间动态开辟内存。

  2. 现代框架核心:initr_dm(驱动模型)

    这是理解现代 U-Boot 架构(以及对接 Linux 内核)最重要的一步。

    物理动作:它会彻底切断重定位前在 SRAM 留下的微型、临时驱动链表,将 gd->dm_root 抹零,然后在 DDR 空间中重新调用 dm_init_and_scan(false)。
    本质意义:

    系统会去全面扫描 DDR 中的 设备树(DTB) 镜像,将设备树里的每一个节点(Node)与具体的驱动(Driver)进行正式的绑定。

  3. 主权确立的官方公告:initr_serial 与 initr_announce

    这是控制流在 DDR 中物理统治权完全确立的标志。
    物理动作:

    initr_serial 让串口驱动全面进驻重定位后的 DDR 高地址寻址线,接管物理寄存器。紧接着,initr_announce 在控制台打印一行Log:"Now running in RAM - U-Boot at: %08lx\n"。
    背景:

    在这一行字打出来之前,哪怕串口能打印东西,也是在利用第一阶段重定位前的残存寄存器状态在"惯性盲打";只有这一行字吐出来,才标志着 U-Boot 真正摆脱了 SRAM 的空间,完全在 DDR RAM 中建立了自己的合法控制流。

  4. 迎回正统内核账本:initr_mmc 与 initr_env(完美闭环)

    i.MX6ULL 跑 Linux 基本上都是从 EMMC 或 SD 卡启动。这两个函数是引导内核的生命线。
    initr_mmc:

    点亮 i.MX6ULL 的 USDHC(超高速数据主机控制器)驱动,配置好时钟和引脚,完成与 EMMC/SD 卡块设备的物理握手。
    initr_env(核心动作:env_relocate):

    块设备通道一旦打通,这个函数立刻执行。它直接奔向 EMMC 上的特定物理扇区(编译时固化的偏移量),把用户保存的、用来引导内核的 bootcmd(启动命令)和 bootargs(内核传参配置)环境变量全量读回内存,覆盖掉出厂写死的默认值。没有这一步,U-Boot 根本不知道去哪里找 Linux 内核。

  5. 控制流的结束:run_main_loop

    这是整个 init_sequence_r 线性初始化流水线的结尾,也是整个 U-Boot 生命周期的终点。
    物理动作:

    它彻底掐断了被动的初始化逻辑,让控制流进入 main_loop() 的死循环中。
    分流:

    在 main_loop 内部开始倒计时(Bootdelay)。
    如果倒计时期间用户按了回车:

    则停留在 U-Boot 命令行 Shell 交互界面(=>),交出主权供工程师调试。
    如果倒计时归零且无干预:

    它会把刚从 EMMC 读出的 bootcmd 字符串放入命令解析器,直接开启 bootm 流程,去加载、校验 Linux 内核镜像,并将设备树物理地址打入 r2 寄存器,强行把主权移交给 Linux 内核。U-Boot 自此完成使命。

在 init_sequence_r 数组里,绝大多数函数(如 initr_malloc、initr_dm、initr_serial)都是 U-Boot 官方写好的、全部芯片通用的公共代码。

board_init 不同,是板级特定函数(Board-Specific Function)。
谁来写?

U-Boot 官方不管这个函数的具体内容,它是由 芯片厂商(如 NXP)或自己的硬件团队 亲自手写的。
在哪找?

对于 i.MX6ULL,它的物理源码隐藏在 board/freescale/mx6ullevk/mx6ullevk.c(或者是你们定制板子的对应 C 文件)中。

本质角色:它是 U-Boot 通用底层框架,专门留给具体硬件工程师的"硬件清场底座接口"。

在其内部,系统会控制底层寄存器,完成了以下系统级合围:

1.确立 ATAGS 传参位置:

强行指定 gd->bd->bi_boot_params = PHYS_SDRAM + 0x100;,为后续向 Linux 内核交接数据埋下物理伏笔。

2.引脚电气属性锁死:

配置 IOMUXC 矩阵,全盘定义 EMMC、串口等核心外设引脚的上下拉电阻与驱动电流,让乱飞的物理引脚归位。

3.强推外设硬件复位:

通过控制 GPIO 电平的物理跳变,对板载的网络 PHY 芯片(如 LAN8720A)实施硬核的掉电复位,直接决定了后续 initr_net 网络功能能否顺利启动。

12. run_main_loop

在 init_sequence_r 的最后会进入 run_main_loop:

c 复制代码
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;
}

CONFIG_SANDBOX 是U-Boot 官方为了方便开发者在没有硬件板卡的情况下,直接在 Linux 电脑(X86 宿主机)上模拟运行、测试 U-Boot 核心逻辑的一种虚拟环境。
对于 i.MX6ULL:

我们跑的是真实的物理芯片,这个宏在 BSP 编译时根本不会开启。
代码意图:

控制流在这里彻底告别了过去的"线性执行"(执行完一个函数接着执行下一个),而是通过一个绝对不可逆的 for( ;; ) 死循环,把 CPU 的控制权死死扣留在 main_loop() 内部。
为什么用死循环?

注释说得很明白:"main_loop() 可能会因为某些异常重试自动启动(Autoboot)而返回,如果它返回了,那就再次运行它。" 这是一个高级的防御性容错设计。在极端恶劣的工业现场,如果自动引导内核中途因为突发电气干扰失败掉出来了,死循环能保证 U-Boot 不会崩溃跑飞,而是重新跌回 main_loop 重新侦听命令或尝试二次引导,给系统留下一线生机。

关系总结:

  1. init_sequence_r 是线性流水线:
    它是一个包含二十多个函数指针的数组,系统像滚轮一样,从头到尾、不回头地顺次依次执行各个外设的初始化。
    内部做了什么: 重新使能 Cortex-A7 的高速 Cache 与 MMU 页表;物理圈定外部 DDR 空间构建全功能大容量的 Malloc 堆;全量扫描设备树(DTB)镜像构建现代驱动模型(DM);打通 USDHC 块设备通道并执行 env_relocate() 迎回环境变量;硬核掉电复位板载以太网 PHY 芯片。
    核心目的: 承接汇编时代的拓荒成果,在空间海量的外部 DDR 内存中,从零开始搭建起一套完整、合法且高效率的 C 语言全功能业务运行底座。
  2. run_main_loop :
    当 init_sequence_r 数组的最后一个指针被调用时,指向的正是 run_main_loop()。它内部直接切断了线性的逻辑。
  3. main_loop() 是死循环:
    run_main_loop 内部用一记冷酷的 for( ;; ) 死循环把控制流死死扣留在 main_loop() 内部。至此,CPU 不再被动地接受初始化指令,而是主动陷入了无限的命令轮询和事件监听中。
    内部做了什么: 启动 bootstage 节点时间戳打卡统计;调用 cli_init() 初始化 U-Boot 独有的命令解析器(如 Husk Shell);引爆 Bootdelay 倒计时并通过串口寄存器疯狂轮询捕获键盘的输入事件。
    核心目的: 履行引导程序的最终目的 ------ 实现软硬件主权的终局变轨。
    若倒计时期间检测到用户敲击回车,则常驻 Shell 交互界面供工程师调试;
    若无人干预,则直接将抢回来的 bootcmd 丢进命令解析引擎,强行呼叫 bootm 流程去解构 Linux 内核,将机器最高控制权移交出去,随后宣告自我解体。

13. main_loop

c 复制代码
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
	const char *s;

	bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

#ifdef CONFIG_VERSION_VARIABLE
	setenv("ver", version_string);  /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */

	cli_init();

	run_preboot_environment_command();

#if defined(CONFIG_UPDATE_TFTP)
	update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */

	s = bootdelay_process();
	if (cli_process_fdt(&s))
		cli_secure_boot_cmd(s);

	autoboot_command(s);

	cli_loop();
	panic("No CLI available");
}

核心函数分析:

  1. cli_init()
    内部做了什么:

    初始化 U-Boot 的命令行接口(CLI,Command Line Interface),比如现代 U-Boot 最常使用的、类似于 Linux Bash 语法的 Hush Shell 引擎。它会初始化内建命令列表的查找表,准备好命令历史缓冲区。
    目的:

    只有这一步执行完,系统后续才具备了解析字符串并将其翻译为底层驱动控制命令(如 md, mw, tftp)的能力。

  2. run_preboot_environment_command()
    内部做了什么:

    它会去检查环境变量中是否存在一个叫 preboot 的特殊环境变量。如果存在,就会在正式启动倒计时之前,率先把 preboot 里面的命令字符串砸给解析器执行一遍。
    工业级应用:

    比如,在某些特定硬件上,要求系统在任何启动动作(甚至倒计时)之前,必须先点亮某颗特殊的安全芯片或进行某种关键物理电平检测,工程师就会把这组动作写进 preboot 中。

  3. s = bootdelay_process();
    内部做了什么:

    它会从已经迁移到 DDR 的环境变量账本中提取两个最关键的数字:

    bootdelay(倒计时秒数,比如 3 秒)和 bootcmd(默认启动命令串)。
    物理本质:

    它会在控制台打印出那句标志性的:Hit any key to stop autoboot: 3。在这几秒内,系统开始轮询串口的 RX 接收寄存器。

    用户按下了回车:函数会立刻拦截,并返回 NULL (即指针 s = NULL)。

    倒计时安全归零:无人干预,函数则会返回 bootcmd 的字符串指针。

  4. autoboot_command(s);
    内部做了什么:

    它接过了上一步拿到的指针 s。

    如果 s 是 NULL(说明用户在倒计时按键了),这个函数内部什么都不做。

    如果 s 包含 bootcmd(说明倒计时归零无人干预),它就会立刻把这一长串启动命令(例如:run findfdt; mmc dev 1; bootz 0x80800000 - 0x83000000)写入解析器。
    命运交接:

    在正常量产出厂的设备中,这里会一路点亮 EMMC、加载内核镜像和设备树,最终利用 bootm,彻底将主权移交给 Linux 内核,U-Boot 在这里完成使命。

  5. cli_loop();
    内部做了什么:

    如果上一步的 autoboot_command(s) 因为用户干预没有执行,或者因为EMMC 里的内核镜像损坏导致自动启动失败返回,控制流就会进入到最后一站 ------ cli_loop()。
    物理本质:

    它会在串口终端彻底吐出我们极其熟悉的 => 命令行提示符。从此,系统进入无限的交互监听状态,静待你在终端输入任何调试指令。
    安全闭环:

    代码最后一行写着 panic("No CLI available");。这意味着如果连最后的命令行交互引擎都崩溃了,系统将直接陷入内核恐慌,彻底死锁。

无人干预时:

autoboot_command 把 bootcmd 这一长串字符串(如 run findfdt; mmc dev 1; bootz 0x80800000 - 0x83000000)存入解析器 run_command_list() 时,会发生以下场景:

  1. 第一阶段:

    字符串的"解构与斩线"(命令拆解)输入进来的 bootcmd 是一个长长的复合字符串,中间用分号 ; 隔开。
    拆分:

    命令解析器(如 Hush Shell)拿到字符串后,首先把分号 ; 视为断句标志,将其拆分为一条条独立的单体命令(例如先执行 mmc dev 1,再执行 bootz ...)。
    查表匹配:

    对于拆出来的每一条命令,解析器会去 U-Boot 内部一块内存区域------内建命令表(Linker List)中去翻账本,通过字符串匹配找到对应的底层 C 语言函数指针。

    匹配到 mmc → \rightarrow → 对应执行 cmd/mmc.c 里的 do_mmc() 函数。

    匹配到 bootz → \rightarrow → 对应执行 cmd/bootz.c 里的 do_bootz() 函数。

  2. 第二阶段:高级外设的数据加载

    命令匹配成功后,U-Boot 开始疯狂压榨硬件,把启动所需的信息全部从非易失性存储介质(EMMC/SD卡)搬移到大容量的外部 DDR 内存中。
    切换通道(mmc dev 1):

    调用 USDHC 驱动,将数据总线切换到 EMMC 芯片上。
    信息迁移(紧接着会执行 mmc read ... 或类似捞内核的命令):

    驱动开始对 EMMC 进行块级别的物理读取,跨越寻址线,把两个关键镜像强行写入 DDR 的指定物理地址:

    Linux 内核镜像(zImage) → \rightarrow → 写入 DDR 的 0x80800000 地址。

    设备树镜像(DTB) → \rightarrow → 写入 DDR 的 0x83000000 地址。

  3. 第三阶段:物理变轨

    当解析器解析到最后一条命令 bootz 0x80800000 - 0x83000000 并调用 do_bootz() 时,整个 U-Boot 开始实施自我解体与主权移交:
    镜像验明身份:

    do_bootz 率先去读取 0x80800000 处的内核头部魔数(Magic Number),确认这不是一段垃圾代码,而是一颗正统的 Linux 芯片。
    彻底清场(cleanup_before_linux):

    在临终前,U-Boot 必须让整个芯片回到最纯净的原始状态。它会强行关闭硬件中断、关闭 D-Cache(数据缓存)。因为如果带着 U-Boot 时代残留的缓存和中断跑进 Linux,Linux 内核会直接崩溃。
    软硬件生命线交接(汇编跳转):

    系统在底层通过函数指针,强行执行一段 ARM 汇编指令(通常是一句 bx 或 mov pc 跳转指令)。在跳进 Linux 内核入口的一瞬间,U-Boot 完成了硬件寄存器的数据存放:

    把值 0 砸进 r0 寄存器。

    把机器 ID(Machine ID)砸进 r1 寄存器。
    【重点】 把设备树(DTB)在 DDR 中的物理首地址(0x83000000)强行死死写入 ARM 架构的 r2 寄存器

  4. 总结:跳转汇编指令一旦执行,CPU 的程序计数器(PC 指针)瞬间指向了 Linux 内核的第一行机器码。Linux 内核的汇编启动代码立刻去 r2 寄存器 里读出 0x83000000 坐标,从此开启了 Linux 视角的设备树动态解析与全盘接管。

    而由 init_sequence_r 搭建起来的、main_loop 保护的的整个 U-Boot 在这一刻直接在物理内存中被就地宣告非法并彻底抹零、全盘解体。U-Boot 至此完成了它所有的历史宿命。

相关推荐
吃好睡好便好3 小时前
用for循环语句求和
开发语言·人工智能·学习·matlab·学习方法
玄米乌龙茶1233 小时前
LLM成长笔记(五):提示词工程与模型调用
人工智能·笔记
Bert.Cai3 小时前
Linux let命令详解
linux·运维·服务器
枕星而眠4 小时前
Linux 线程:原理、属性、实战与面试避坑
linux·运维·c语言·面试
晚风予卿云月4 小时前
【Linux】环境变量概念、作用、配置与修改详解
linux·运维·服务器·环境变量
不会编程的懒洋洋4 小时前
VisionPro 中 几何相交工具 Geometry-Intersection
图像处理·笔记·c#·视觉检测·机器视觉·visionpro
r-t-H4 小时前
从零开始搭建CDH-第十二章
linux·hive·spark·centos·hbase
_李小白4 小时前
【C++学习笔记】新特性之inline变量
c++·笔记·学习
心中有国也有家4 小时前
hccl 架构拆解:昇腾集合通信库到底在做什么?
人工智能·经验分享·笔记·分布式·算法·架构