1、BL1

BL1是干什么的?

BL1位于ROM中,在EL3下从reset vector处开始运行。(说明了启动是从EL3启动的,为什么?因为EL3安全等级最高呗。)

(BL1这些都是镜像,我的认识就是以前的bootloader一个人干的事,现在因为安全,分成了这样一个五步的启动来保证安全。)

系统上电之后首先会运行SCP boot ROM。之后会跳转到ATF的bl1中继续执行。

BL1做的工作主要有:

  • 决定启动路径:冷启动还是热启动。
  • 架构初始化:异常向量、CPU复位处理函数配置、控制寄存器设置(SCRLR_EL3/SCR_EL3/CPTR_EL3/DAIF)
  • 平台初始化:使能Trusted Watchdog、初始化控制台、配置硬件一致性互联、配置MMU、初始化相关存储设备。
  • 固件更新处理
  • BL2镜像加载和执行:
    • BL1输出“Booting Trusted Firmware"。
    • BL1加载BL2到SRAM;如果SRAM不够或者BL2镜像错误,输出“Failed to load BL2 firmware.”。
    • BL1切换到Secure EL1并将执行权交给BL2.
      BL1镜像的异常向量表初始化了两个:一个是入口bl1_entrypoint,EL1镜像正常执行流程;另一个是SMC调用接口,EL2执行结束会通过SMC返回执行BL31。

2、BL1代码

bl1的主要代码存放在bl1目录中, bl1的连接脚本是bl1/bl1.ld.s文件,其中可以看到bl1的入口函数是:bl1_entrypoint。

BL1镜像的异常向量表初始化了两个:

  • 一个是入口bl1_entrypoint,EL1镜像正常执行流程;
  • 另一个是SMC调用接口,EL2执行结束会通过SMC返回执行BL31。

代码量较长,这里按照逻辑做了一个跳转图,最好是自己跟着图,自己拉取源码,这样看会方便一点哦。

bl1_entrypoint

该函数主要需要执行EL3环境的基本初始化,设定向量表,加载bl2 image并跳转到bl2等操作

1、找到bl1入口点:bl1_vector_table–>

进行EL3执行环境初始化,设定向量表,加载bl2镜像并跳转到BL2执行:bl1_entrypoint–>

2、该函数是以宏的形式被定义的,主要完成el3基本设置和向量表注册
:el3_entrypoint_common–>

3、该函数用来完成早期的初始化操作,主要包括memory, page table, 所需外围设备的初始化以及相关状态设定等:bl1_early_patform_setup–>

4、该函数用来获取bl2 image的描述信息,获取bl2的入口地址,准备下个阶段的CPU上下文,以备执行从bl1跳转到bl2的操作使用:bl1_prepare_next_image–>

5、bl1_main()完成架构和平台特有初始化操作,然后加载BL2镜像并跳转执行:bl1_main

1、bl1_entrypoint 入口 bl1_vector_table

vector_base bl1_vector_tableb    bl1_entrypoint      (bl1)bl1函数入口b    report_exception    /* Undef */b    bl1_aarch32_smc_handler    /* SMC call */b    report_exception    /* Prefetch abort */b    report_exception    /* Data abort */b    report_exception    /* Reserved */b    report_exception    /* IRQ */b    report_exception    /* FIQ */func bl1_aarch32_smc_handler/* ------------------------------------------------* SMC in BL1 is handled assuming that the MMU is* turned off by BL2.* ------------------------------------------------*//* ----------------------------------------------* Only RUN_IMAGE SMC is supported.* ----------------------------------------------*/mov    r8, #BL1_SMC_RUN_IMAGE--------------------------仅支持BL1_SMC_RUN_IMAGE SMC调用;其他调用触发report_exception。cmp    r8, r0blne    report_exception/* ------------------------------------------------* Make sure only Secure world reaches here.* ------------------------------------------------*/ldcopr  r8, SCRtst    r8, #SCR_NS_BIT---------------------------------如果处于非安全状态,则触发report_exception。blne    report_exception/* ---------------------------------------------------------------------* Pass control to next secure image.* Here it expects r1 to contain the address of a entry_point_info_t* structure describing the BL entrypoint.* ---------------------------------------------------------------------*/mov    r8, r1------------------------------------------第一个参数r0是功能id,即BL1_SMC_RUN_IMAGE_SMC。第二个参数是entry_point_info_t变量。mov    r0, r1bl    bl1_print_next_bl_ep_info#if SPIN_ON_BL1_EXITbl    print_debug_loop_message
debug_loop:b    debug_loop
#endifmov    r0, r8bl    bl1_plat_prepare_exitstcopr    r0, TLBIALLdsb    syisb/** Extract PC and SPSR based on struct `entry_point_info_t`* and load it in LR and SPSR registers respectively.*/ldr    lr, [r8, #ENTRY_POINT_INFO_PC_OFFSET]ldr    r1, [r8, #(ENTRY_POINT_INFO_PC_OFFSET + 4)]msr    spsr, r1add    r8, r8, #ENTRY_POINT_INFO_ARGS_OFFSETldm    r8, {r0, r1, r2, r3}----------------------------执行跳转到BL31。eret
endfunc bl1_aarch32_smc_handler

2、bl1_entrypoint()

bl1_entrypoint()进行EL3执行环境初始化,设定向量表,加载bl2镜像并跳转到BL2执行。

func bl1_entrypoint/* ---------------------------------------------------------------------* If the reset address is programmable then bl1_entrypoint() is* executed only on the cold boot path. Therefore, we can skip the warm* boot mailbox mechanism.* ---------------------------------------------------------------------*//* EL3级别运行环境的初始化,该函数定义在   include/common/aarch64/el3_common_macros.S文件中
*/el3_entrypoint_common                    \_set_endian=1                    \----------------------是否设定大小端。_warm_boot_mailbox=!PROGRAMMABLE_RESET_ADDRESS    \-----是否检查当前属于冷启动还是热启动。_secondary_cold_boot=!COLD_BOOT_SINGLE_CPU    \---------确定当前CPU是主CPU还是从CPU。_init_memory=1                    \---------------------是否初始化memory。_init_c_runtime=1                \----------------------是否初始化C语言执行环境。_exception_vectors=bl1_exceptions-----------------------异常向量表。/* ---------------------------------------------* Architectural init. can be generic e.g.* enabling stack alignment and platform spec-* ific e.g. MMU & page table setup as per the* platform memory map. Perform the latter here* and the former in bl1_main.* ---------------------------------------------*//*调用bl1_early_platform_setup函数完成底层初始化*/bl    bl1_early_platform_setup-------------------初始化memory、page table,所需外围设备初始化等。bl    bl1_plat_arch_setup-------------------------/* --------------------------------------------------* Initialize platform and jump to our c-entry point* for this type of reset.* --------------------------------------------------*//*调用bl1_main函数,初始化验证模块,加载下一阶段的image到RAM中   */bl    bl1_main--------------------------------------------进行必要初始化,加载BL2镜像然后为退出EL3进入S.EL1做好准备。/* --------------------------------------------------* Do the transition to next boot image.* --------------------------------------------------*/b    el3_exit--------------------------------//调用el3_exit函数,跳转到下一个image(bl2)
endfunc bl1_entrypoint

3、el3_entrypoint_common

该函数是以宏的形式被定义的,主要完成el3基本设置和向量表注册

.macro el3_entrypoint_common                    \_set_endian, _warm_boot_mailbox, _secondary_cold_boot,    \_init_memory, _init_c_runtime, _exception_vectors.if \_set_endian-------------------------------------------------------在进行内存读写之前,设置好系统的大小端。/* -------------------------------------------------------------* Set the CPU endianness before doing anything that might* involve memory reads or writes.* -------------------------------------------------------------*/mrs    x0, sctlr_el3bic    x0, x0, #SCTLR_EE_BITmsr    sctlr_el3, x0isb.endif /* _set_endian */.if \_warm_boot_mailbox------------------------------------------------根据当前平台的entrypoint判断是冷启动还是热启动。/* -------------------------------------------------------------* This code will be executed for both warm and cold resets.* Now is the time to distinguish between the two.* Query the platform entrypoint address and if it is not zero* then it means it is a warm boot so jump to this address.* -------------------------------------------------------------*/bl    plat_get_my_entrypointcbz    x0, do_cold_boot--------------------------------------------如果为0说明是冷启动,执行do_cold_boot();非0则跳转到entrypoint执行。br    x0do_cold_boot:.endif /* _warm_boot_mailbox *//* ---------------------------------------------------------------------* It is a cold boot.* Perform any processor specific actions upon reset e.g. cache, TLB* invalidations etc.* ---------------------------------------------------------------------*/bl    reset_handler---------------------------------------------------执行reset_handler()。el3_arch_init_common \_exception_vectors------------------------------初始化异常向量。.if \_secondary_cold_boot---------------------------------------------判断当前CPU是主CPU还是从CPU。/* -------------------------------------------------------------* Check if this is a primary or secondary CPU cold boot.* The primary CPU will set up the platform while the* secondaries are placed in a platform-specific state until the* primary CPU performs the necessary actions to bring them out* of that state and allows entry into the OS.* -------------------------------------------------------------*/bl    plat_is_my_cpu_primarycbnz    w0, do_primary_cold_boot/* This is a cold boot on a secondary CPU */bl    plat_secondary_cold_boot_setup/* plat_secondary_cold_boot_setup() is not supposed to return */bl    el3_panicdo_primary_cold_boot:.endif /* _secondary_cold_boot *//* ---------------------------------------------------------------------* Initialize memory now. Secondary CPU initialization won't get to this* point.* ---------------------------------------------------------------------*/.if \_init_memory----------------------------------------------------初始化内存。bl    platform_mem_init.endif /* _init_memory *//* ---------------------------------------------------------------------* Init C runtime environment:*   - Zero-initialise the NOBITS sections. There are 2 of them:*       - the .bss section;*       - the coherent memory section (if any).*   - Relocate the data section from ROM to RAM, if required.* ---------------------------------------------------------------------*/.if \_init_c_runtime-------------------------------------------------初始化C执行环境。
#if IMAGE_BL31/* -------------------------------------------------------------* Invalidate the RW memory used by the BL31 image. This* includes the data and NOBITS sections. This is done to* safeguard against possible corruption of this memory by* dirty cache lines in a system cache as a result of use by* an earlier boot loader stage.* -------------------------------------------------------------*/adr    x0, __RW_START__adr    x1, __RW_END__sub    x1, x1, x0bl    inv_dcache_range
#endif /* IMAGE_BL31 */ldr    x0, =__BSS_START__ldr    x1, =__BSS_SIZE__bl    zeromem16#if USE_COHERENT_MEMldr    x0, =__COHERENT_RAM_START__ldr    x1, =__COHERENT_RAM_UNALIGNED_SIZE__bl    zeromem16
#endif#if IMAGE_BL1ldr    x0, =__DATA_RAM_START__ldr    x1, =__DATA_ROM_START__ldr    x2, =__DATA_SIZE__bl    memcpy16
#endif.endif /* _init_c_runtime *//* ---------------------------------------------------------------------* Use SP_EL0 for the C runtime stack.* ---------------------------------------------------------------------*/msr    spsel, #0/* ---------------------------------------------------------------------* Allocate a stack whose memory will be marked as Normal-IS-WBWA when* the MMU is enabled. There is no risk of reading stale stack memory* after enabling the MMU as only the primary CPU is running at the* moment.* ---------------------------------------------------------------------*/bl    plat_set_my_stack.endm

4、bl1_main()

bl1_main()完成架构和平台特有初始化操作,然后加载BL2镜像并跳转执行。该函数完成bl2 image的加载和运行环境的设置,如果开启了trusted boot,则需要对image进行verify操作

/******************************************************************************** Function to perform late architectural and platform specific initialization.* It also queries the platform to load and run next BL image. Only called* by the primary cpu after a cold boot.******************************************************************************/
void bl1_main(void)
{unsigned int image_id;/* Announce our arrival */NOTICE(FIRMWARE_WELCOME_STR);NOTICE("BL1: %s\n", version_string);NOTICE("BL1: %s\n", build_message);INFO("BL1: RAM %p - %p\n", (void *)BL1_RAM_BASE,(void *)BL1_RAM_LIMIT);
.../* Perform remaining generic architectural setup from EL3 */bl1_arch_setup();#if TRUSTED_BOARD_BOOT/* Initialize authentication module */auth_mod_init();----------------------------------------------初始化安全模块和镜像解析模块。
#endif /* TRUSTED_BOARD_BOOT *//* Perform platform setup in BL1. */bl1_platform_setup();/* Get the image id of next image to load and run. */image_id = bl1_plat_get_next_image_id();----------------------获取下一级启动镜像的ID。/** We currently interpret any image id other than* BL2_IMAGE_ID as the start of firmware update.*/if (image_id == BL2_IMAGE_ID)bl1_load_bl2();------------------------------------------将BL2镜像加载到TSRAM中。elseNOTICE("BL1-FWU: *******FWU Process Started*******\n");bl1_prepare_next_image(image_id);----------------------------获取image_id对应镜像描述信息,并为进入下一级镜像执行准备好上下文。
}/******************************************************************************** This function locates and loads the BL2 raw binary image in the trusted SRAM.* Called by the primary cpu after a cold boot.* TODO: Add support for alternative image load mechanism e.g using virtio/elf* loader etc.******************************************************************************/
void bl1_load_bl2(void)
{image_desc_t *image_desc;image_info_t *image_info;entry_point_info_t *ep_info;meminfo_t *bl1_tzram_layout;meminfo_t *bl2_tzram_layout;int err;/* Get the image descriptor */image_desc = bl1_plat_get_image_desc(BL2_IMAGE_ID);assert(image_desc);/* Get the image info */image_info = &image_desc->image_info;/* Get the entry point info */ep_info = &image_desc->ep_info;/* Find out how much free trusted ram remains after BL1 load */bl1_tzram_layout = bl1_plat_sec_mem_layout();INFO("BL1: Loading BL2\n");#if LOAD_IMAGE_V2err = load_auth_image(BL2_IMAGE_ID, image_info);
#else/* Load the BL2 image */err = load_auth_image(bl1_tzram_layout,BL2_IMAGE_ID,image_info->image_base,image_info,ep_info);#endif /* LOAD_IMAGE_V2 */if (err) {ERROR("Failed to load BL2 firmware.\n");plat_error_handler(err);}/** Create a new layout of memory for BL2 as seen by BL1 i.e.* tell it the amount of total and free memory available.* This layout is created at the first free address visible* to BL2. BL2 will read the memory layout before using its* memory for other purposes.*/
#if LOAD_IMAGE_V2bl2_tzram_layout = (meminfo_t *) bl1_tzram_layout->total_base;
#elsebl2_tzram_layout = (meminfo_t *) bl1_tzram_layout->free_base;
#endif /* LOAD_IMAGE_V2 */bl1_init_bl2_mem_layout(bl1_tzram_layout, bl2_tzram_layout);ep_info->args.arg1 = (uintptr_t)bl2_tzram_layout;NOTICE("BL1: Booting BL2\n");VERBOSE("BL1: BL2 memory layout address = %p\n",(void *) bl2_tzram_layout);
}/******************************************************************************** This function prepares the context for Secure/Normal world images.* Normal world images are transitioned to EL2(if supported) else EL1.******************************************************************************/
void bl1_prepare_next_image(unsigned int image_id)
{unsigned int security_state;image_desc_t *image_desc;entry_point_info_t *next_bl_ep;#if CTX_INCLUDE_AARCH32_REGS/** Ensure that the build flag to save AArch32 system registers in CPU* context is not set for AArch64-only platforms.*/if (((read_id_aa64pfr0_el1() >> ID_AA64PFR0_EL1_SHIFT)& ID_AA64PFR0_ELX_MASK) == 0x1) {ERROR("EL1 supports AArch64-only. Please set build flag ""CTX_INCLUDE_AARCH32_REGS = 0");panic();}
#endif/* Get the image descriptor. */image_desc = bl1_plat_get_image_desc(image_id);---------------获取镜像描述信息,包括入口地址、名字等等。assert(image_desc);/* Get the entry point info. */next_bl_ep = &image_desc->ep_info;/* Get the image security state. */security_state = GET_SECURITY_STATE(next_bl_ep->h.attr);------镜像是属于安全还是非安全镜像。/* Setup the Secure/Non-Secure context if not done already. */if (!cm_get_context(security_state))cm_set_context(&bl1_cpu_context[security_state], security_state);/* Prepare the SPSR for the next BL image. */if (security_state == SECURE) {-------------------------------设置镜像运行的SPSR数据。next_bl_ep->spsr = SPSR_64(MODE_EL1, MODE_SP_ELX,DISABLE_ALL_EXCEPTIONS);} else {/* Use EL2 if supported else use EL1. */if (read_id_aa64pfr0_el1() &(ID_AA64PFR0_ELX_MASK << ID_AA64PFR0_EL2_SHIFT)) {next_bl_ep->spsr = SPSR_64(MODE_EL2, MODE_SP_ELX,DISABLE_ALL_EXCEPTIONS);} else {next_bl_ep->spsr = SPSR_64(MODE_EL1, MODE_SP_ELX,DISABLE_ALL_EXCEPTIONS);}}/* Allow platform to make change */bl1_plat_set_ep_info(image_id, next_bl_ep);/* Prepare the context for the next BL image. */cm_init_my_context(next_bl_ep);cm_prepare_el3_exit(security_state);--------------------------为运行下一个镜像,EL3做好准备。/* Indicate that image is in execution state. */image_desc->state = IMAGE_STATE_EXECUTED;print_entry_point_info(next_bl_ep);---------------------------打印下一级进行相关信息。下面即将el3_exit,退出EL3进入新的进项运行。
}

5、bl1_early_patform_setup

该函数用来完成早期的初始化操作,主要包括memory, page table, 所需外围设备的初始化以及相关状态设定等;

void bl1_early_platform_setup(void)
{
/* 使能看门狗,初始化console,初始化memory */arm_bl1_early_platform_setup();/** Initialize Interconnect for this cluster during cold boot.* No need for locks as no other CPU is active.*/plat_arm_interconnect_init();//初始化外围设备/** Enable Interconnect coherency for the primary CPU's cluster.*/plat_arm_interconnect_enter_coherency();//使能外围设备
}

5、bl1_prepare_next_image

该函数用来获取bl2 image的描述信息,获取bl2的入口地址,这只下个阶段的CPU上下文,以备执行从bl1跳转到bl2的操作使用

void bl1_prepare_next_image(unsigned int image_id)
{unsigned int security_state;image_desc_t *image_desc;entry_point_info_t *next_bl_ep;#if CTX_INCLUDE_AARCH32_REGS/** Ensure that the build flag to save AArch32 system registers in CPU* context is not set for AArch64-only platforms.*/if (((read_id_aa64pfr0_el1() >> ID_AA64PFR0_EL1_SHIFT)& ID_AA64PFR0_ELX_MASK) == 0x1) {ERROR("EL1 supports AArch64-only. Please set build flag ""CTX_INCLUDE_AARCH32_REGS = 0");panic();}
#endif/* Get the image descriptor. */
/* 获取bl2 image的描述信息,主要包括入口地址,名字等信息 */image_desc = bl1_plat_get_image_desc(image_id);assert(image_desc);/* Get the entry point info. */
/* 获取image的入口地址信息 */next_bl_ep = &image_desc->ep_info;/* Get the image security state. */
/* 获取bl2 image的安全状态(判定该image是属于安全态的image的还是非安全态的image) */security_state = GET_SECURITY_STATE(next_bl_ep->h.attr);/* Setup the Secure/Non-Secure context if not done already. */
/* 设定用于存放CPU context的变量 */if (!cm_get_context(security_state))cm_set_context(&bl1_cpu_context[security_state], security_state);/* Prepare the SPSR for the next BL image. */
/* 为下个阶段的image准备好SPSR数据 */if (security_state == SECURE) {next_bl_ep->spsr = SPSR_64(MODE_EL1, MODE_SP_ELX,DISABLE_ALL_EXCEPTIONS);} else {/* Use EL2 if supported else use EL1. */if (read_id_aa64pfr0_el1() &(ID_AA64PFR0_ELX_MASK << ID_AA64PFR0_EL2_SHIFT)) {next_bl_ep->spsr = SPSR_64(MODE_EL2, MODE_SP_ELX,DISABLE_ALL_EXCEPTIONS);} else {next_bl_ep->spsr = SPSR_64(MODE_EL1, MODE_SP_ELX,DISABLE_ALL_EXCEPTIONS);}}/* Allow platform to make change */bl1_plat_set_ep_info(image_id, next_bl_ep);/* Prepare the context for the next BL image. */
/* 使用获取到的bl2 image的entrypoint info数据来初始化cpu context */cm_init_my_context(next_bl_ep);
/* 为进入到下个EL级别做准备 */cm_prepare_el3_exit(security_state);/* Indicate that image is in execution state. */
/* 设定image的执行状态 */image_desc->state = IMAGE_STATE_EXECUTED;/* 打印出bl2 image的入口信息 */print_entry_point_info(next_bl_ep);
}

参考资料:
https://icyshuai.blog.csdn.net/article/details/72468962
https://www.cnblogs.com/arnoldlu/p/14175126.html

ATF启动(二):BL1相关推荐

  1. [ATF]-ATF启动--BL31跳转到optee和uboot

    ATF 1.背景 2.ATF编译 3.ATF的启动 4.获取optee/uboot的跳转地址 4.总结一下ATF启动流程 ★★★ 链接 : 个人博客导读首页-点击此处 ★★★ 1.背景 在vendor ...

  2. ATF启动(一):整体启动流程

    前言 关于ATF启动这里先整个宏观的概念. 这个blog讲的很好,就不重复写了,自己写还写不到这么清晰,图页很漂亮. 原文链接:https://www.cnblogs.com/arnoldlu/p/1 ...

  3. uboot启动之BL1阶段的分析1

    对uboot启动的BL1阶段的主体代码分析1 BL1阶段代码的分析以start.s文件作为主要的目标,此篇博文主要对整个个流程进行分析. 总体分析: BL1阶段的代码固化在IROM中的BL0调用执行, ...

  4. 赫拉(hera)分布式任务调度系统之项目启动(二)

    文章目录 赫拉 创建表 打包部署 测试 TIPS 加入群聊 赫拉 大数据平台,随着业务发展,每天承载着成千上万的ETL任务调度,这些任务集中在hive,shell脚本调度.怎么样让大量的ETL任务准确 ...

  5. AOSP 8.0 系统启动之三--Zygote启动(二)

    目录 前言 一.Art虚拟机启动 二.首次进入Java世界 2.1 禁用子线程创建 2.2 创建zygote socket并监听 2.3 加载系统资源 2.4 GC初始化并启动 2. 5启动Syste ...

  6. ATF启动(六):bl32(OP-TEE)-->bl33 ATF ending

    前言 前面BL31里面函数 bl31_main void bl31_main(void) {NOTICE("BL31: %s\n", version_string);NOTICE( ...

  7. ATF启动(五):服务注册

    前言 我们知道BL31提供smc这个.前面也知道了在atf中.这个smc是怎么执行的. 这篇文章我们来看看这个到底是什么?以及我么如果想要使用的话怎么去注册一个自定义的服务. 参考文档:<SMC ...

  8. Android系统10 RK3399 init进程启动(二) RK3399开发板硬件介绍

    配套系列教学视频链接: 安卓系列教程之ROM系统开发-百问100ask 说明 系统:Android10.0 设备: FireFly RK3399 (ROC-RK3399-PC-PLUS) 前言 本文介 ...

  9. 计算机为什么启动二次才能打开,win10快速启动导致电脑第二次才能开机最佳解决方法...

    为了提高win10系统的开机速度,有些用户会选择开启系统中的快速启动功能,可是近日有用户开启win10系统快速启动功能之后每次电脑开启都会出现第二次才能开机的情况,面对这种情况应该怎么解决呢?下面就来 ...

最新文章

  1. 单片机异常复位后如何保存变量数据
  2. Android--查找程序根目录下所有文件/Java IO操作
  3. Adnroid体系与系统架构
  4. Rxjava、Retrofit返回json数据解析异常处理
  5. 数据结构(python语言)目录链接
  6. FastDFS分布式文件系统设计原理
  7. 每日一题(48)—— 中断
  8. 升序排序中国_干货满满!6行python代码挑战展示2020下半年中国最娱乐的人气男明星人气排行榜top10!...
  9. HTTP WS 区别
  10. 面向对象的tab选项卡实现
  11. SSLOJ 1298.网站计划
  12. dpdk LRO功能总结
  13. 台式计算机的打印机端口,台式电脑怎么连接网络打印机
  14. vs2013断点调试
  15. 阿里巴巴图库的使用教程
  16. 在美国读博士的那七年
  17. “PHP语言,是全世界最好用的编程语言!“
  18. PT2262 单片机解码程序
  19. [乐意黎原创] 删除QQ的MiniBrowser浏览器,QQ聊天会话中点击链接直接用默认浏览器中打开
  20. 关于amdCPU+华硕主板B450主板和海盗船内存条运行Windows10系统蓝屏问题

热门文章

  1. iOS基于YYCache 改造,支持缓存 KEY 过期
  2. 好佳居窗帘十大品牌-客厅窗帘的四种风格搭配
  3. 尚硅谷MySQL学习笔记(Day_2)-DQL语言介绍:常见函数--单行函数
  4. 洛谷 P1127 词链
  5. 【LeetCode - 1176】健身计划评估
  6. 基数排序(radix sort)
  7. FileSystem的append方法文件内容追加坑记
  8. 《炬丰科技-半导体工艺》臭氧的新型光刻胶剥离技术
  9. 亚马逊跨境电商靠谱吗?需要代运营来入手?进来看就知道了!
  10. SpringBoot 如何配置 Https 以及 443端口被占用问题