范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

linux的initcall机制应用层用链接脚本实现

  initcall机制的由来
  我们都知道,linux对驱动程序提供静态编译进内核和动态加载两种方式,当我们试图将一个驱动程序编译进内核时,开发者通常提供一个xxx_init()函数接口以启动这个驱动程序同时提供某些服务。
  那么,根据常识来说,这个xxx_init()函数肯定是要在系统启动的某个时候被调用,才能启动这个驱动程序。
  最简单直观地做法就是:开发者试图添加一个驱动程序时,在内核启动init程序的某个地方直接添加调用自己驱动程序的xxx_init()函数,在内核启动时自然会调用到这个程序。
  但是,回头一想,这种做法在单人开发的小系统中或许可以,但是在linux中,如果驱动程序是这么个添加法,那就是一场灾难,这个道理我想不用我多说。
  不难想到另一种方式,就是集中提供一个地方,如果你要添加你的驱动程序,你就将你的初始化函数在这个地方进行添加,在内核启动的时候统一扫描这个地方,再执行这一部分的所有被添加的驱动程序。
  那到底怎么添加呢?直接在C文件中作一个列表,在里面添加初始化函数?我想随着驱动程序数量的增加,这个列表会让人头昏眼花。
  当然,对于linus大神而言,这些都不是事,linux的做法是:
  底层实现上,在内核镜像文件中,自定义一个段,这个段里面专门用来存放这些初始化函数的地址,内核启动时,只需要在这个段地址处取出函数指针,一个个执行即可。
  对上层而言,linux内核提供xxx_init(init_func)宏定义接口,驱动开发者只需要将驱动程序的init_func使用xxx_init()来修饰,这个函数就被自动添加到了上述的段中,开发者完全不需要关心实现细节。
  对于各种各样的驱动而言,可能存在一定的依赖关系,需要遵循先后顺序来进行初始化,考虑到这个,linux也对这一部分做了分级处理。
  initcall的源码
  在平台对应的init.h文件中,可以找到xxx_initcall的定义:/*Only for built-in code, not modules.*/  #define early_initcall(fn) __define_initcall(fn, early)  #define pure_initcall(fn) __define_initcall(fn, 0)  #define core_initcall(fn) __define_initcall(fn, 1)  #define core_initcall_sync(fn) __define_initcall(fn, 1s)  #define postcore_initcall(fn) __define_initcall(fn, 2)  #define postcore_initcall_sync(fn) __define_initcall(fn, 2s)  #define arch_initcall(fn) __define_initcall(fn, 3)  #define arch_initcall_sync(fn) __define_initcall(fn, 3s)  #define subsys_initcall(fn) __define_initcall(fn, 4)  #define subsys_initcall_sync(fn) __define_initcall(fn, 4s)  #define fs_initcall(fn) __define_initcall(fn, 5)  #define fs_initcall_sync(fn) __define_initcall(fn, 5s)  #define rootfs_initcall(fn) __define_initcall(fn, rootfs)  #define device_initcall(fn) __define_initcall(fn, 6)  #define device_initcall_sync(fn) __define_initcall(fn, 6s)  #define late_initcall(fn) __define_initcall(fn, 7)  #define late_initcall_sync(fn) __define_initcall(fn, 7s)
  xxx_init_call(fn)的原型其实是__define_initcall(fn, n),n是一个数字或者是数字+s,这个数字代表这个fn执行的优先级,数字越小,优先级越高,带s的fn优先级低于不带s的fn优先级。
  继续跟踪代码,看看__define_initcall(fn,n):#define __define_initcall(fn, id)   static initcall_t __initcall_##fn##id __used   __attribute__((__section__(".initcall" #id ".init"))) = fn;
  值得注意的是,attribute()是gnu C中的扩展语法,它可以用来实现很多灵活的定义行为,这里不细究。
  attribute((section(".initcall" #id ".init")))表示编译时将目标符号放置在括号指定的段中。
  而#在宏定义中的作用是将目标字符串化,##在宏定义中的作用是符号连接,将多个符号连接成一个符号,并不将其字符串化。
  __used是一个宏定义,
  #define __used __attribute__((__used__))
  使用前提是在编译器编译过程中,如果定义的符号没有被引用,编译器就会对其进行优化,不保留这个符号,而__attribute__((used))的作用是告诉编译器这个静态符号在编译的时候即使没有使用到也要保留这个符号。
  为了更方便地理解,我们拿举个例子来说明,开发者声明了这样一个函数:pure_initcall(test_init);
  所以pure_initcall(test_init)的解读就是:
  首先宏展开成:__define_initcall(test_init, 0)
  然后接着展开:static initcall_t __initcall_test_init0 = test_init;这就是一个简单的变量定义。
  同时声明__initcall_test_init0这个变量即使没被引用也保留符号,且将其放置在内核镜像的.initcall0.init段处。
  需要注意的是,根据官方注释可以看到early_initcall(fn)只针对内置的核心代码,不能描述模块。
  xxx_initcall修饰函数的调用
  既然我们知道了xxx_initcall是怎么定义而且目标函数的放置位置,那么使用xxx_initcall()修饰的函数是怎么被调用的呢?
  我们就从内核C函数起始部分也就是start_kernel开始往下挖,这里的调用顺序为:start_kernel  -> rest_init();  -> kernel_thread(kernel_init, NULL, CLONE_FS);  -> kernel_init()  -> kernel_init_freeable();  -> do_basic_setup();  -> do_initcalls();
  这个do_initcalls()就是我们需要寻找的函数了,在这个函数中执行所有使用xxx_initcall()声明的函数,接下来我们再来看看它是怎么执行的:static initcall_t *initcall_levels[] __initdata = {  __initcall0_start,  __initcall1_start,  __initcall2_start,  __initcall3_start,  __initcall4_start,  __initcall5_start,  __initcall6_start,  __initcall7_start,  __initcall_end,  };  int __init_or_module do_one_initcall(initcall_t fn)  {  ...  if (initcall_debug)  ret = do_one_initcall_debug(fn);  else  ret = fn();  ...  return ret;  }  static void __init do_initcall_level(int level)  {  initcall_t *fn;  ...  for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)  do_one_initcall(*fn);  }  static void __init do_initcalls(void)  {  int level;  for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)  do_initcall_level(level);  }
  在上述代码中,定义了一个静态的initcall_levels数组,这是一个指针数组,数组的每个元素都是一个指针.
  do_initcalls()循环调用do_initcall_level(level),level就是initcall的优先级数字,由for循环的终止条件ARRAY_SIZE(initcall_levels) - 1可知,总共会调用do_initcall_level(0)~do_initcall_level(7),一共七次。
  而do_initcall_level(level)中则会遍历initcall_levels[level]中的每个函数指针,initcall_levels[level]实际上是对应的__initcall##level##_start指针变量,然后依次取出__initcall##level##_start指向地址存储的每个函数指针,并调用do_one_initcall(*fn),实际上就是执行当前函数。
  可以猜到的是,这个__initcall##level##start所存储的函数指针就是开发者用xxx_initcall()宏添加的函数,对应".initcall##level##.init"段。
  do_one_initcall(*fn)的执行:判断initcall_debug的值,如果为真,则调用do_one_initcall_debug(fn);如果为假,则直接调用fn。事实上,调用do_one_initcall_debug(fn)只是在调用fn的基础上添加一些额外的打印信息,可以直接看成是调用fn。
  那么,在initcall源码部分有提到,在开发者添加xxx_initcall(fn)时,事实上是将fn放置到了".initcall##level##.init"的段中,但是在do_initcall()的源码部分,却是从initcall_levelslevel取出,initcall_levels[level]是怎么关联到".initcall##level##.init"段的呢?
  答案在vmlinux.lds.h中:#define INIT_CALLS_LEVEL(level)   VMLINUX_SYMBOL(__initcall##level##_start) = .;   KEEP(*(.initcall##level##.init))   KEEP(*(.initcall##level##s.init))   #define INIT_CALLS   VMLINUX_SYMBOL(__initcall_start) = .;   KEEP(*(.initcallearly.init))   INIT_CALLS_LEVEL(0)   INIT_CALLS_LEVEL(1)   INIT_CALLS_LEVEL(2)   INIT_CALLS_LEVEL(3)   INIT_CALLS_LEVEL(4)   INIT_CALLS_LEVEL(5)   INIT_CALLS_LEVEL(rootfs)   INIT_CALLS_LEVEL(6)   INIT_CALLS_LEVEL(7)   VMLINUX_SYMBOL(__initcall_end) = .;
  在这里首先定义了__initcall_start,将其关联到".initcallearly.init"段。
  然后对每个level定义了INIT_CALLS_LEVEL(level),将INIT_CALLS_LEVEL(level)展开之后的结果是定义__initcall##level##_start,并将
  __initcall##level##_start关联到".initcall##level##.init"段和".initcall##level##s.init"段。
  到这里,__initcall##level##_start和".initcall##level##.init"段的对应就比较清晰了,所以,从initcall_levels[level]部分一个个取出函数指针并执行函数就是执行xxx_init_call()定义的函数。
  总结
  便于理解,我们需要一个示例来梳理整个流程,假设我是一个驱动开发者,开发一个名为beagle的驱动,在系统启动时需要调用beagle_init()函数来启动启动服务。
  我需要先将其添加到系统中:
  core_initcall(beagle_init)
  core_initcall(beagle_init)宏展开为__define_initcall(beagle_init, 1),所以beagle_init()这个函数被放置在".initcall1.init"段处。
  在内核启动时,系统会调用到do_initcall()函数。 根据指针数组initcall_levels[1]找到__initcall1_start指针,在vmlinux.lds.h可以查到:__initcall1_start对应".initcall1.init"段的起始地址,依次取出段中的每个函数指针,并执行函数。
  添加的服务就实现了启动。
  可能有些C语言基础不太好的朋友不太理解do_initcall_level()函数中依次取出地址并执行的函数执行逻辑:
  for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) do_one_initcall(*fn);
  fn为函数指针,fn++相当于函数指针+1,相当于:内存地址+sizeof(fn),sizeof(fn)根据平台不同而不同,一般来说,32位机上是4字节,64位机则是8字节(关于指针在操作系统中的大小可以参考另一篇博客:不同平台下指针大小 )。
  而initcall_levels[level]指向当前".initcall##level##s.init"段,initcall_levels[level+1]指向".initcall##(level+1)##s.init"段,两个段之间的内存就是存放所有添加的函数指针。
  也就是从".initcall##level##s.init"段开始,每次取一个函数出来执行,并累加指针,直到取完。
  写个测试用例吧,应用层:
  c_init.c/*  * @Description:  * @Version: 2.0  * @Autor: lsh  * @Date: 2022-02-25 19:48:58  * @LastEditors: lsh  * @LastEditTime: 2022-02-25 20:00:04  */  #include   #include   #include   typedef void (*init_call)(void);  /*  * These two variables are defined in link script.  */  extern init_call _init_start;  extern init_call _init_end;  #define _init __attribute__((unused, section(".myinit")))  #define DECLARE_INIT(func) init_call _fn_##func _init = func  static void C_init(void)  {  printf("C_init ");  }  DECLARE_INIT(C_init);
  ldscript.lds/* Script for -z combreloc -z separate-code: combine and sort reloc sections with separate code segment */  /* 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/local/lib/x86_64-linux-gnu"); SEARCH_DIR("=/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64"); SEARCH_DIR("=/usr/local/lib64"); SEARCH_DIR("=/lib64"); SEARCH_DIR("=/usr/lib64"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");  SECTIONS  {  /* Read-only sections, merged into text segment: */  PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;  .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 = .);  }  . = ALIGN(CONSTANT (MAXPAGESIZE));  .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 = .);  . = ALIGN(CONSTANT (MAXPAGESIZE));  /* Adjust the address for the rodata segment. We want to adjust up to  the same address within the page on the next page up. */  . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1)));  .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 :  {  PROVIDE_HIDDEN (__tdata_start = .);  *(.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 = .);  . = .;  _init_start = .;  .myinit : { *(.myinit) }  _init_end = .;  __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) }  /* 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_*) }  }
  main.c/*  * @Description:内核模块原理  * @Version: 2.0  * @Autor: lsh  * @Date: 2022-02-25 17:47:46  * @LastEditors: lsh  * @LastEditTime: 2022-02-25 19:54:20  */  #include   #include   #include   typedef void (*init_call)(void);  /*  * These two variables are defined in link script.  */  extern init_call _init_start;  extern init_call _init_end;  #define _init __attribute__((unused, section(".myinit")))  #define DECLARE_INIT(func) init_call _fn_##func _init = func  static void A_init(void)  {  write(1, "A_init ", sizeof("A_init "));  }  DECLARE_INIT(A_init);  static void B_init(void)  {  printf("B_init ");  }  DECLARE_INIT(B_init);  // static void C_init(void)  // {  // printf("C_init ");  // }  // DECLARE_INIT(C_init);  /*  * DECLARE_INIT like below:  * static init_call _fn_A_init __attribute__((unused, section(".myinit"))) = A_init;  * static init_call _fn_C_init __attribute__((unused, section(".myinit"))) = C_init;  * static init_call _fn_B_init __attribute__((unused, section(".myinit"))) = B_init;  */  void do_initcalls(void)  {  init_call *init_ptr = &_init_start;  for (; init_ptr < &_init_end; init_ptr++)  {  printf("init address: %p ", init_ptr);  (*init_ptr)();  }  }  int main(void)  {  do_initcalls();  return 0;  }
  编译教程
  执行:gcc -c section.c -o section.o 编译应用源码。
  执行:readelf -S section.o 查看段信息. 可以看到,段[6]是我们自定义的数据段.
  执行:gcc -T ldscript.lds section.o -o section 链接成可执行的bin文件
  执行:readelf -S section 查看bin文件的段分布情况 在我链接成的可执行bin中,在[25]段中存在我们自定义的段。
  运行后可以看到打印为调用了所有3个函数。
  ————————————————
  版权声明:本文为CSDN博主「若灮」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
  原文链接:https://blog.csdn.net/u011164819/article/details/123281172

日产又一技术之作,全新Ariya纯电汽车,新势力看完沉默了众所周知,新能源汽车是未来时代的绝对趋势,对于造车新势力和传统车企来说都是一个平等的起点。但你细看现在的新能源市场,造车新势力的风头明显盖过了传统车企,原因很简单,传统车企造出的新小米笔记本迟来四年的更新,打了个翻身仗还是依旧咸鱼?2016年7月27日,小米在北京发布了自家首款笔记本,于2017年声望达到了顶峰,从此便一蹶不振。几年时间,红米变成了Redmi,笔记本电脑业务也被Redmi揽了过去,发布了数款超忍不住喊出真香的骁龙870旗舰,年度性价比最高在这三天的高频使用下,iQOONeo5的各个方面都有了明确的认识。今天,我将分享iQOONeo5的三天经验,并谈谈我眼里iQOONeo5的优缺点,感兴趣的伙伴可以做一个了解。在外观对K套装惊喜亮相,2999收割电视和手机,这力度还有谁?5月6日下午三点,OPPO新品发布会正式召开,此次发布会发布了OPPOK9OPPO智能电视K9OPPO手环活力版OPPOEncoAir等七款新品以及两个套装,还有各种突破次元的刺客OPPOK9系列超次元发布会前瞻七部产品两个套装,价格成悬念转眼间,五一长假已经进入尾声,虽然很多小伙伴不愿意承认,但明天就要上班了!不过对于OPPO而言,上班就意味着忙碌,因为他们要在5月6日召开OPPOK9系列超次元发布会。而从目前曝光玩游戏手机发烫怎么办?黑鲨冰封散热背夹2Pro秒破解时间已经进入到了4月下旬,天气也是一天天的热了起来。人觉得热,手机也一样会热,尤其是在我们玩游戏的时候。手机发热之后,最大的问题就是会出现降频的问题,导致手机卡顿,尤其是在游戏的过FindX3Pro凭什么比小米11Ultra更顺滑?OSync超频响应很关键上半年发布高端旗舰手机中,OPPOFindX3Pro和小米11Ultra无疑是热度最高的,毕竟无论是外观设计还是硬件配置来看,这两款手机都称得上是机皇级别的。也是因为如此,这两款手颜值实力都是绝绝子,Reno6星黛紫明日开售相信在选购手机时,除了性能影像和系统体验外,外观设计部分也是不少人所注重的点。优秀的产品设计不仅能够吸引消费者选购,更能体现一家厂商的设计美学,要说今年哪款产品在外观设计上给人留下2199的realmeX7Pro,这手机配置和性价比也是没谁了?今年的5G旗舰手机确实越来越贵。联发天玑1000的出现无疑是整个旗舰手机市场的一股清流。它的存在使得5G手机的性能强劲。同时,我们的价格更为人性化。真我X7Pro是配备天玑1000618换机推荐!iPhone12降价Reno6系列新品上市,怎么选都划算每年的618,无疑都是换新手机的最佳时机,毕竟品牌调价加上平台补贴都能剩下不少。而今年的618正如火如荼进行中,想必不少小伙伴都想趁着各种优惠福利给自己或者家人换一台新手机。趁着这144Hz骁龙870,2499元起的黑鲨4值得买吗?与普通手机厂商不同,黑鲨手机从诞生之日起就一直是专注于游戏手机研发的手机厂商,它推出的所有机型都围绕手机游戏而设的。今年3月,他们推出了第四代游戏手机产品黑鲨4系列。相比面面俱到的
打击数据黑市完善数据市场体系光明时评作者盘和林王俊(分别系工信部信息通信经济专家委员会委员中南财经政法大学数字经济研究院执行院长,中国社会科学院工业经济所博士后)数据贩卖已成为大数据产业的灰色地带,个人信息倒Win11新任务管理器曝光UI响应速度提升1476据WindowsLatest报道,在任何版本的Windows上,你都会发现有少数进程在后台运行,即使在空闲状态下也会活跃使用系统资源。用户可以用任务管理器监控活跃的进程或程序,如果最新研究奥密克戎BA。2亚型变异株不仅传播速度更快也可能引发重症财联社2月22日讯,据联合早报消息,日本东京大学的最新研究结果显示,奥密克戎BA。2亚型变异株不仅传播速度更快,也可能引发重症,甚至可能规避疫苗产生的部分抗体,对一些治疗药物也具有首只新能源车主题QDII基金银华全球新能源车量化优选股票基金近日获批近日,由银华基金上报的银华全球新能源车量化优选股票型基金(QDII)正式获批,为广大投资者布局新能源车产业提供了新的思路和稀缺性投资工具。据业内人士介绍,这款产品是业内首只获批的新8500mAh电池加持,金立M50Pro的续航实力无人能及强者生存,弱者淘汰,自然界的生存法则向来都是这样。就拿智能手机领域来说,曾经非常火热的一些手机品牌,因为逐渐跟不上市场发展的步伐最终被淘汰。像诺基亚金立这样的手机品牌就是在发展的浪美国海外仓一件代发虚拟仓小包空运专线小包海运小包对比随着跨境电商越来越火,竞争也越来越大。ebay亚马逊沃尔玛相继疯狂赚钱的时代渐渐过去,很多卖家不得不重新对自己的跨境贸易做新的定位。很多卖家转了新的平台,比如一些小众多家的电商跨境IDC报告显示分布式存储市场浪潮领跑环球网科技综合报道近日,IDC公布2021年第三季度中国软件定义存储(SDS)市场报告。报告显示,第三季度中国SDS市场销量22527台,其中浪潮分布式存储销量达到3515台,占据亚马逊店铺怎么上传产品?上传步骤是怎样的?上传步骤是怎样的?作为新手卖家,规定一周只能传1000个sku,不过上传产品也是跟账户权重有关的,你d产品销量越好,隔段时间就会提高可上传产品的数量限额。上传详情1进入后台,点的I库克信心满满,iPhone14迎来这4大升级,果粉终于不再挤牙膏近几年,苹果iPhone手机最值得吐槽的丑陋刘海屏60Hz低刷新率屏祖传的12MP主摄这几大槽点,终于在iPhone14系列新机上迎来大变革。近日,据苹果上游供应链最新爆料称,iP华为要小心了,韩国6G欲弯道超车,网速是5G的50倍阅读下面文章之前,希望您能够在上方点个免费的关注!接下来您每天都能够收到免费的国际资讯哟!您的关注是我的动力多多支持下嘛!!早在2019年内,美国就将华为拉入了实体清单,大家都知道换iphone手机的小秘密你知道吗?内容较长,但都是干货使用iphone的朋友们都知道,苹果公司一年发一部新iphone,规格类型也从以前的1个,变成现在的4个,发布新iphone的价格也不算低,也在每年上涨,赚的少