基于LinkerScript与__attribute__((section))实现C语言函数调用实战

原创 Linux二进制 2024-09-11 08:20

在嵌入式开发和系统编程中,有时候我们需要对程序的布局进行精细控制,以便更好地管理内存资源。Linux下的链接器(ld)提供了强大的功能,可以让我们在链接阶段定制程序的布局。本文将介绍如何利用 Linker Script 结合 C 语言中的 __attribute__((section)) 属性来实现函数的自定义排列,并通过一个简单的示例展示如何遍历这些函数并调用它们。

一、引言

在编写大型应用程序或嵌入式系统软件时,对程序的内存布局进行优化是非常重要的。通过使用 Linker Script,我们可以精确地控制程序的各个部分如何在内存中布局。此外,C 语言的__attribute__((section)) 属性允许我们将特定的数据或函数放置到指定的section 中。这种技术在实现初始化函数列表、配置选项等方面非常有用。

二、实战演示

为了演示这一技术的应用,我们编写了一个简单的 C 程序,该程序定义了一个结构体来存储命令及其帮助信息,并将这些命令放置在一个自定义的 section 中。我们还将基于默认的Linker Script 来定义这个 section,并在 main 函数中遍历这些命令并调用它们。同时,利用 # 和 ## 定义命令宏,再增加命令时,只需实现命令的功能函数后, 调用 ADD_CMD 宏即可。

源代码 (test.c)如下:

#include 

typedef struct{
const char *name; /* 命令的名字 */
const char *help; /* 帮助信息 */
void (*func)(void); /*命令的功能函数指针*/
} cmd_t;

#define __my_section __attribute__ ((section(".mysection"), aligned(8)))

#define ADD_CMD(name, help, func) \
cmd_t __cmd_##name __my_section = {#name, help, func}

void do_cmd1(void)
{
printf("This is %s function.\n", __func__);
}
ADD_CMD(cmd1, "help of cmd1", do_cmd1);

void do_cmd2(void)
{
printf("This is %s function.\n", __func__);
}
ADD_CMD(cmd2, "help of cmd2", do_cmd2);

extern cmd_t __mysection_start;
extern cmd_t __mysection_stop;

int main(void)
{
cmd_t *p = &__mysection_start;

for(; p < &__mysection_stop; p++)
{
printf("========================\n");
printf("%s\n", p->name);
p->func();
printf("========================\n");
}

return 0;
}

使用 ld -verbose > test.lds 把编译器默认的链接脚本输出到 test.lds 文件,修改链接脚本 (test.lds),增加 __mysection_start 和 __mysection_end 用于记录特定 section 的开始地址与结束地址,*(.mysection) 表示所有的 .o文件的 .mysection 节内容集中放入输出文件的 .mysection 节,如下:

/* Script for -z combreloc: combine and sort reloc sections */
/* Copyright (C) 2014-2018 Free Software Foundation, Inc.
Copying and distribution of this script, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. */
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
"elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("=/usr/x86_64-redhat-linux/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/x86_64-redhat-linux/lib"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib");
SECTIONS
{
/* Read-only sections, merged into text segment: */
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
__mysection_start = . ;
. = ALIGN(8);
.mysection : { *(.mysection) }
__mysection_stop = . ;
.interp : { *(.interp) }
.note.gnu.build-id : { *(.note.gnu.build-id) }
.hash : { *(.hash) }
.gnu.hash : { *(.gnu.hash) }
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.gnu.version : { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }
.rela.dyn :
{
*(.rela.init)
*(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
*(.rela.fini)
*(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*)
*(.rela.data .rela.data.* .rela.gnu.linkonce.d.*)
*(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*)
*(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*)
*(.rela.ctors)
*(.rela.dtors)
*(.rela.got)
*(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*)
*(.rela.ldata .rela.ldata.* .rela.gnu.linkonce.l.*)
*(.rela.lbss .rela.lbss.* .rela.gnu.linkonce.lb.*)
*(.rela.lrodata .rela.lrodata.* .rela.gnu.linkonce.lr.*)
*(.rela.ifunc)
}
.rela.plt :
{
*(.rela.plt)
PROVIDE_HIDDEN (__rela_iplt_start = .);
*(.rela.iplt)
PROVIDE_HIDDEN (__rela_iplt_end = .);
}
.init :
{
KEEP (*(SORT_NONE(.init)))
}
.plt : { *(.plt) *(.iplt) }
.plt.got : { *(.plt.got) }
.plt.sec : { *(.plt.sec) }
.text :
{
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(.text .stub .text.* .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
}
.fini :
{
KEEP (*(SORT_NONE(.fini)))
}
PROVIDE (__etext = .);
PROVIDE (_etext = .);
PROVIDE (etext = .);
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
.rodata1 : { *(.rodata1) }
.eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) }
.eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) }
.gcc_except_table : ONLY_IF_RO { *(.gcc_except_table
.gcc_except_table.*) }
.gnu_extab : ONLY_IF_RO { *(.gnu_extab*) }
/* These sections are generated by the Sun/Oracle C++ compiler. */
.exception_ranges : ONLY_IF_RO { *(.exception_ranges
.exception_ranges*) }
/* Adjust the address for the data segment. We want to adjust up to
the same address within the page on the next page up. */
. = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
/* Exception handling */
.eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) }
.gnu_extab : ONLY_IF_RW { *(.gnu_extab) }
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) }
.exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) }
/* Thread Local Storage sections */
.tdata : { *(.tdata .tdata.* .gnu.linkonce.td.*) }
.tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) }
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
}
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
PROVIDE_HIDDEN (__fini_array_end = .);
}
.ctors :
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
/* We don't want to include the .ctor section from
the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
}
.jcr : { KEEP (*(.jcr)) }
.data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
.dynamic : { *(.dynamic) }
.got : { *(.got) *(.igot) }
. = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
.got.plt : { *(.got.plt) *(.igot.plt) }
.data :
{
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
}
.data1 : { *(.data1) }
_edata = .; PROVIDE (edata = .);
. = .;
__bss_start = .;
.bss :
{
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
/* Align here to ensure that the .bss section occupies space up to
_end. Align after .bss to ensure correct alignment even if the
.bss section disappears because there are no input sections.
FIXME: Why do we need it? When there is no .bss section, we don't
pad the .data section. */
. = ALIGN(. != 0 ? 64 / 8 : 1);
}
.lbss :
{
*(.dynlbss)
*(.lbss .lbss.* .gnu.linkonce.lb.*)
*(LARGE_COMMON)
}
. = ALIGN(64 / 8);
. = SEGMENT_START("ldata-segment", .);
.lrodata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
{
*(.lrodata .lrodata.* .gnu.linkonce.lr.*)
}
.ldata ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)) :
{
*(.ldata .ldata.* .gnu.linkonce.l.*)
. = ALIGN(. != 0 ? 64 / 8 : 1);
}
. = ALIGN(64 / 8);
_end = .; PROVIDE (end = .);
. = DATA_SEGMENT_END (.);
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
.gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
/* DWARF 3 */
.debug_pubtypes 0 : { *(.debug_pubtypes) }
.debug_ranges 0 : { *(.debug_ranges) }
/* DWARF Extension. */
.debug_macro 0 : { *(.debug_macro) }
.debug_addr 0 : { *(.debug_addr) }
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) *(.gnu_object_only) }
}

我们需要编译上述源代码和链接脚本。我们可以使用以下命令进行编译:

gcc test.c -T test.lds -o test -g -Wl,-Map=test.map
 

拓展:映射文件】在使用 gcc 编译器时,-Wl,-Map=test.map 选项用于生成一个映射文件(map file),这个文件通常命名为 test.map。映射文件提供了关于最终链接生成的可执行文件或库的详细信息,包括但不限于:

  1. Section 布局:映射文件显示了每个 section(如 .text.data.bss.rodata 等)的起始地址、大小和位置。
  2. 符号表:映射文件列出了所有的全局符号(如函数和变量),包括它们的地址和大小。
  3. 输入文件:映射文件记录了所有参与链接过程的输入文件(如目标文件和库文件)的信息。
  4. 未解析的符号:如果存在任何未解析的符号(即找不到定义的符号),映射文件也会指出这些问题。

生成映射文件的作用

  1. 调试和验证

  • 映射文件可以帮助开发者验证链接过程是否按预期进行。
  • 它可以用于检查是否有未解析的符号或其他链接错误。
  • 对于复杂项目,映射文件可以帮助确认各个模块是否被正确链接。
  • 内存布局分析

    • 开发者可以通过映射文件了解程序的内存布局,这对于嵌入式系统和资源受限环境尤为重要。
    • 映射文件可以揭示哪些 section 占用了多少内存,帮助优化内存使用。
  • 定位问题

    • 当程序出现奇怪的行为时,映射文件可以提供有关内存布局和符号位置的重要线索,帮助定位问题。
    • 特别是在多文件或多模块项目中,映射文件可以帮助识别哪些符号来自哪个文件。

    编译完成后,我们会得到一个名为 test 的可执行文件以及一个名为 test.map 的地图文件。运行这个程序:

    [root@localhost custom_section]# ./test
    ========================
    cmd1
    This is do_cmd1 function.
    ========================
    ========================
    cmd2
    This is do_cmd2 function.
    ========================

    通过运行结果显示,主函数通过链接脚本定义的两个全局变量 __mysection_start 和__mysection_stop 以及 cmd_t 类型的指针 p 遍历了 .mysection 中的所有命令,并依次调用它们的名字和功能函数。

    三、实战解析

    1、源代码解析

    结构体定义

    typedef struct {
    const char *name; /* 命令的名字 */
    const char *help; /* 帮助信息 */
    void (*func)(void); /* 命令的功能函数指针 */
    } cmd_t;

    我们定义了一个 cmd_t 结构体,其中包含命令的名称、帮助信息和一个指向功能函数的指针。

    宏定义

    #define __my_section __attribute__((section(".mysection"), aligned(8)))

    使用 __attribute__((section(".mysection"), aligned(8))) 将结构体实例放置到名为.mysection的 section 中,并对其进行了 8 字节对齐。

     

    注意:具体对齐方式可能因系统的不同而有所不同,请结合实际系统进行设置,一旦对齐方式设置错误,会引起程序 core dump

    命令添加宏

    #define ADD_CMD(name, help, func) \
    cmd_t __cmd_##name __my_section = {#name, help, func}

    该宏允许我们在程序中方便地添加命令,每个命令都会被放置到 .mysection 中。# 操作符用于将宏参数转换成字符串。当宏被展开时,# 后面的宏参数会被转换成一个字符串字面量。这意味着宏参数会被原样保留,而不是先进行宏替换后再转换成字符串。## 操作符用于连接(拼接)两个标识符或者字符串。当宏被展开时,## 前后的两个操作数会被拼接成一个单一的操作数。

    因此,文中的宏展开过程如下:

    ADD_CMD(cmd1, "help of cmd1", do_cmd1);

    宏展开后变为:

    cmd_t __cmd_cmd1 __attribute__ ((section(".mysection"), aligned(8))) = {"cmd1", "help of cmd1", do_cmd1};

    功能函数

    void do_cmdx(void)
    {
    printf("This is %s function.\n", __func__);
    }

    定义了 2 个简单的功能函数 do_cmdx

    外部变量声明

    extern cmd_t __mysection_start;
    extern cmd_t __mysection_stop;

    声明了两个全局变量,用于标识.mysection的起始和结束位置。可能有人会疑惑,链接脚本中定义的这两个变量明明没有类型,为什么这里用 extern 声明的时候却变成了 cmd_t 类型?在链接器脚本中,__mysection_start 和 __mysection_stop 用来标记 .mysection 的起始和结束位置,它们不需要类型信息。在 C 代码中,__mysection_start 和 __mysection_stop 必须声明为 cmd_t 类型,这样才能在 C 代码中正确地访问这些变量,即我们想要访问什么类型的数据,则必需要将这两个符号声明为对应的类型。通过这种方式,我们可以确保在 C 代码中正确地使用这些符号,并在链接器脚本中正确地布局程序的各个部分。

    主函数

    int main(void)
    {
    cmd_t *p = &__mysection_start;

    for (; p < &__mysection_stop; p++)
    {
    printf("========================\n");
    printf("%s\n", p->name);
    p->func();
    printf("========================\n");
    }

    return 0;
    }

    主函数遍历 .mysection 中的所有命令,并依次调用它们的功能函数。你使用 p 指针来遍历 .mysection 节中的所有 cmd_t 结构体。p 指针从 __mysection_start 的地址开始,一直增加到 __mysection_stop 的地址之前。每次循环,p 都会指向下一个 cmd_t 结构体,直到达到 .mysection 节的末尾。

     

    拓展cmd_t *p = &__mysection_start,这里为什么要用&__mysection_start,而不是 __mysection_start__mysection_start 本身不就存储的地址值吗?

     

    解析__mysection_start 和 __mysection_stop 被定义为 cmd_t 类型的全局变量。因此,如果直接使用 __mysection_start 或 __mysection_stop,它们会是 cmd_t 类型的值,而不是指针。cmd_t *p = &__mysection_start; 表示 p 是一个指向 cmd_t 类型的指针,而 &__mysection_start 是 cmd_t 类型变量 __mysection_start 的地址。如果不使用 &,则编译器会报错,因为它期望一个指针类型的值,而您却提供了一个 cmd_t 类型的值。为了确保指针变量 p 正确初始化,并且在比较时使用正确的类型,您应该始终使用取地址运算符 &

    2、链接脚本解析

    全局变量定义

    __mysection_start = .;
    . = ALIGN(8);
    .mysection : { *(.mysection) }
    __mysection_stop = .;

    链接脚本定义了两个全局变量 __mysection_start 和 __mysection_stop,它们分别指向 .mysection 的起始和结束位置。通过 . = ALIGN(8);保证了对齐要求。

    四、总结

    通过上述示例,我们展示了如何使用 Linker Script 和 C 语言的__attribute__((section)) 属性来实现对程序布局的定制。这种方法不仅有助于优化内存布局,还可以在初始化函数列表、配置选项等方面发挥重要作用。希望这篇实战文章能帮助读者理解和应用这一技术。


    Linux二进制 Linux编程、内核模块、网络原创文章分享,欢迎关注"Linux二进制"微信公众号
    评论
    • 2025年,科技浪潮汹涌澎湃的当下,智能数字化变革正进行得如火如荼,从去年二季度开始,触觉智能RK3562核心板上市以来,受到了火爆的关注,上百家客户选用了此方案,也获得了众多的好评与认可,为客户的降本增效提供了广阔的空间。随着原厂的更新,功能也迎来了一波重大的更新,无论是商业级(RK3562)还是工业级(RK3562J),都可支持NPU和2×CAN,不再二选一。我们触觉智能做了一个艰难又大胆的决定,为大家带来两大重磅福利,请继续往下看~福利一:RK3562核心板149元特惠再续,支持2×CAN
      Industio_触觉智能 2025-03-12 14:45 26浏览
    • 在追求更快、更稳的无线通信路上,传统射频架构深陷带宽-功耗-成本的“不可能三角”:带宽每翻倍,系统复杂度与功耗增幅远超线性增长。传统方案通过“分立式功放+多级变频链路+JESD204B 接口”的组合试图平衡性能与成本,却难以满足实时性严苛的超大规模 MIMO 通信等场景需求。在此背景下,AXW49 射频开发板以“直采+异构”重构射频范式:基于 AMD Zynq UltraScale+™ RFSoC Gen3XCZU49DR 芯片的 16 通道 14 位 2.5GSPS ADC 与 16
      ALINX 2025-03-13 09:27 32浏览
    • 文/杜杰编辑/cc孙聪颖‍主打影像功能的小米15 Ultra手机,成为2025开年的第一款旗舰机型。从发布节奏上来看,小米历代Ultra机型,几乎都选择在开年发布,远远早于其他厂商秋季主力机型的发布时间。这毫无疑问会掀起“Ultra旗舰大战”,今年影像手机将再次被卷上新高度。无意臆断小米是否有意“领跑”一场“军备竞赛”,但各种复杂的情绪难以掩盖。岁岁年年机不同,但将2-3年内记忆中那些关于旗舰机的发布会拼凑起来,会发现,包括小米在内,旗舰机的革新点,除了摄影参数的不同,似乎没什么明显变化。贵为旗
      华尔街科技眼 2025-03-13 12:30 60浏览
    • 曾经听过一个“隐形经理”的故事:有家公司,新人进来后,会惊讶地发现老板几乎从不在办公室。可大家依旧各司其职,还能在关键时刻自发协作,把项目完成得滴水不漏。新员工起初以为老板是“放羊式”管理,结果去茶水间和老员工聊过才发现,这位看似“隐形”的管理者其实“无处不在”,他提前铺好了企业文化、制度和激励机制,让一切运行自如。我的观点很简单:管理者的最高境界就是——“无为而治”。也就是说,你的存在感不需要每天都凸显,但你的思路、愿景、机制早已渗透到组织血液里。为什么呢?因为真正高明的管理,不在于事必躬亲,
      优思学院 2025-03-12 18:24 81浏览
    • DeepSeek自成立之初就散发着大胆创新的气息。明明核心开发团队只有一百多人,却能以惊人的效率实现许多大厂望尘莫及的技术成果,原因不仅在于资金或硬件,而是在于扁平架构携手塑造的蜂窝创新生态。创办人梁文锋多次强调,与其与大厂竞争一时的人才风潮,不如全力培养自家的优质员工,形成不可替代的内部生态。正因这样,他对DeepSeek内部人才体系有着一套别具一格的见解。他十分重视中式教育价值,因而DeepSeek团队几乎清一色都是中国式学霸。许多人来自北大清华,或者在各种数据比赛中多次获奖,可谓百里挑一。
      优思学院 2025-03-13 12:15 47浏览
    • 引言汽车行业正经历一场巨变。随着电动汽车、高级驾驶辅助系统(ADAS)和自动驾驶技术的普及,电子元件面临的要求从未如此严格。在这些复杂系统的核心,存在着一个看似简单却至关重要的元件——精密电阻。贞光科技代理品牌光颉科技的电阻选型过程,特别是在精度要求高达 0.01% 的薄膜和厚膜技术之间的选择,已成为全球汽车工程师的关键决策点。当几毫欧姆的差异可能影响传感器的灵敏度或控制系统的精确性时,选择正确的电阻不仅仅是满足规格的问题——它关系到车辆在极端条件下的安全性、可靠性和性能。在这份全面指南中,我们
      贞光科技 2025-03-12 17:25 92浏览
    • 一、行业背景与需求痛点智能电子指纹锁作为智能家居的核心入口,近年来市场规模持续增长,用户对产品的功能性、安全性和设计紧凑性提出更高要求:极致空间利用率:锁体内部PCB空间有限,需高度集成化设计。语音交互需求:操作引导(如指纹识别状态、低电量提醒)、安全告警(防撬、试错报警)等语音反馈。智能化扩展能力:集成传感器以增强安全性(如温度监测、防撬检测)和用户体验。成本与可靠性平衡:在复杂环境下确保低功耗、高稳定性,同时控制硬件成本。WTV380-P(QFN32)语音芯片凭借4mm×4mm超小封装、多传
      广州唯创电子 2025-03-13 09:24 41浏览
    •        随着人工智能算力集群的爆发式增长,以及5.5G/6G通信技术的演进,网络数据传输速率的需求正以每年30%的速度递增。万兆以太网(10G Base-T)作为支撑下一代数据中心、高端交换机的核心组件,其性能直接决定了网络设备的稳定性与效率。然而,万兆网络变压器的技术门槛极高:回波损耗需低于-20dB(比千兆产品严格30%),耐压值需突破1500V(传统产品仅为1000V),且需在高频信号下抑制电磁干扰。全球仅有6家企业具备规模化量产能力,而美信科
      中科领创 2025-03-13 11:24 40浏览
    • 在海洋监测领域,基于无人艇能够实现高效、实时、自动化的海洋数据采集,从而为海洋环境保护、资源开发等提供有力支持。其中,无人艇的控制算法训练往往需要大量高质量的数据支持。然而,海洋数据采集也面临数据噪声和误差、数据融合与协同和复杂海洋环境适应等诸多挑战,制约着无人艇技术的发展。针对这些挑战,我们探索并推出一套基于多传感器融合的海洋数据采集系统,能够高效地采集和处理海洋环境中的多维度数据,为无人艇的自主航行和控制算法训练提供高质量的数据支持。一、方案架构无人艇要在复杂海上环境中实现自主导航,尤其是完
      康谋 2025-03-13 09:53 44浏览
    • 文/Leon编辑/cc孙聪颖作为全球AI领域的黑马,DeepSeek成功搅乱了中国AI大模型市场的格局。科技大厂们选择合作,接入其模型疯抢用户;而AI独角兽们则陷入两难境地,上演了“Do Or Die”的抉择。其中,有着“大模型六小虎”之称的六家AI独角兽公司(智谱AI、百川智能、月之暗面、MiniMax、阶跃星辰及零一万物),纷纷开始转型:2025年伊始,李开复的零一万物宣布转型,不再追逐超大模型,而是聚焦AI商业化应用;紧接着,消息称百川智能放弃B端金融市场,聚焦AI医疗;月之暗面开始削减K
      华尔街科技眼 2025-03-12 17:37 145浏览
    • 本文介绍OpenHarmony4.1系统开发板,出现打不开WiFi和蓝牙的问题排查和解决方法。触觉智能Purple Pi OH鸿蒙开发板演示,搭载了瑞芯微RK3566四核处理器,1TOPS算力NPU;Laval鸿蒙社区推荐并通过了开源鸿蒙XTS认证,成功适配OpenHarmony3.2、4.0、4.1、5.0 Release系统,SDK源码全开放!WiFi打不开缺少WiFi固件在WiFi打不开时我们可以通过使用串口工具查看WiFi打印信息:这条log主要说明了打开固件文件失败,说明了在/vend
      Industio_触觉智能 2025-03-12 14:32 53浏览
    • 一、行业背景与用户需求随着健康消费升级,智能眼部按摩仪逐渐成为缓解眼疲劳、改善睡眠的热门产品。用户对这类设备的需求不再局限于基础按摩功能,而是追求更智能化、人性化的体验,例如:语音交互:实时反馈按摩模式、操作提示、安全提醒。环境感知:通过传感器检测佩戴状态、温度、压力等,提升安全性与舒适度。低功耗长续航:适应便携场景,延长设备使用时间。高性价比方案:在控制成本的同时实现功能多样化。针对这些需求,WTV380-8S语音芯片凭借其高性能、多传感器扩展能力及超高性价比,成为眼部按摩仪智能化升级的理想选
      广州唯创电子 2025-03-13 09:26 33浏览
    • 本文介绍Android系统主板应用配置默认获取管理所有文件权限方法,基于触觉智能SBC3588行业主板演示,搭载了瑞芯微RK3588芯片,八核处理器,6T高算力NPU;音视频接口、通信接口等各类接口一应俱全,支持安卓Android、Linux、开源鸿蒙OpenHarmony、银河麒麟Kylin等操作系统。配置前提在配置前,建议先将应用配置成系统应用,不然配置后系统每次重启后都会弹窗提示是否获取权限。应用配置成系统应用,可参考以下链接方法:瑞芯微开发板/主板Android系统APK签名文件使用方法
      Industio_触觉智能 2025-03-12 14:34 54浏览
    • 前言在快速迭代的科技浪潮中,汽车电子技术的飞速发展不仅重塑了行业的面貌,也对测试工具提出了更高的挑战与要求。作为汽车电子测试领域的先锋,TPT软件始终致力于为用户提供高效、精准、可靠的测试解决方案。新思科技出品的TPT软件迎来了又一次重大更新,最新版本TPT 2024.12将进一步满足汽车行业日益增长的测试需求,推动汽车电子技术的持续革新。基于当前汽车客户的实际需求与痛点,结合最新的技术趋势,对TPT软件进行了全面的优化与升级。从模型故障注入测试到服务器函数替代C代码函数,从更准确的需求链接到P
      北汇信息 2025-03-13 14:43 40浏览
    • 北京时间3月11日,国内领先的二手消费电子产品交易和服务平台万物新生(爱回收)集团(纽交所股票代码:RERE)发布2024财年第四季度和全年业绩报告。财报显示,2024年第四季度万物新生集团总收入48.5亿元,超出业绩指引,同比增长25.2%。单季non-GAAP经营利润1.3亿元(non-GAAP口径,即经调整口径,均不含员工股权激励费用、无形资产摊销及因收购产生的递延成本,下同),并汇报创历史新高的GAAP净利润7742万元,同比增长近27倍。总览全年,万物新生总收入同比增长25.9%达到1
      华尔街科技眼 2025-03-13 12:23 47浏览
    我要评论
    0
    点击右上角,分享到朋友圈 我知道啦
    请使用浏览器分享功能 我知道啦