GNU ld 链接器lang_process() (一)

news2025/4/13 0:32:43

一、lang_process() 

        从现在开始介绍 lang_process()函数,是GNU ld(GNU链接器)的一个核心函数,负责执行链接过程中的各个关键操作。lang_process(void) 函数涵盖了整个链接过程中的各个关键步骤,包括符号解析、重定位、大小计算、内存区域管理、节的映射和输出文件的生成等这个函数的功能非常复杂,它确保了链接器能够将多个目标文件合并成最终的可执行文件或共享库。

void
lang_process (void)
{
  /* Finalize dynamic list.  */
  if (link_info.dynamic_list)
    lang_finalize_version_expr_head (&link_info.dynamic_list->head);

  current_target = default_target;

  /* Open the output file.  */
  lang_for_each_statement (ldlang_open_output);  // 输出一下
  init_opb ();  // x = 1

  ldemul_create_output_section_statements ();  // 机器相关,x86函数为NULL

  /* Add to the hash table all undefineds on the command line.  */
  lang_place_undefineds ();  // 将未定义符号加入link_info.hash

  if (!bfd_section_already_linked_table_init ())  // 初始化了一个hash table
    einfo (_("%P%F: Failed to create hash table\n"));

  /* Create a bfd for each input file.  */
  current_target = default_target;  // elf64-x86-64
  open_input_bfds (statement_list.head, OPEN_BFD_NORMAL);  // 王子龙在看

#ifdef ENABLE_PLUGINS
  if (link_info.lto_plugin_active)  // 没进
    {
      lang_statement_list_type added;
      lang_statement_list_type files, inputfiles;

      /* Now all files are read, let the plugin(s) decide if there
	 are any more to be added to the link before we call the
	 emulation's after_open hook.  We create a private list of
	 input statements for this purpose, which we will eventually
	 insert into the global statement list after the first claimed
	 file.  */
      added = *stat_ptr;
      /* We need to manipulate all three chains in synchrony.  */
      files = file_chain;
      inputfiles = input_file_chain;
      if (plugin_call_all_symbols_read ())
	einfo (_("%P%F: %s: plugin reported error after all symbols read\n"),
	       plugin_error_plugin ());
      /* Open any newly added files, updating the file chains.  */
      open_input_bfds (*added.tail, OPEN_BFD_NORMAL);
      /* Restore the global list pointer now they have all been added.  */
      lang_list_remove_tail (stat_ptr, &added);
      /* And detach the fresh ends of the file lists.  */
      lang_list_remove_tail (&file_chain, &files);
      lang_list_remove_tail (&input_file_chain, &inputfiles);
      /* Were any new files added?  */
      if (added.head != NULL)
	{
	  /* If so, we will insert them into the statement list immediately
	     after the first input file that was claimed by the plugin.  */
	  plugin_insert = find_replacements_insert_point ();
	  /* If a plugin adds input files without having claimed any, we
	     don't really have a good idea where to place them.  Just putting
	     them at the start or end of the list is liable to leave them
	     outside the crtbegin...crtend range.  */
	  ASSERT (plugin_insert != NULL);
	  /* Splice the new statement list into the old one.  */
	  lang_list_insert_after (stat_ptr, &added,
				  &plugin_insert->header.next);
	  /* Likewise for the file chains.  */
	  lang_list_insert_after (&input_file_chain, &inputfiles,
				  &plugin_insert->next_real_file);
	  /* We must be careful when relinking file_chain; we may need to
	     insert the new files at the head of the list if the insert
	     point chosen is the dummy first input file.  */
	  if (plugin_insert->filename)
	    lang_list_insert_after (&file_chain, &files, &plugin_insert->next);
	  else
	    lang_list_insert_after (&file_chain, &files, &file_chain.head);

	  /* Rescan archives in case new undefined symbols have appeared.  */
	  open_input_bfds (statement_list.head, OPEN_BFD_RESCAN);
	}
    }
#endif /* ENABLE_PLUGINS */

  /* Make sure that nobody has tried to add a symbol to this list
     before now.  */
  ASSERT (link_info.gc_sym_list == NULL);

  link_info.gc_sym_list = &entry_symbol;  // {next = 0x55555599c470, name = 0x555555988b10 "_start"}

  if (entry_symbol.name == NULL)
    {
      link_info.gc_sym_list = ldlang_undef_chain_list_head;

      /* entry_symbol is normally initialied by a ENTRY definition in the
	 linker script or the -e command line option.  But if neither of
	 these have been used, the target specific backend may still have
	 provided an entry symbol via a call to lang_default_entry().
	 Unfortunately this value will not be processed until lang_end()
	 is called, long after this function has finished.  So detect this
	 case here and add the target's entry symbol to the list of starting
	 points for garbage collection resolution.  */
      lang_add_gc_name (entry_symbol_default);
    }

  lang_add_gc_name (link_info.init_function);
  lang_add_gc_name (link_info.fini_function);

  ldemul_after_open ();
  if (config.map_file != NULL)
    lang_print_asneeded ();

  bfd_section_already_linked_table_free ();  // 为啥又把table free了???

  /* Make sure that we're not mixing architectures.  We call this
     after all the input files have been opened, but before we do any
     other processing, so that any operations merge_private_bfd_data
     does on the output file will be known during the rest of the
     link.  */
  lang_check ();

  /* Handle .exports instead of a version script if we're told to do so.  */
  if (command_line.version_exports_section)
    lang_do_version_exports_section ();

  /* Build all sets based on the information gathered from the input
     files.  */
  ldctor_build_sets ();  // 符号相关的

  /* PR 13683: We must rerun the assignments prior to running garbage
     collection in order to make sure that all symbol aliases are resolved.  
这段注释的意思是,为了确保所有符号别名(symbol aliases)都得到解析,
我们必须在运行垃圾回收(garbage collection)之前重新运行赋值(assignments)过程。
在这里,“符号别名”指的是在链接脚本或源代码中通过别名来引用的符号。
例如,你可以在链接脚本或源代码中将一个符号(Symbol)用另一个名称来引用,
这被称为符号别名。这种情况下,为了确保所有的别名都指向了正确的符号,
需要在运行垃圾回收之前重新进行一次符号赋值的过程。
这是一个确保程序在连接时符号引用正确的重要步骤。 */
  lang_do_assignments (lang_mark_phase_enum);  // 看看

  lang_do_memory_regions();
  expld.phase = lang_first_phase_enum;

  /* Size up the common data.  */
  lang_common ();  // 处理符号的

  /* Remove unreferenced sections if asked to.  */
  lang_gc_sections ();  // 删除不用的节,ld --gc-sections开启

  /* Check relocations.  */
  lang_check_relocs ();

  /* Update wild statements.排序用的  */
  update_wild_statements (statement_list.head);

  /* Run through the contours of the script and attach input sections
     to the correct output sections.  */
  lang_statement_iteration++;
  map_input_to_output_sections (statement_list.head, NULL, NULL);  // 重点看看!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  process_insert_statements ();

  /* Find any sections not attached explicitly and handle them.  */
  lang_place_orphans ();

  if (!bfd_link_relocatable (&link_info))
    {// 进来了
      asection *found;

      /* Merge SEC_MERGE sections.  This has to be done after GC of
	 sections, so that GCed sections are not merged, but before
	 assigning dynamic symbols, since removing whole input sections
	 is hard then.  */
      bfd_merge_sections (link_info.output_bfd, &link_info);

      /* Look for a text section and set the readonly attribute in it.  */
      found = bfd_get_section_by_name (link_info.output_bfd, ".text");

      if (found != NULL)
	{
	  if (config.text_read_only)// 进了
	    found->flags |= SEC_READONLY;
	  else
	    found->flags &= ~SEC_READONLY;
	}
    }

  /* Do anything special before sizing sections.  This is where ELF
     and other back-ends size dynamic sections.  */
  ldemul_before_allocation ();

  /* We must record the program headers before we try to fix the
     section positions, since they will affect SIZEOF_HEADERS.  */
  lang_record_phdrs ();

  /* Check relro sections.  */
  if (link_info.relro && !bfd_link_relocatable (&link_info))  // 进了
    lang_find_relro_sections ();  // 这在干啥????

  /* Size up the sections.  */
  lang_size_sections (NULL, !RELAXATION_ENABLED);  // 看看

  /* See if anything special should be done now we know how big
     everything is.  This is where relaxation is done.  */
  ldemul_after_allocation ();

  /* Fix any .startof. or .sizeof. symbols.  */
  lang_set_startof ();  // 符号相关的

  /* Do all the assignments, now that we know the final resting places
     of all the symbols.  */
  lang_do_assignments (lang_final_phase_enum);

  ldemul_finish ();  // 删除Execule中的符号,改为使用keep中的符号

  /* Convert absolute symbols to section relative.  */
  ldexp_finalize_syms ();  // 处理链接脚本定义的绝对符号

  /* Make sure that the section addresses make sense.  */
  if (command_line.check_section_addresses)  // 进了
    lang_check_section_addresses ();

  /* Check any required symbols are known.  */
  ldlang_check_require_defined_symbols ();

  lang_end ();
}

二、lang_process执行函数

1.lang_finalize_version_expr_head

  if (link_info.dynamic_list)
    lang_finalize_version_expr_head (&link_info.dynamic_list->head);

1)link_info 是一个结构体或对象,用于存储与链接过程相关的各种信息和参数。它通常在链接器的代码中用来跟踪链接过程中的状态和配置。link_info 可能包含各种链接器的配置选项、输入文件列表、输出文件信息、符号表、段表、动态库信息等等,这取决于链接器的实现和配置。2)2)link_info.dynamic_listlink_info 结构体中的一个成员,用于存储与动态链接库版本信息和表达式相关的数据。

3)&link_info.dynamic_list->head:这是对动态链接库版本列表头部的引用。

4)lang_finalize_version_expr_head:这是链接器的一个函数或方法,它的目的是处理动态链接库版本信息和相关的表达式。这通常包括检查版本依赖关系,解析版本表达式,以及确保所链接的动态库版本符合版本规范。

2. lang_for_each_statement(打开输出文件)

        在lang_process()中调用lang_for_each_statement函数,并将 ldlang_open_output (这是一个函数)作为参数传入。函数 lang_for_each_statement 通常用于遍历链接器语句列表,并对每个语句执行特定的操作。在这种情况下,它将每个语句传递给 ldlang_open_output 函数。

lang_for_each_statement 函数的作用是遍历链接器的语句列表,对每个语句执行特定的操作。它通常用于链接器的初始化和处理过程中,以便处理链接脚本中定义的各种语句,执行必要的操作来构建可执行文件或共享库。

具体来说,lang_for_each_statement 的主要作用包括:

  1. 遍历链接器语句:它帮助链接器迭代链接器语句列表,这些语句包括符号定义、节定义、输入文件、输出文件、地址重定位等等。

  2. 执行用户自定义操作:用户可以通过传递一个函数指针给 lang_for_each_statement,指定要在每个链接器语句上执行的操作。这允许用户定义处理链接器语句的自定义逻辑,以适应特定需求。

  3. 确保正确的链接过程:链接器的处理过程通常需要根据链接脚本和命令行选项执行一系列操作,如为符号分配地址、将输入文件与输出文件相关联、应用重定位等。lang_for_each_statement 确保了这些操作能够正确执行。

  4. 实现链接脚本逻辑:在链接脚本中,通常会定义一系列的链接器语句,包括节的位置和属性、符号的地址分配等。lang_for_each_statement 用于实现这些链接脚本的逻辑。

        总之,lang_for_each_statement 是链接器内部用于管理和处理链接器语句的关键工具,它允许用户扩展和自定义链接过程,以适应不同的链接需求和目标。这种灵活性是链接器的一个关键特征,使其能够支持各种不同的输出文件格式和链接需求。

lang_for_each_statement (ldlang_open_output);

 lang_for_each_statement实现:

void
lang_for_each_statement (void (*func) (lang_statement_union_type *))
{
  lang_for_each_statement_worker (func, statement_list.head);
}

        lang_for_each_statement 实际调用的是lang_for_each_statement_worker函数,传递的参数为ldlang_open_output函数和statement_list结构体的head字段,即语句列表的首个语句。

  1. 它会调用 lang_for_each_statement_worker 函数,将传递给 lang_for_each_statementfunc 函数指针和语句链表 statement_list 的头部(statement_list.head)作为参数传递给 lang_for_each_statement_worker 函数。

  2. lang_for_each_statement_worker 函数会遍历 statement_list 中的每个语句,对每个语句都调用传入的 func 函数来执行特定的操作

static lang_statement_list_type statement_list;
typedef struct statement_list
{
  union lang_statement_union *  head;
  union lang_statement_union ** tail;
} lang_statement_list_type;
typedef union lang_statement_union
{
  lang_statement_header_type header;
  lang_wild_statement_type wild_statement;
  lang_data_statement_type data_statement;
  lang_reloc_statement_type reloc_statement;
  lang_address_statement_type address_statement;
  lang_output_section_statement_type output_section_statement;
  lang_assignment_statement_type assignment_statement;
  lang_input_statement_type input_statement;
  lang_target_statement_type target_statement;
  lang_output_statement_type output_statement;
  lang_input_section_type input_section;
  lang_common_statement_type common_statement;
  lang_object_symbols_statement_type object_symbols_statement;
  lang_fill_statement_type fill_statement;
  lang_padding_statement_type padding_statement;
  lang_group_statement_type group_statement;
  lang_insert_statement_type insert_statement;
} lang_statement_union_type;
typedef struct lang_statement_header_struct
{
  union lang_statement_union *next;
  enum statement_enum type;
} lang_statement_header_type;
enum statement_enum
{
  lang_output_section_statement_enum,0
  lang_assignment_statement_enum,1
  lang_input_statement_enum,2
  lang_address_statement_enum,3
  lang_wild_statement_enum,4
  lang_input_section_enum,5
  lang_object_symbols_statement_enum,6
  lang_fill_statement_enum,7
  lang_data_statement_enum,8
  lang_reloc_statement_enum,9
  lang_target_statement_enum,10
  lang_output_statement_enum,11
  lang_padding_statement_enum,12
  lang_group_statement_enum,13
  lang_insert_statement_enum,14
  lang_constructors_statement_enum
};

3.  ldlang_open_output

static void
ldlang_open_output (lang_statement_union_type *statement)
{
  switch (statement->header.type)
    {
    case lang_output_statement_enum:  // 只有是输出语句类型时
      ASSERT (link_info.output_bfd == NULL);
       // link_info.output_bfd 是一个 BFD 句柄,用于表示链接器正在生成的输出文件。
       // 它包含有关输出文件的各种信息,如文件格式、段、节、符号表等。通过该句柄,链接器可以访问和操作输出文件的各个部分,以正确地生成最终的目标文件或可执行文件。
      open_output (statement->output_statement.name);
      // 表示当前语句是描述输出文件的语句,该分支调用open_output函数打开该输出文件。statement->output_statement.name为输出文件的名称。
      ldemul_set_output_arch (); //   它将链接器的输出架构设置为与当前目标或配置相关的架构。
      
      if (config.magic_demand_paged
	  && !bfd_link_relocatable (&link_info))
	link_info.output_bfd->flags |= D_PAGED;
      else
	link_info.output_bfd->flags &= ~D_PAGED;
      if (config.text_read_only)
	link_info.output_bfd->flags |= WP_TEXT;
      else
	link_info.output_bfd->flags &= ~WP_TEXT;
      if (link_info.traditional_format)
	link_info.output_bfd->flags |= BFD_TRADITIONAL_FORMAT;
      else
	link_info.output_bfd->flags &= ~BFD_TRADITIONAL_FORMAT;
      break;

    case lang_target_statement_enum:
      current_target = statement->target_statement.target;
      // 表示当前语句是目标架构语句,该分支内部将当前目标架构设置为语句中指定的目标
      break;
    default:
      break;
    }
}

 4. lang_for_each_statement_worker

lang_for_each_statement_worker (void (*func) (lang_statement_union_type *),
				lang_statement_union_type *s)
 // 传进来的是ldlang_open_output函数以及statement_list.head
{
  for (; s != NULL; s = s->header.next)
    {
      func (s); // 调用ldlang_open_output

      switch (s->header.type)
	{
	case lang_constructors_statement_enum:
	  lang_for_each_statement_worker (func, constructor_list.head);
	  break;
	case lang_output_section_statement_enum:
	  if (s->output_section_statement.constraint != -1)
	    lang_for_each_statement_worker(func, 
                  s->output_section_statement.children.head);
	  break;
	case lang_wild_statement_enum:
	  lang_for_each_statement_worker (func,
					  s->wild_statement.children.head);
	  break;
	case lang_group_statement_enum:
	  lang_for_each_statement_worker (func,
					  s->group_statement.children.head);
	  break;
	case lang_data_statement_enum:
	case lang_reloc_statement_enum:
	case lang_object_symbols_statement_enum:
	case lang_output_statement_enum:
	case lang_target_statement_enum:
	case lang_input_section_enum:
	case lang_input_statement_enum:
	case lang_assignment_statement_enum:
	case lang_padding_statement_enum:
	case lang_address_statement_enum:
	case lang_fill_statement_enum:
	case lang_insert_statement_enum:
	  break;
	default:
	  FAIL ();
	  break;
	}
    }
}

5.open_output

static void
open_output (const char *name)
{
  // 确定输出文件的目标架构(用户指出/其它/默认) elf64-x86-64
  output_target = lang_get_output_target ();

  // 查看生成的目标文件与用户指定的字节序一致,选择适当的输出目标(output_target)
  // 根据用户指定或者默认的目标架构来决定输出文件的字节序
  /* Has the user requested a particular endianness on the command
     line?  */
  // command_line.endian  用户的命令行
  if (command_line.endian != ENDIAN_UNSET)
    {
      // ENDIAN_UNSET 用于表示字节序未明确定义或未设置

      /* Get the chosen target.  */
      const bfd_target *target
	= bfd_iterate_over_targets (get_target, (void *) output_target);
  // target 用于存储当前遍历到的目标文件描述符。当找到与 output_target 匹配的目标时,target 将存储该匹配的目标。
  //目标文件格式:描述目标文件所采用的二进制文件格式,如ELF、COFF、Mach-O等。这包括文件头的结构和元数据。
  //目标体系结构:描述目标文件的目标计算机体系结构,如x86、ARM、MIPS等。这决定了目标文件的指令集和寄存器布局。
  //字节序(endianness):描述目标文件中数据的排列顺序,如大端序(Big-Endian)或小端序(Little-Endian)。
  //文件扩展名:用于标识目标文件格式的常见文件扩展名,如".elf"、".o"等。
  //其他格式特定的信息:包括段、节、符号表等目标文件的特定信息。
      /* If the target is not supported, we cannot do anything.  */
      if (target != NULL)
	{
	  enum bfd_endian desired_endian;

	  if (command_line.endian == ENDIAN_BIG)
	    desired_endian = BFD_ENDIAN_BIG;
	  else
	    desired_endian = BFD_ENDIAN_LITTLE;

	  /* See if the target has the wrong endianness.  This should
	     not happen if the linker script has provided big and
	     little endian alternatives, but some scrips don't do
	     this.  */
	  if (target->byteorder != desired_endian)
      // target->byteorder 表示目标文件描述符所使用的字节序
	    {
	      /* If it does, then see if the target provides
		 an alternative with the correct endianness.  */
	      if (target->alternative_target != NULL
		  && (target->alternative_target->byteorder == desired_endian))
		output_target = target->alternative_target->name;
	      else
		{
		  /* Try to find a target as similar as possible to
		     the default target, but which has the desired
		     endian characteristic.  */
		  bfd_iterate_over_targets (closest_target_match,
					    (void *) target);

		  /* Oh dear - we could not find any targets that
		     satisfy our requirements.  */
		  if (winner == NULL)
		    einfo (_("%P: warning: could not find any targets"
			     " that match endianness requirement\n"));
		  else
		    output_target = winner->name;
		}
	    }
	}
    }

  link_info.output_bfd = bfd_openw (name, output_target);
  // 该函数接受输出文件的名称和目标架构作为参数
  // 链接器需要将生成的代码和数据写入此输出文件。
  // 调用bfd_openw函数打开输出文件,并赋值给link_info.output_bfd,设置link_info;

  if (link_info.output_bfd == NULL)
    {
      if (bfd_get_error () == bfd_error_invalid_target)
	einfo (_("%P%F: target %s not found\n"), output_target);

      einfo (_("%P%F: cannot open output file %s: %E\n"), name);
    }

  delete_output_file_on_failure = TRUE;
  //  设置输出文件的格式
  if (!bfd_set_format (link_info.output_bfd, bfd_object))
    einfo (_("%P%F:%s: can not make object file: %E\n"), name);

  // 设置输出文件的架构
  if (!bfd_set_arch_mach (link_info.output_bfd,
			   ldfile_output_architecture,
			   ldfile_output_machine))
    einfo (_("%P%F:%s: can not set architecture: %E\n"), name);
  // 函数创建链接时使用的hash表,用于存储链接器符号表中的符号信息,用于符号解析和链接过程中的符号查找
  link_info.hash = bfd_link_hash_table_create (link_info.output_bfd);
  if (link_info.hash == NULL)
    einfo (_("%P%F: can not create hash table: %E\n"));

  bfd_set_gp_size (link_info.output_bfd, g_switch_value);
}

 6. bfd_openw

bfd *
bfd_openw (const char *filename, const char *target)
{
  bfd *nbfd;
  const bfd_target *target_vec;

  /* nbfd has to point to head of malloc'ed block so that bfd_close may
     reclaim it correctly.  */
  nbfd = _bfd_new_bfd ();
  if (nbfd == NULL)
    return NULL;

  target_vec = bfd_find_target (target, nbfd);
  if (target_vec == NULL)
    {
      _bfd_delete_bfd (nbfd);
      return NULL;
    }

  /* PR 11983: Do not cache the original filename, but
     rather make a copy - the original might go away.  */
  nbfd->filename = xstrdup (filename);
  nbfd->direction = write_direction;

  if (bfd_open_file (nbfd) == NULL)
    {
      /* File not writeable, etc.  */
      bfd_set_error (bfd_error_system_call);
      _bfd_delete_bfd (nbfd);
      return NULL;
  }

  return nbfd;
}

        在lang_process()中,调用lang_for_each_statement() ,将ldlang_open_output作为参数传入,实际调用的是lang_for_each_statement_worker函数,传递的参数为dlang_open_output函数与statement_list结构体的head字段,即语句列表的首个语句。lang_for_each_statement_worker函数最开始会先运行作为参数被传递进来的ldlang_open_output函数,并把另一个参数statement_list结构体的head字段传递给ldlang_open_output函数。

open_output函数主要工作为:

1.确定输出文件的目标架构;

2.根据用户指定或者默认的目标架构来决定输出文件的字节序;

3.调用bfd_openw函数打开输出文件,并赋值给link_info.output_bfd,设置link_info;

4.调用bfd_link_hash_table_create函数创建链接时使用的hash表,用于存储链接器符号表中的符号信息,用于符号解析和链接过程中的符号查找。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1169489.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

误删的文件恢复了成乱码 误删的文件恢复了成乱码怎么调整

电脑系统:Windows11 电脑型号:惠普 软件版本:EasyRcovery14 关于电脑,我们可以说是非常熟悉,并熟练掌握了对电脑的最基本操作,比如复制、粘贴、新建、删除文件。但我们真的很懂它吗?比如误删…

常见的单token登录方案

现在主流的单token方案为jwttoken和redis token 常用的跟jwt token集成框架有shrio、spring security、aop切面。redis也能跟这三者集成。跟redis相比,jwt token比较难注销,得等到有效期过了才行,实际根据项目需求来就行。 简单介绍如下&am…

奇偶校验码和循环冗余码

在数据链路层的传输中,1可能变成0,0可能变成1,这是比特差错。 为了应对比特差错,有两种方式,即自动重传请求ARQ(Automatic Repeat-reQuest)和前向纠错FEC(Forward Error Correction&…

数字电路与逻辑设计 触发器

与非门构成的RS触发器 在这个中禁止RS 00 要记住s对应Q 或非门构成的RS触发器 注意这里的RS换了位置 且不允许RS 11 同步触发器 钟控RS触发器 钟控D触发器 cp 为 0 的时候不变 钟控JK触发器 00不变11改,JK不同随J摆 钟控T触发器 什么是空翻&am…

APLHA开发板系统启动

一. 简介 前面学习了 uboot的移植,在 NXP的 Kernel内核源码里添加 ALPHA开发板的工作。本文为设置CPU主频做准备,开发板从 Nand-Flash中启动根文件系统。 具体来说,我的目的是开发板能正常启动(从Nand-Flash加载根文件系统&#…

MyBatis底层原理(小白版本)

!特别声明!:这篇文章只是单纯用来应对面试,并不能用来当作深度解析的文章来看。本人才疏学浅,文章也可能有不对的地方,望指正。 此源码分析使用的是Java11 基本使用流程: String resource &q…

DAY43 完全背包理论基础 + 518.零钱兑换II

完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。 完全背包和01背包问题唯一不同…

【51单片机】串口与LED点阵屏(学习笔记)

一、串口 1、串口的概述 串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信。 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大的扩展了单片机的应用…

数据中心如何散热?

数据中心的散热是一个非常重要的问题,因为数据中心内运行的服务器、存储设备以及网络设备等都会产生大量的热量,如果不能有效地进行散热,将会导致设备故障和性能下降。下面是一些常见的数据中心散热方法: 空调系统:数据…

20231103配置cv180zb的编译环境【填坑篇】

20231103配置cv180zb的编译环境【填坑篇】 2023/11/3 11:36 感谢您选择了晶视科技的cv180zb,让我们一起来填坑。 在你根据文档找不到答案的时候,是不是想把他们家那个写文档的家伙打一顿,我顶你。 当你在在网上找一圈,BAIDU/BING/…

C++初阶(八)类和对象

📘北尘_:个人主页 🌎个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上,不忘来时的初心 文章目录 一、Static成员1、Static概念2、Static特性3、试题 二、友元1、友元的类型2、友元函数3、 友元…

CSS中flex和inline-flex的区别

CSS中flex和inline-flex的区别 起因 display:flex\inline-flex是CSS中弹性布局时,用于容器元素的样式选项。 让人有些糊涂,两者的区别时什么。网上看了一些文章都在扯什么宽度之类的,完全就是胡扯。 还有的写什么“将对象作为弹性伸缩盒显示…

【软件STM32cubeIDE下H73xx配置串口uart1+中断接收/DMA收发+HAL库+简单数据解析-基础样例】

#【软件STM32cubeIDE下H73xx配置串口uart1中断接收/DMA收发HAL库简单数据解析-基础样例】 1、前言2、实验器件3-1、普通收发中断接收实验第一步:代码调试-基本配置(1)基本配置(3)时钟配置(4)保存…

TEMU拼多多跨境平台要求提供的UL测试报告如何办理?电子产品UL测试标准要求

平台销售的电子产品,要符合指定的标准,如果不合格很容易发生起火,等危及消费者生命财产的安全,因此很多客户因为缺少UL报告,导致产品被下架,销售权被移除等问题,也少不了同行之间的恶意举报触发…

ActiveMQ、RabbitMQ、RocketMQ、Kafka介绍

一、消息中间件的使用场景 消息中间件的使用场景总结就是六个字:解耦、异步、削峰 1.解耦 如果我方系统A要与三方B系统进行数据对接,推送系统人员信息,通常我们会使用接口开发来进行。但是如果运维期间B系统进行了调整,或者推送过…

如何开始短视频的制作,短视频脚本如何写?

在短视频创作拍摄的过程中,你有没有遇到过类似的情况: 拍摄拍到中途手忙脚乱的,不知道接下来该拍摄什么类容,或者拍了一半发现拍摄场景不行,又重新调整拍摄场景,再者,拍摄过程中发现缺少了拍摄道…

【面试专题】设计模式篇①

1.工厂设计模式 工厂设计模式是一种创建型模式,它提供了一种创建对象的接口,但具体创建的对象类型可以在运行时决定。工厂设计模式主要解决的是创建对象的灵活性问题。 工厂设计模式主要包括简单工厂模式、工厂方法模式和抽象工厂模式三种。 简单工厂…

虹科示波器 | 汽车免拆检修 | 2010款江铃陆风X8车发动机怠速抖动、加速无力

一、故障现象 一辆2010款江铃陆风X8车,搭载4G6GS4N发动机,累计行驶里程约为20万km。该车在其他修理厂进行发动机大修,维修后试车,发动机怠速抖动、加速无力。用故障检测仪检测,发动机控制模块(ECM&#xff…

JVM常用命令

jps —查看pid jstat -gcutil 4364 1000 2000 —查看堆内存占用百分比,每秒打印1次,总共打印2000次 S0:幸存1区当前使用比例 S1:幸存2区当前使用比例 E:伊甸园区使用比例 O:老年代使用比例 M:元…