基于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二进制"微信公众号
    评论
    •     为控制片内设备并且查询其工作状态,MCU内部总是有一组特殊功能寄存器(SFR,Special Function Register)。    使用Eclipse环境调试MCU程序时,可以利用 Peripheral Registers Viewer来查看SFR。这个小工具是怎样知道某个型号的MCU有怎样的寄存器定义呢?它使用一种描述性的文本文件——SVD文件。这个文件存储在下面红色字体的路径下。    例:南京沁恒  &n
      电子知识打边炉 2025-01-04 20:04 98浏览
    • 大模型的赋能是指利用大型机器学习模型(如深度学习模型)来增强或改进各种应用和服务。这种技术在许多领域都显示出了巨大的潜力,包括但不限于以下几个方面: 1. 企业服务:大模型可以用于构建智能客服系统、知识库问答系统等,提升企业的服务质量和运营效率。 2. 教育服务:在教育领域,大模型被应用于个性化学习、智能辅导、作业批改等,帮助教师减轻工作负担,提高教学质量。 3. 工业智能化:大模型有助于解决工业领域的复杂性和不确定性问题,尽管在认知能力方面尚未完全具备专家级的复杂决策能力。 4. 消费
      丙丁先生 2025-01-07 09:25 80浏览
    • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
      华普微HOPERF 2025-01-06 17:23 141浏览
    • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
      Industio_触觉智能 2025-01-06 10:43 87浏览
    • 随着市场需求不断的变化,各行各业对CPU的要求越来越高,特别是近几年流行的 AIOT,为了有更好的用户体验,CPU的算力就要求更高了。今天为大家推荐由米尔基于瑞芯微RK3576处理器推出的MYC-LR3576核心板及开发板。关于RK3576处理器国产CPU,是这些年的骄傲,华为手机全国产化,国人一片呼声,再也不用卡脖子了。RK3576处理器,就是一款由国产是厂商瑞芯微,今年第二季推出的全新通用型的高性能SOC芯片,这款CPU到底有多么的高性能,下面看看它的几个特性:8核心6 TOPS超强算力双千
      米尔电子嵌入式 2025-01-03 17:04 55浏览
    • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球无人机锂电池产值达到2457百万美元,2024-2030年期间年复合增长率CAGR为9.6%。 无人机锂电池是无人机动力系统中存储并释放能量的部分。无人机使用的动力电池,大多数是锂聚合物电池,相较其他电池,锂聚合物电池具有较高的能量密度,较长寿命,同时也具有良好的放电特性和安全性。 全球无人机锂电池核心厂商有宁德新能源科技、欣旺达、鹏辉能源、深圳格瑞普和EaglePicher等,前五大厂商占有全球
      GIRtina 2025-01-07 11:02 63浏览
    • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
      华普微HOPERF 2025-01-06 15:29 125浏览
    • By Toradex 秦海1). 简介嵌入式平台设备基于Yocto Linux 在开发后期量产前期,为了安全以及提高启动速度等考虑,希望将 ARM 处理器平台的 Debug Console 输出关闭,本文就基于 NXP i.MX8MP ARM 处理器平台来演示相关流程。 本文所示例的平台来自于 Toradex Verdin i.MX8MP 嵌入式平台。  2. 准备a). Verdin i.MX8MP ARM核心版配合Dahlia载板并
      hai.qin_651820742 2025-01-07 14:52 40浏览
    • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
      优思学院 2025-01-06 12:03 113浏览
    • PLC组态方式主要有三种,每种都有其独特的特点和适用场景。下面来简单说说: 1. 硬件组态   定义:硬件组态指的是选择适合的PLC型号、I/O模块、通信模块等硬件组件,并按照实际需求进行连接和配置。    灵活性:这种方式允许用户根据项目需求自由搭配硬件组件,具有较高的灵活性。    成本:可能需要额外的硬件购买成本,适用于对系统性能和扩展性有较高要求的场合。 2. 软件组态   定义:软件组态主要是通过PLC
      丙丁先生 2025-01-06 09:23 83浏览
    • 村田是目前全球量产硅电容的领先企业,其在2016年收购了法国IPDiA头部硅电容器公司,并于2023年6月宣布投资约100亿日元将硅电容产能提升两倍。以下内容主要来自村田官网信息整理,村田高密度硅电容器采用半导体MOS工艺开发,并使用3D结构来大幅增加电极表面,因此在给定的占位面积内增加了静电容量。村田的硅技术以嵌入非结晶基板的单片结构为基础(单层MIM和多层MIM—MIM是指金属 / 绝缘体/ 金属) 村田硅电容采用先进3D拓扑结构在100um内,使开发的有效静电容量面积相当于80个
      知白 2025-01-07 15:02 66浏览
    • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
      GIRtina 2025-01-06 11:10 103浏览
    • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
      知白 2025-01-06 12:04 167浏览
    我要评论
    0
    点击右上角,分享到朋友圈 我知道啦
    请使用浏览器分享功能 我知道啦