【裸机开发】链接脚本(.lds文件)实战:从基础语法到内存布局优化

【裸机开发】链接脚本(.lds文件)的基本语法 阅读详情

1. 链接脚本:裸机开发的“内存规划师”

刚开始玩裸机开发那会儿,我总觉得链接脚本(.lds文件)是个特别神秘的东西。编译、链接这些流程,IDE通常都帮你默默处理好了,你只管写代码就行。直到有一次,我需要在SRAM里跑一段关键代码,在DDR里放数据,结果程序一跑就飞,怎么都找不到原因。折腾了好几天,最后才发现是链接脚本没配好,代码和数据放错地方了。那次经历让我彻底明白,在裸机开发里,链接脚本就是你程序的“内存规划师”,它决定了你的代码、变量最终躺在内存的哪个角落。

简单来说,链接脚本就是告诉链接器(比如GNU的ld):“嘿,你把这些编译好的.o文件里的代码段(.text)、数据段(.data)、未初始化数据段(.bss)等等,按照我画的这张地图,摆到内存的指定位置去。” 没有操作系统帮你管理内存,这个布局就得你自己来操心。它直接关系到你的程序能不能正确加载、能不能高效运行,甚至能不能跑起来。

为什么在嵌入式裸机开发里,这个文件尤其重要?因为资源太紧张了。你的芯片可能内部有几十KB的SRAM,外面挂了几百MB的DDR,还有一块NOR Flash。上电后,代码要从Flash加载到RAM里执行,或者直接在Flash里原地执行(XIP)。不同的内存速度、容量、特性都不一样。链接脚本就是让你能精细地控制,哪段代码放SRAM(快),哪段数据放DDR(大),中断向量表必须放在哪个固定地址。可以说,不懂链接脚本,裸机开发就只学了一半。

2. 拆解.lds:从基础命令到核心结构

一个链接脚本,看起来一堆符号和括号,其实结构很清晰。咱们先从一个最简单的骨架说起,然后往里填肉。

2.1 核心框架:SECTIONS命令

链接脚本的灵魂就是 SECTIONS 命令,所有关于内存布局的“规划”都在这个大括号里完成。它的基本样子是这样的:

SECTIONS
{
    /* 这里就是我们的内存布局规划图 */
    . = 0x80000000; /* 首先,把“当前位置指针”指向内存起始地址 */
    .text : { *(.text) } /* 把所有输入文件的.text段收集起来,放在这里 */
    .data : { *(.data) } /* 接着放.data段 */
    .bss : { *(.bss) }  /* 最后放.bss段 */
}

这个脚本做了三件事:

  1. . = 0x80000000;:这个点号 . 是个定位器计数器,你可以把它想象成一个在内存地址空间里移动的“光标”。这行代码把光标初始位置设在了 0x80000000,通常这就是我们DDR内存的起始地址。
  2. .text : { *(.text) }:定义了一个名为 .text 的输出段。冒号后面的 { *(.text) } 是输入段描述,意思是“把所有输入目标文件(*.o)里的 .text 段,全部收集起来,合并放到我们输出的 .text 段里”。链接器会按照它遇到文件的顺序,把代码段一个一个拼接起来。
  3. 同理,.data 和 .bss 段也是这么处理的。

这只是一个最基础的布局。实际项目中,我们往往需要更精细的控制,比如指定某个特定的.o文件必须放在最前面(比如包含启动代码的文件),或者把不同文件的同一种段分开存放。

2.2 关键命令与内置变量

除了 SECTIONS,还有几个命令出场率极高:

  • ENTRY(symbol):指定程序的入口点。比如 ENTRY(_start) 就告诉链接器,程序的第一条指令从符号 _start 所在的地址开始执行。这在裸机启动时至关重要,因为芯片上电复位后,会从一个固定地址(比如0x80000000)取指执行,这个地址存放的必须是 _start 的代码。
  • MEMORY:这个命令太有用了,它能让你定义内存区域。比如你的芯片内存不是连续一块,有SRAM、有DDR,用这个命令可以清晰划分。
MEMORY
{
    SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
    DDR (rwx)  : ORIGIN = 0x80000000, LENGTH = 512M
}

这里定义了两块内存:SRAM 可读可写可执行,从0x20000000开始,大小128KB;DDR 也是可读可写可执行,从0x80000000开始,大小512MB。定义好后,在 SECTIONS 里就可以用 >SRAM 或 >DDR 来指定某个段放到哪块内存了。

至于内置变量,其实就是那些常见的段名:

  • .text:代码段,存放你的函数指令。
  • .data:已初始化的全局变量和静态变量(初始值非零)。
  • .rodata:只读数据段,比如字符串常量、const修饰的全局变量。
  • .bss:未初始化或初始化为零的全局变量和静态变量。这个段在elf文件里不占空间,但运行时需要在内存中预留位置并清零。
  • .heap 和 .stack:堆和栈段,在更复杂的脚本里需要手动规划。

3. 实战:优化内存布局的三大技巧

懂了基础语法,咱们来点实战的。怎么让链接脚本真正为你的项目服务,提升性能和可靠性?我分享三个最常用的技巧。

3.1 地址对齐:让CPU跑得更快

内存控制器访问内存时,通常有对齐要求。比如32位ARM CPU,一次总线访问读取4字节(32位)效率最高。如果你的数据地址是0x80000001,CPU想读一个4字节整数,就得先读0x80000000-0x80000003,再读0x80000004-0x80000007,然后拼凑出你想要的数据,这太慢了。

链接脚本里用 ALIGN() 函数来解决这个问题。看这个例子:

SECTIONS
{
    . = 0x80000000;
    .text : {
        *(.text)
    }
    . = ALIGN(4); /* 确保当前位置是4字节对齐的 */
    .rodata : {
        *(.rodata)
    }
    .data ALIGN(8) : { /* 也可以直接跟在段名后面 */
        *(.data)
    }
    . = ALIGN(4);
    .bss : {
        *(.bss)
    }
}

假设 .text 段结束在地址 0x80001003。如果没有 . = ALIGN(4);,.rodata 段就会紧接着从 0x80001003 开始,这显然不是4字节对齐。加了 ALIGN(4) 后,链接器会把当前位置“垫高”到下一个4的整数倍地址,也就是 0x80001004,然后再开始放置 .rodata。.data 段后面的 ALIGN(8) 意思也一样,保证 .data 段的起始地址是8字节对齐的。

对齐不仅关乎速度,在某些架构下,不对齐的访问甚至会引发硬件异常(比如ARM的某些模式)。所以,在关键段(尤其是代码段和中断向量表)前后加上对齐操作,是个好习惯。

3.2 多内存区域管理:SRAM与DDR的协同作战

这是嵌入式裸机开发的核心场景。SRAM速度快,但容量小;DDR容量大,但速度相对慢,而且需要初始化后才能用。合理的分配策略是:把对性能最敏感、最关键的代码(比如启动代码、中断服务程序)放到SRAM;把大量的只读数据、代码和全局变量放到DDR。

结合前面提到的 MEMORY 命令,我们可以这样写:

MEMORY
{
    SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
    DDR (rwx)  : ORIGIN = 0x80000000, LENGTH = 512M
}

SECTIONS
{
    /* 第一阶段:在SRAM中执行的启动代码和中断向量表 */
    .isr_vector 0x20000000 : {
        KEEP(*(.isr_vector)) /* KEEP确保即使未被引用,该段也不会被优化掉 */
    } >SRAM

    .fast_code : {
        *(.fast_code) /* 你可以通过GCC属性将特定函数放到这个自定义段 */
        *startup.o(.text) /* 确保startup.o的代码在SRAM里 */
    } >SRAM

    /* 第二阶段:主程序放到DDR */
    .text : {
        *(.text)
        *(.text.*)
    } >DDR

    .rodata : {
        *(.rodata)
        *(.rodata.*)
    } >DDR

    .data : {
        _data_start = .;
        *(.data)
        *(.data.*)
        _data_end = .;
    } >DDR AT>SRAM /* 注意这里!VMA在DDR,但LMA在SRAM */

    .bss (NOLOAD) : { /* NOLOAD表示该段不加载到镜像文件,仅预留地址空间 */
        _bss_start = .;
        *(.bss)
        *(.bss.*)
        *(COMMON)
        _bss_end = .;
    } >DDR
}

这里有几个关键点:

  1. >SRAM 和 >DDR:指定输出段存放在哪块内存区域(VMA,虚拟内存地址/程序运行地址)。
  2. AT>SRAM:这是一个高级用法。它指定了加载地址(LMA)。对于 .data 段,它的运行时地址(VMA)在DDR(>DDR),但它的初始值在镜像文件里,需要被加载到SRAM(AT>SRAM)这个临时位置。为什么?因为DDR在系统刚启动时可能还没初始化!你的启动代码需要先把 .data 段的初始值从SRAM(或Flash)拷贝到DDR的最终位置。这通过 _data_start, _data_end 这些符号在C代码里实现。
  3. 自定义段:像 .fast_code,你可以通过GCC的 __attribute__((section(".fast_code"))) 把某个函数标记为需要放到快速内存中。
  4. 符号赋值:_bss_start = .; 这行代码创建了一个全局符号 _bss_start,它的值等于当前位置计数器 . 的值,也就是.bss段的起始地址。在C代码里声明 extern uint32_t _bss_start;,就可以在启动代码中用这个地址来清零.bss段了。

3.3 自定义符号与段:实现高级功能

链接脚本不只是被动地安排内存,还能主动创建符号和定义特殊段,实现一些“黑魔法”。

场景一:精确获取段边界。 前面已经看到了 _bss_start 和 _bss_end 的用法。对于.data段也一样,我们需要知道它的加载地址(LMA,在Flash/SRAM里)和运行地址(VMA,在DDR里),才能在启动时完成数据搬运。

.data : {
    _data_vma_start = .;    /* 在DDR中的起始地址 (VMA) */
    *(.data)
    _data_vma_end = .;
} >DDR AT>FLASH /* 假设从Flash加载 */

_data_lma_start = LOADADDR(.data); /* 获取.data段在Flash中的加载地址 (LMA) */

在C启动文件中,你就可以这样写:

extern uint32_t _data_vma_start, _data_vma_end, _data_lma_start;
void copy_data_section(void) {
    uint32_t *src = &_data_lma_start;
    uint32_t *dst = &_data_vma_start;
    while (dst < &_data_vma_end) {
        *dst++ = *src++;
    }
}

场景二:构建初始化函数表。 这是一个在RTOS(如RT-Thread)中广泛使用的技巧,用于实现组件的自动初始化,而不需要硬编码调用顺序。

首先,在C头文件中定义一个宏:

#define INIT_EXPORT(fn, level) \
    __attribute__((used, section(".init_fn." level))) const init_fn_t _init_##fn = fn

然后,在不同的模块初始化函数上使用不同的宏:

INIT_EXPORT(board_init, "1"); // 板级初始化,优先级1
INIT_EXPORT(device_init, "3"); // 设备初始化,优先级3

最后,在链接脚本里,把这些分散在各处的函数指针收集到一个连续的数组中:

.init_array : {
    __init_array_start = .;
    KEEP(*(SORT(.init_fn.*))) /* SORT会按段名后的数字排序! */
    __init_array_end = .;
} >SRAM

系统启动时,只需要从 __init_array_start 遍历到 __init_array_end,依次调用这些函数指针即可。新增模块完全不需要修改主初始化流程,链接器帮你完成了“注册”。

4. 避坑指南:那些年我踩过的链接脚本的“坑”

语法看起来简单,但实际写起来,坑可不少。我总结了几类最常见的问题。

第一坑:地址重叠。 这是最致命也最隐蔽的错误。比如你算错了某个段的大小,或者没考虑对齐填充,导致.text段和.data段地址重叠了。程序可能能加载,但一运行就数据错乱。务必在链接后查看生成的map文件(GCC加 -Wl,-Map=output.map 选项)。map文件里详细列出了每个段、每个符号的最终地址和大小,是排查内存布局问题的终极武器。

第二坑:忘记清零.bss段。 链接脚本只负责告诉程序.bss段在哪里、有多大,但不会自动帮你清零。如果启动代码里漏了这一步,那么未初始化的全局变量就会是随机值,程序行为不可预测。记住,清零.bss是C运行时环境初始化必不可少的一步。

第三坑:启动文件顺序错误。 包含中断向量表和最开始几条跳转指令的启动文件(通常是 startup.o 或 vectors.o),必须放在输出.text段的最前面。因为芯片上电后,是从内存起始地址取指令执行的。如果你的链接脚本是 *(.text),那么链接器按什么顺序链接.o文件是不确定的。正确做法是显式指定:

.text : {
    startup.o (.text) /* 确保第一 */
    *(.text)
}

第四坑:对齐导致的空洞浪费。 为了对齐,链接器可能会在段与段之间插入填充字节。在内存紧张的单片机环境,这可能浪费几百字节甚至几K的空间。你需要权衡性能和空间。有时可以调整段的顺序,让大小相近的段放在一起,减少对齐造成的总空洞。也可以使用 SUBALIGN 来指定子对齐,但需谨慎。

第五坑:跨域访问未考虑。 比如你把代码放在0x00000000开始的Flash区域(XIP执行),但把.data段放在了0x20000000的SRAM。如果你的代码里有一条直接寻址访问.data的指令,而CPU的地址总线或内存控制器不支持从0x00000000地址直接访问0x20000000的区域(可能需要特殊的重映射或配置),那么这条指令就会失败。在设计内存布局时,必须结合芯片的内存映射图来考虑。

5. 进阶:链接脚本的调试与验证

写好了链接脚本,怎么知道它工作得对不对呢?光编译通过可不行。

首要工具:Map文件。 前面提过,用 -Wl,-Map=project.map 生成map文件。打开它,重点看这几部分:

  1. Memory Configuration:确认你定义的 MEMORY 区域是否正确。
  2. Linker script and memory map:这是核心。找到你的输出段(比如 .text, .data),看它们的起始地址(VMA)和结束地址是否符合预期,是否在正确的内存区域。检查关键符号(如 _bss_start, _etext)的地址。
  3. 检查是否有任何段超出了你定义的 MEMORY 区域长度。

辅助工具:objdump和readelf。 生成elf文件后,可以用 arm-none-eabi-objdump -h project.elf 查看各段头部信息,包括大小、VMA、LMA。用 arm-none-eabi-readelf -s project.elf 可以查看符号表,确认你定义的链接脚本符号(如段边界符号)是否存在且地址正确。

终极测试:写个测试用例。 在程序最开始,打印出关键符号的地址:

extern char _text_start, _text_end, _data_start, _data_end, _bss_start, _bss_end;
printf("Text: %p - %p\n", &_text_start, &_text_end);
printf("Data: %p - %p\n", &_data_start, &_data_end);
printf("BSS:  %p - %p\n", &_bss_start, &_bss_end);

看看输出是否和你链接脚本里设计的一致。同时,可以在这几个段的前后设置“警戒值”(比如0xDEADBEEF),运行一段时间后检查这些值是否被意外修改,以检测栈溢出或数组越界等问题。

6. 不同架构下的细微差异

虽然GNU ld的链接脚本语法是通用的,但不同架构的芯片,其内存映射、启动方式可能不同,链接脚本也需要相应调整。

以ARM Cortex-M系列为例:它的中断向量表必须放在Flash起始地址(通常是0x08000000)。向量表的第一项是初始栈指针(MSP)的值,第二项是复位向量(Reset_Handler)。所以链接脚本里,必须确保包含向量表的那个.o文件(比如 startup_stm32fxxx.o)的 .isr_vector 段放在最前面。

MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
    SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS
{
    .isr_vector : {
        KEEP(*(.isr_vector))
    } >FLASH

    .text : {
        *(.text)
    } >FLASH
    /* ... 其他段 */
}

而对于像i.MX RT这样的跨界处理器,它可能没有内部Flash,程序放在外部QSPI Flash中,但希望全速在内部的ITCM(指令紧耦合内存)运行。这就涉及到XIP(就地执行)和代码重定位。链接脚本会更复杂,需要定义两个.text段:一个在Flash(LMA),一个在ITCM(VMA),并在启动代码中将代码从Flash拷贝到ITCM。

RISC-V架构可能又有所不同,但核心思想不变:根据芯片手册的内存映射,在 MEMORY 中定义好区域,在 SECTIONS 中根据启动流程和性能需求,合理安排各个段的VMA和LMA。

说到底,链接脚本没有一成不变的模板。最好的学习方式就是找一块开发板,从最简单的“Hello World”裸机程序开始,尝试修改链接脚本,把代码段、数据段放到不同的地址,然后观察程序行为,同时结合map文件和反汇编(objdump -d)来理解每一步修改带来的变化。这个过程可能会遇到各种奇怪的问题,但每解决一个,你对程序如何从源代码变成在内存中运行的二进制映像的理解就会加深一层。当你能够根据芯片手册和项目需求,熟练地定制出最合适的内存布局时,你会发现,链接脚本这个“内存规划师”,已经成了你嵌入式开发工具箱里一件非常得心应手的利器。

【裸机开发】深入解析.lds链接脚本:从基础语法到实战应用 本文深入解析.lds链接脚本在裸机开发中的关键作用,从基础语法到实战应用全面讲解。通过ENTRY指令、MEMORY命令和SECTIONS配置等核心语法,帮助开发者精确控制内存布局,解决裸机开发中的常见问题,提升嵌入式系统开发效率。 阅读详情

相关推荐

嵌入式开发必备:手把手教你编写高效.lds链接脚本(附常见错误排查)

本文深入解析了嵌入式开发中.lds链接脚本的编写与调试技巧。针对裸机开发等场景,详细讲解了链接脚本的语法、内存布局规划、数据段搬运机制,并提供了多核共享内存、性能优化等实战案例与常见错误排查方法,帮助开发者构建高效可靠的嵌入式系统。

weixin_29166013的博客 272

嵌入式开发必备:手把手教你编写.lds链接脚本(附内存对齐技巧)

本文深入解析了嵌入式开发中.lds链接脚本的编写方法,是裸机开发等场景下控制程序内存布局的核心技能。文章从基础语法讲起,通过实例详细说明了如何定义内存区域、合并与放置程序段,并重点剖析了内存对齐技巧对系统性能与稳定性的关键影响,帮助开发者编写出高效、健壮的链接脚本。

cherry的博客 897

PowerPC嵌入式开发:CodeWarrior与GCC构建工具链配置实战指南

在嵌入式系统开发中,构建工具链是连接高级语言与底层硬件的核心桥梁,它通过编译器和链接器将源代码转换为目标硬件可执行的机器指令。其工作原理涉及指令集架构适配、内存布局优化和二进制接口规范,技术价值在于直接决定了代码的执行效率、内存占用和系统稳定性。在嵌入式Linux、实时操作系统和裸机应用等场景中,针对特定处理器架构(如Power Architecture)的优化配置尤为关键。本文聚焦PowerPC处理器开发,深入解析CodeWarrior与GCC两套主流工具链的配置逻辑,涵盖处理器选型、内存映射、ABI规范

weixin_30613343的博客 319

从编译原理到实战:图解.lds链接脚本如何影响程序内存分布

本文深入解析.lds链接脚本在程序内存分布中的关键作用,特别适用于裸机开发场景。通过图解和实例代码,详细讲解链接脚本语法、内存区域划分及常见问题排查方法,帮助开发者优化嵌入式系统性能,确保代码高效运行。

dapp9builder的博客 603

嵌入式系统启动全解析:从Bootloader源码到固件升级实战

在嵌入式系统开发中,系统启动流程是连接硬件与软件的关键基础。其核心在于Bootloader,它作为系统上电后执行的初始软件,负责初始化硬件、建立运行环境并加载最终应用程序或操作系统。从原理上看,Bootloader通常采用多级引导架构,例如一级ROM Code完成最基础的硬件初始化,二级Bootloader(如SPL)则负责初始化外部内存等复杂硬件,为后续完整引导程序(如U-Boot)的运行铺平道路。这一过程的技术价值在于确保了系统从冷启动到应用运行的可靠性与灵活性,是嵌入式设备稳定工作的基石。其应用场景广

weixin_34204722的博客 594

MC68HC912B32评估板开发指南:从硬件连接到汇编编程实战

在嵌入式系统开发领域,评估板是工程师评估微控制器(MCU)性能、验证硬件设计的关键工具。其工作原理在于提供一个接近芯片厂商参考设计的纯净硬件平台,允许开发者直接访问MCU的所有引脚和资源,从而进行精准的底层硬件交互和软件调试。这种直接控制硬件的技术价值在于,它能帮助开发者透彻理解芯片的时序、电气特性和外设行为,为产品原型设计提供可靠的验证基础,有效规避后期风险。典型的应用场景包括汽车电子、工业控制等对可靠性和成本敏感的领域。本文以经典的M68EVB912B32评估板为例,详细解析其硬件构成、固件(D-Bug

weixin_30530939的博客 403

ARM--SDK、led、beep与链接脚本

IMX6ULL 芯片的寄存器物理地址可通过芯片手册查询,C 语言中通过 “指针强制转换” 将物理地址映射为可操作的变量。// 1. 时钟控制寄存器(CCM):控制外设时钟使能#define CCM_CCGR0 *((volatile unsigned int *)0x020C4068) // 时钟门控寄存器0#define CCM_CCGR1 *((volatile unsigned int *)0x020C406C) // 时钟门控寄存器1。

CQ_YM的博客 998

基于GCC/Eclipse的CC26xx/CC13xx无线MCU开发环境搭建与调试指南

在嵌入式系统开发中,GNU工具链(GCC/GDB)与集成开发环境(IDE)的结合,为开发者提供了跨平台、开源且高度可控的解决方案。其核心原理在于通过编译器将C/C++源代码转换为目标处理器的机器码,链接器则依据内存布局脚本将代码与数据分配到Flash和RAM的指定区域。这种技术方案的价值在于实现了对编译、链接过程的完全掌控,便于代码体积优化和内存布局分析,尤其适合追求开发流程透明度和深度定制的场景。在物联网和无线传感网络应用中,德州仪器(TI)的CC26xx/CC13xx系列ARM Cortex-M3无线微

weixin_30522183的博客 360

硬件开发2-ARM裸机开发2-I.MX6ULL-main点灯

本文介绍了基于ARM架构的嵌入式开发流程,重点讲解了LED控制程序的实现与优化过程。开发过程从编写启动文件start.S开始,包含异常向量表配置、模式切换和栈指针初始化。通过main函数调用外设接口控制LED,使用volatile关键字防止编译器优化。文章详细说明了Makefile的编译规则、交叉编译工具链配置以及SP寄存器的作用。随后展示了代码优化过程,包括时钟配置、硬件寄存器定义分离、功能模块封装等。最后介绍了BSP工程管理和链接脚本(.lds)的编写要点,确保启动代码的正确执行顺序和内存布局管理。整个

DebugKitty的博客 993

利用Android系统漏洞实现后台弹窗(外弹)

关键:从 Android 8.0(API 26)开始,禁止使用 context.sendBroadcast() 发送隐式广播(即没有指定 setPackage() 或明确组件的广播)。demo核心代码展示利用Android显示系统和广播系统的漏洞实现在后台弹窗的功能,支持最新Android15,16版本。核心功能都是在so层去做,大量使用了jni反调, 如果要上架到应用市场,需要对代码做进一步混淆,加固以应对市场安全扫描。3,开启虚拟显示窗口,这是个透明像素窗口。5,可开启一个线程,调用这个外弹窗口。

2018,再出发~ 1317

国产万卡集群的软硬协同实战:从MFU到分布式训练调优

大模型训练的算力需求往往被简单等同于显卡数量,但真正决定训练效率的是有效算力而非硬件峰值。MFU作为衡量模型浮点利用率的指标,反映着集群在并行策略、通信拓扑和算子适配等环节的综合表现。分布式训练中的张量并行、流水线并行与集群网络拓扑的匹配程度,直接影响通信耗时与训练稳定性。在国产算力加速落地的背景下,昇腾、海光等芯片的软件栈与通信库调优成为提升训练吞吐的关键。万卡集群的规划与运维涉及存储带宽、故障诊断、检查点恢复等工程细节,中小团队也可通过小规模集群演练积累软硬协同经验。本文从分布式训练的基础原理出发,结合

weixin_29057065的博客 203

uboot源码分析五 启动流程分析一 链接脚本 u-boot.lds

链接脚本 u-boot.lds OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { . = 0x00000000; . = ALIGN(4); .text : { *(.__image_copy_start) ...

monkea123的博客 743

Ubuntu单系统安装全攻略:从删除Windows到UEFI引导设置(避坑指南)

本文提供Ubuntu单系统安装的完整指南,涵盖从安全删除Windows系统到UEFI引导设置的详细步骤。通过专业工具准备、分区方案设计、引导加载器配置等关键环节,帮助用户避免常见安装问题,实现纯净高效的Ubuntu单系统环境。特别强调UEFI引导的深度配置与安装后的性能优化,适合希望彻底转向Linux的用户。

weixin_29210727的博客 411

从单片机到Linux内核:揭秘链接脚本在Bootloader中的高级玩法

本文深入探讨了链接脚本(.lds文件)在Bootloader和Linux内核开发中的高级应用。与裸机开发中简单的静态布局不同,高级玩法涉及多阶段启动、位置无关码混合、动态入口点、缓存行对齐优化,以及利用自定义符号实现驱动初始化自动化。掌握这些技巧是理解复杂系统启动流程的关键。

terraform7cloud的博客 181

LABVIEW学习之MODBUS-TCP通讯

LABVIEW学习之MODBUS-TCP通讯 1,MODBUS-TCP通讯介绍: MODBUS通讯是工业上面经常用到的一种通讯协议。该协议的原理及介绍不在此累述,下面直接介绍LABVIEW对此协议的应用。 2,MODBUS通讯所需的函数: 安装了DSC模块之后可以在数据通信内找到相关函数 3 ,主VI 主要思路就是使用状态机来进行连接 读取 写入 断开。使用枚举类型来选择状态: 1,连接 2,写入多个M继电器: 3,写入单个M继电器 4,写入双字节D...

风动9527的博客 2万+

基于555定时器的D类功放设计:从PWM原理到无反馈电路实践

D类功放(数字功放)是一种高效率的音频放大技术,其核心原理是脉冲宽度调制(PWM)。它将输入的模拟音频信号转换为一系列高速开关的脉冲,通过控制功率开关管(如MOSFET)的通断,再经低通滤波器还原放大后的音频。相比传统AB类功放,其转换效率通常超过90%,显著降低了热能损耗。这项技术在追求高能效和小型化的现代音频设备中价值凸显,广泛应用于有源音箱、汽车音响和便携式设备。本文聚焦于一种独特的实现方案:以经典的555定时器芯片为核心,构建一个无整体负反馈的D类功放。通过剖析其PWM调制器、FET电压-电流转换级

weixin_30162121的博客 226

C 语言实现 I.MX6ULL 点灯(续上一篇)、SDK、beep及bsp工程管理

通过#define。

m0_74317917的博客 1152

sysrepo 开发者指南(一个基于libyang与支持rpc的数据库)

sysrepo 开发者指南(一个基于libyang与支持rpc的数据库) 日志记录 默认情况下,所有 Sysrepo 日志记录都处于关闭状态。如果启用,日志消息有 3 种输出。它们可以写入stderr、由 处理syslog或简单地传递给回调。 请务必注意,启用 Sysrepo 日志记录也会修改libyang日志记录设置。错误消息被视为标准 Sysrepo 错误,其他严重性较低的消息完全隐藏。要更改此行为,只需在启用 Sysrepo 日志记录后调整libyang日志记录设置。 此外,几乎每个 Sysrepo

weixin_43489394的博客 9231

C程序编译链接全流程解析:从源码到嵌入式芯片执行的底层原理

程序编译是将高级语言转换为机器可执行代码的核心技术流程,其本质是通过多阶段处理实现语义转换与硬件适配。从预处理展开宏定义和头文件,到编译器进行词法语法分析、语义检查及中间代码优化,再到汇编器生成可重定位目标文件,每一步都为最终机器码生成奠定基础。链接器则通过符号解析与地址重定位,将分散的目标文件与库函数整合为完整可执行程序,并依据链接脚本完成内存布局分配。在嵌入式开发中,理解编译链接全过程对于调试程序运行错误、优化代码体积与性能至关重要,特别是处理全局变量初始化、中断向量表配置等底层问题时,掌握链接脚本编写

weixin_30655569的博客 416

电压比较器---滞回比较器(施密特触发器)

当输入电压从小电压开始增加,当其大于VT+时,电压比较器输出低电平,此时阈值电压变成下门限电压(VT-);当输入电压从大电压开始减小,当其小于VT-时,电压比较器输出高电平,此时阈值电压变成上门限电压(VT+);只有当第一次超过上门限电压或者下门限电压值,电压比较器的输出才会翻转变化;Uo为比较输出电压,有两种电压状态VoH(高电平)和VoL(低电平);当Uo输出为高时,电压比较器的阈值电压是上门限电压(VT+);当Uo输出为低时,电压比较器的阈值电压是下门限电压(VT-);Vref为比较参考电压;

weixin_69076249的博客 1万+

基于SSD2828和STM32的MIPI显示屏驱动系统设计与实现

在硬件层面,需要将STM32、SSD2828和MIPI显示屏连接起来。STM32与SSD2828的连接:通过SPI接口连接STM32和SSD2828,用于配置和控制SSD2828。SSD2828与MIPI显示屏的连接:通过MIPI DSI接口连接SSD2828和MIPI显示屏,用于传输显示数据。

qq_38334677的博客 7151

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

本文深度解构了 ARM 架构下 U-Boot 的核心配置文件——链接脚本 u-boot.lds 。文章从“内存摆放”这一底层逻辑出发,详细分析了链接脚本如何为 Makefile 收集到的二进制零件安排绝对物理坐标

kdxiaojie的博客 714

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

嵌入式C程序编译链接与启动过程深度解析:从源码到MCU运行

在嵌入式系统开发中,理解C语言程序如何从源代码转化为可在微控制器上执行的机器码,是掌握底层硬件交互和性能优化的关键。这一过程的核心在于编译与链接两大阶段。编译器将高级C语言通过预处理、词法语法分析、语义分析、中间代码优化和目标代码生成等步骤,转换为特定架构的汇编指令,再经汇编器生成包含代码和数据的二进制目标文件。链接器则扮演着地址空间架构师的角色,它依据链接脚本这一“设计图纸”,通过符号解析与重定位,将多个独立的目标文件及库文件,按照定义的内存区域(如FLASH、RAM)和段(.text、.data、.bs

weixin_30632883的博客 495

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

从Keil到VS Code:ARM开发者必看的“显性化”转型指南

从Keil到VS Code的转型,表面是从"图形界面"到"命令行"的切换,实则是从"依赖工具"到"掌控流程"的思维转变。

xiangaler的博客 1167

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

CC26xx/CC13xx无线MCU GCC/GDB开发环境搭建与调试实战指南

在嵌入式开发领域,GNU工具链(GCC/GDB)因其开源、免费和跨平台的特性,已成为构建自动化工作流和持续集成的基石。其核心原理在于通过交叉编译器将高级语言代码转换为目标架构的机器码,再经由链接器根据内存布局脚本生成可执行文件。这套工具链的技术价值在于提供了对构建过程的完全控制权,便于集成静态分析、单元测试等工程实践。在无线MCU(如TI的CC26xx/CC13xx系列)开发中,结合Eclipse IDE和Make构建系统,可以高效完成从代码编写到烧录调试的全流程。本文聚焦于解决从传统ARM开发环境转向开源

weixin_30340617的博客 423
上一篇: 智能家居DIY:用树莓派+海康摄像头搭建低成本监控系统(支持RTSP推流)
下一篇: AUTO-DL平台高效部署Nuscenes数据集全流程解析
php55
博客等级 码龄15年 51粉丝 1553原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值