GNU ld 链接器lang_process() (一)

一、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 = 1ldemul_create_output_section_statements ();  // 机器相关,x86函数为NULL/* Add to the hash table all undefineds on the command line.  */lang_place_undefineds ();  // 将未定义符号加入link_info.hashif (!bfd_section_already_linked_table_init ())  // 初始化了一个hash tableeinfo (_("%P%F: Failed to create hash table\n"));/* Create a bfd for each input file.  */current_target = default_target;  // elf64-x86-64open_input_bfds (statement_list.head, OPEN_BFD_NORMAL);  // 王子龙在看#ifdef ENABLE_PLUGINSif (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 thereare any more to be added to the link before we call theemulation's after_open hook.  We create a private list ofinput statements for this purpose, which we will eventuallyinsert into the global statement list after the first claimedfile.  */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 immediatelyafter 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, wedon't really have a good idea where to place them.  Just puttingthem at the start or end of the list is liable to leave themoutside 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 toinsert the new files at the head of the list if the insertpoint chosen is the dummy first input file.  */if (plugin_insert->filename)lang_list_insert_after (&file_chain, &files, &plugin_insert->next);elselang_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 listbefore 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 thelinker script or the -e command line option.  But if neither ofthese have been used, the target specific backend may still haveprovided 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 thiscase here and add the target's entry symbol to the list of startingpoints 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 thisafter all the input files have been opened, but before we do anyother processing, so that any operations merge_private_bfd_datadoes on the output file will be known during the rest of thelink.  */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 inputfiles.  */ldctor_build_sets ();  // 符号相关的/* PR 13683: We must rerun the assignments prior to running garbagecollection 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 sectionsto 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 ofsections, so that GCed sections are not merged, but beforeassigning dynamic symbols, since removing whole input sectionsis 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;elsefound->flags &= ~SEC_READONLY;}}/* Do anything special before sizing sections.  This is where ELFand other back-ends size dynamic sections.  */ldemul_before_allocation ();/* We must record the program headers before we try to fix thesection 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 bigeverything 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 placesof 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,0lang_assignment_statement_enum,1lang_input_statement_enum,2lang_address_statement_enum,3lang_wild_statement_enum,4lang_input_section_enum,5lang_object_symbols_statement_enum,6lang_fill_statement_enum,7lang_data_statement_enum,8lang_reloc_statement_enum,9lang_target_statement_enum,10lang_output_statement_enum,11lang_padding_statement_enum,12lang_group_statement_enum,13lang_insert_statement_enum,14lang_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;elselink_info.output_bfd->flags &= ~D_PAGED;if (config.text_read_only)link_info.output_bfd->flags |= WP_TEXT;elselink_info.output_bfd->flags &= ~WP_TEXT;if (link_info.traditional_format)link_info.output_bfd->flags |= BFD_TRADITIONAL_FORMAT;elselink_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_outputswitch (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-64output_target = lang_get_output_target ();// 查看生成的目标文件与用户指定的字节序一致,选择适当的输出目标(output_target)// 根据用户指定或者默认的目标架构来决定输出文件的字节序/* Has the user requested a particular endianness on the commandline?  */// 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;elsedesired_endian = BFD_ENDIAN_LITTLE;/* See if the target has the wrong endianness.  This shouldnot happen if the linker script has provided big andlittle endian alternatives, but some scrips don't dothis.  */if (target->byteorder != desired_endian)// target->byteorder 表示目标文件描述符所使用的字节序{/* If it does, then see if the target providesan 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 tothe default target, but which has the desiredendian characteristic.  */bfd_iterate_over_targets (closest_target_match,(void *) target);/* Oh dear - we could not find any targets thatsatisfy our requirements.  */if (winner == NULL)einfo (_("%P: warning: could not find any targets"" that match endianness requirement\n"));elseoutput_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 mayreclaim 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, butrather 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.hqwc.cn/news/161160.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

初识jQuery

文章目录 一、jQuery介绍二、Jquery优势三、jQuery版本四、jQuery对象jQuery的引用js代码与jQuery代码对比标签对象与jQuery对象 五、jQuery查找标签1.基本选择器2.组合选择器3.层次选择器4.属性选择器5.基本筛选器6.表单筛选器 六、筛选器方法七、操作标签1.class操作2.文本操…

Java-Hbase介绍

1.1. 概念 base 是分布式、面向列的开源数据库(其实准确的说是面向列族)。HDFS 为 Hbase 提供可靠的 底层数据存储服务,MapReduce 为 Hbase 提供高性能的计算能力,Zookeeper 为 Hbase 提供 稳定服务和 Failover 机制&#xff0c…

Flutter 07 框架和三棵树(Widgets、Elements和RenderObjects)

一、Flutter框架的整体结构: Flutter是Google推出并开源的跨平台开发框架,主打跨平台、高保真、高性能。开发者可以通过Dart语 言开发Flutter应用,一套代码同时运行在ios和Android平台。不仅如此,Flutter还支持Web、桌面、嵌 入应…

【RabbitMQ】RabbitMQ 消息的堆积问题 —— 使用惰性队列解决消息的堆积问题

文章目录 一、消息的堆积问题1.1 什么是消息的堆积问题1.2 消息堆积的解决思路 二、惰性队列解决消息堆积问题2.1 惰性队列和普通队列的区别2.2 惰性队列的声明方式2.3 演示惰性队列接收大量消息2.4 惰性队列的优缺点 一、消息的堆积问题 1.1 什么是消息的堆积问题 消息的堆积…

同城售后系统退款业务重构心得 | 京东云技术团队

一、重构背景 1.1、退款 到家、小时购、天选退款有2套结构,代码逻辑混乱; 其中小时购、天选部分售后单是和平生pop交互退款,部分是和售后中台交互退款;并且兼容3套逻辑; 痛点:代码繁重,缺乏…

CCLINK IEFB总线转ETHERNET/IP网络的协议网关使欧姆龙和三菱的数据互通的简单配置方法

想要实现CCLINK IEFB总线和ETHERNET/IP网络的数据互通。 捷米JM-EIP-CCLKIE是一款ETHERNET/IP从站功能的通讯网关,该产品主要功能是实现CCLINK IEFB总线和ETHERNET/IP网络的数据互通。本网关连接到ETHERNET/IP总线和CCLINK IEFB总线上都可以做为从站使用。网关分别…

创建基于多任务的并发服务器

有几个请求服务的客户端&#xff0c;我们就创建几个子进程。 这个过程有以下三个阶段&#xff1a; 这里父进程传递的套接字文件描述符&#xff0c;实际上不需要传递&#xff0c;因为子进程会复制父进程拥有的所有资源。 #include <stdio.h> #include <stdlib.h>…

Android---App 的安装过程

Android 系统中两个比较重要的服务 ActivityManagerService(AMS) 和 WindowManagerService(WMS)&#xff0c;这篇文章中通过分析 apk 的安装过程&#xff0c;来了解 Android 中另一个比较重要的系统服务 -- PackageManagerService(PMS)。 编译阶段 在分析安装过程之前&#x…

ElasticSearch集群架构实战及其原理剖析

ES集群架构 为什么要使用ES集群架构 分布式系统的可用性与扩展性&#xff1a; 高可用性 服务可用性&#xff1a;允许有节点停止服务&#xff1b;数据可用性&#xff1a;部分节点丢失&#xff0c;不会丢失数据&#xff1b; 可扩展性 请求量提升/数据的不断增长(将数据分布…

上线项目问题——无法加载响应数据

目录 无法加载响应数据解决 无法加载响应数据 上线项目时 改用服务器上的redis和MySQL 出现请求能请求到后端&#xff0c;后端也能正常返回数据&#xff0c;但是在前端页面会显示 以为是跨域问题&#xff0c;但是环境还在本地&#xff0c;排除跨域问题以为是服务器问题&#…

一款简单而强大的文档翻译网站

一款文字/文件翻译的网站,支持多个领域的翻译&#xff0c;支持常见的语言翻译(韩/日/法/英/俄/德…),最大百分比的保持原文排版(及个别除外基本100%还原)。 新用户注册就有100页的免费额度&#xff0c;每月系统还会随机赠送翻译额度&#xff0c;说实话这比好多的企业要好的多了…

设计模式_策略模式

策略模式 介绍 设计模式定义案例问题堆积在哪里解决办法策略模式对算法进现封装&#xff0c;抽象 如&#xff1a;IF elseIF 一大堆 可以配合工厂模式使用炼丹炉里做饭 要求 菜谱 和 食材可配置问题在可配置 菜谱封装菜谱 然后抽象菜谱&#xff0c;为了统一使用方法 类图 Cai…