从0写bootloader — 最简单的bootloader和App

2023-11-10

地址空间划分

对于空间划分是人为定义的。
在这里插入图片描述

bootloader编写

启动文件start.s:

					PRESERVE8    ; instruct is aligned by 8 bytes 指令集8字节对齐
                THUMB        ; use Thumb instruction set    使用thumb指令集
					
				AREA    RESET, DATA, READONLY  ;DATA定义数据段,READONLY只读
				EXPORT  __Vectors

__Vectors       DCD     0        ; 分配内存空间,0初始化,CPU自动将该处的值设置给sp,Top of Stack
                DCD     Reset_Handler   ; 分配内存空间,用Reset Handler初始化
				
				AREA    |.text|, CODE, READONLY    ; CODE表示定义代码段,READONLY只读
; Reset handler
Reset_Handler    PROC                 ; 子程序开始标志
				 EXPORT  Reset_Handler             [WEAK]   ; 导出Reset_Handler全局可见以及弱定义
				 IMPORT  main                               ; 导入main,类似C语言的extern main,声明main由外部定义
		
                 LDR     sp, = (0x20000000+0x10000)  ; 手动设置栈,只有设置了栈才能跳到C语言的世界执行
                 BL     main                         ; 跳到C语言世界执行
         
                 ENDP				; 子程序结束标志
				
				 END     ;汇编文件结束

最简单的启动,暂时不使用中断向量。
如果是通过MDK设置链接地址,不勾选use memory layout from Target Dialog(不使用Target对话框的内存布局)
在这里插入图片描述
遇到的问题:必须到导出中断向量全局可见,不然程序无法启动:EXPORT __Vectors

链接地址通过散列文件指定: 这是创建工程默认创建的散列文件

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00080000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }
}

usart.c: 只使用串口打印简单的信息

#include "usart.h"
#include "stdio.h"

typedef unsigned int uint32_t;
typedef struct
{
  volatile uint32_t SR;    /*!< USART Status register, Address offset: 0x00 */
  volatile uint32_t DR;    /*!< USART Data register,   Address offset: 0x04 */
  volatile uint32_t BRR;   /*!< USART Baud rate register, Address offset: 0x08 */
  volatile uint32_t CR1;   /*!< USART Control register 1, Address offset: 0x0C */
  volatile uint32_t CR2;   /*!< USART Control register 2, Address offset: 0x10 */
  volatile uint32_t CR3;   /*!< USART Control register 3, Address offset: 0x14 */
  volatile uint32_t GTPR;  /*!< USART Guard time and prescaler register, Address offset: 0x18 */
} USART_TypeDef;


void uart_init(void)
{
	USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
	volatile unsigned int *pReg;
	/* 使能GPIOA/USART1模块 */
	/* RCC_APB2ENR */
	pReg = (volatile unsigned int *)(0x40021000 + 0x18);
	*pReg |= (1<<2) | (1<<14);
	
	/* 配置引脚功能: PA9(USART1_TX), PA10(USART1_RX) 
	 * GPIOA_CRH = 0x40010800 + 0x04
	 */
	pReg = (volatile unsigned int *)(0x40010800 + 0x04);
	
	/* PA9(USART1_TX) */
	*pReg &= ~((3<<4) | (3<<6));
	*pReg |= (1<<4) | (2<<6);  /* Output mode, max speed 10 MHz; Alternate function output Push-pull */

	/* PA10(USART1_RX) */
	*pReg &= ~((3<<8) | (3<<10));
	*pReg |= (0<<8) | (1<<10);  /* Input mode (reset state); Floating input (reset state) */
	
	/* 设置波特率
	 * 115200 = 8000000/16/USARTDIV
	 * USARTDIV = 4.34
	 * DIV_Mantissa = 4
	 * DIV_Fraction / 16 = 0.34
	 * DIV_Fraction = 16*0.34 = 5
	 * 真实波特率:
	 * DIV_Fraction / 16 = 5/16=0.3125
	 * USARTDIV = DIV_Mantissa + DIV_Fraction / 16 = 4.3125
	 * baudrate = 8000000/16/4.3125 = 115942
 	 */
#define DIV_Mantissa 4
#define DIV_Fraction 5
	usart1->BRR = (DIV_Mantissa<<4) | (DIV_Fraction);
	
	/* 设置数据格式: 8n1 */
	usart1->CR1 = (1<<13) | (0<<12) | (0<<10) | (1<<3) | (1<<2);	
	usart1->CR2 &= ~(3<<12);
	
	/* 使能USART1 */
}
	
int mygetchar(void)
{
	USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
	while ((usart1->SR & (1<<5)) == 0);
	return usart1->DR;
}

int myputchar(char c)
{
	USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
	while ((usart1->SR & (1<<7)) == 0);
	usart1->DR = c;
	
	return c;
}

void myputstr(char *str)
{
	while (*str)
	{
        putchar(*str);
		str++;
	}
}

#if 1
//#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
    int handle;
};

FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
    x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
    USART_TypeDef *usart1 = (USART_TypeDef *)0x40013800;
	while ((usart1->SR & (1<<7)) == 0);
	usart1->DR = ch;
    return ch;
}
#endif

main.c:

#include "usart.h"
#include "stdio.h"

#define BOOTLOADER_VERSION    "1.0" 

#define APP_ADDR   0x800B001

typedef void (*app_func)(void);

void uart_init(void);

int main(void)
{
    app_func app;
    
    unsigned int *p = (unsigned int *)0x800B000;
    unsigned int addr = *(p+1);
    
    uart_init();
    
    myputstr("Bootloader: ");  
    myputstr(BOOTLOADER_VERSION);
    printf("\r\n");
    myputstr(__DATE__);
    
    printf(" %s\r\n", __TIME__);

    app = (app_func)(APP_ADDR);
    
    app();    /* 跳转执行app */
}

烧录程序上电运行,打印信息:

Bootloader: 1.0
Jul  5 2022 16:34:32

bootloader正常运行。

#define APP_ADDR   0x800B001
app = (app_func)(APP_ADDR);
    
app();    /* 跳转执行app */

从自定义划分的地址看,APP开始的地址是0x800B000
这里为什么跳转使用的是0x800B001呢?
原因是STM32是Cortex-M3内核,M3内核只支持Thumb指令集当跳转地址最后一个bit是1表示后续执行的程序使用的是Thumb指令集若为0表示使用的是ARM指令集。所以跳转地址如果是0x800B000则无法成功。


APP编写

启动文件和usart驱动内容和bootloader一样。

main.c : 简单打印一些内容

#include "stdio.h"
#include "usart.h"

void uart_init(void);

void delay(int times)
{
   while (--times);
}

char buf[100] = {"guangjieMVP"};
int main(void)
{
    int cnt = 0;
     
    uart_init();
    
    //printf("%s\r\n", buf);
    myputstr("App Start\r\n");
    myputstr(buf);
    
    while (1)
    {
        //printf("app %d\r\n", cnt++);
        myputstr("App runing\r\n");
        delay(1000000);
    }
}

这个APP无法跳转执行


跳转APP无法执行

当我们使用main作为C程序入口地址时,MDK 编译器自动会在main之前添加一段程序

    RESET
    __Vectors
        0x0800b000:    00000000    ....    DCD    0
        0x0800b004:    0800b0ad    ....    DCD    134262957
    $t
    !!!main
    __main
        0x0800b008:    f000f802    ....    BL       __scatterload ; 0x800b010
        0x0800b00c:    f000f83f    ..?.    BL       __rt_entry ; 0x800b08e
    !!!scatter
    __scatterload
    __scatterload_rt2
    __scatterload_rt2_thumb_only
       ....
    __scatterload_null
       ....
    $d
        0x0800b03c:    00000418    ....    DCD    1048
        0x0800b040:    00000438    8...    DCD    1080
    $t
    !!handler_copy
    __scatterload_copy
		...
    !!handler_zi
    __scatterload_zeroinit
   		....

,查看反汇编文件可知天降__main这段程序,包含两部分__scatterload 和__rt_entry 两部分程序
MDK __main()代码执行分析_TS_up的博客-CSDN博客___main
https://blog.csdn.net/tianshi_1988/article/details/51084516

APP程序跳转无法执行的原因是添加了__main,至于深层原因暂时搞不懂。。。


解决APP跳转无法执行

1、修改APP的main函数名字

将main函数名字改为mymain或者其他,只要不是main这个名字。

; Reset handler
Reset_Handler    PROC                 ; 子程序开始标志
				 EXPORT  Reset_Handler        [WEAK]   ; 导出Reset_Handler全局可见以及弱定义
				 IMPORT  mymain       ; 导入main,类似C语言的extern main,声明main由外部定义
		
                 LDR     sp, = (0x20000000+0x10000)  ; 设置栈,只有设置了栈才能跳到C语言的世界执行
                 BL     mymain                         ; 跳到C语言世界执行
         
                 ENDP				; 子程序结束标志
int mymain(void)
{
	...
	...
}

当修改名字后,编译器不会再自动添加一大段__main代码。

编译下载程序,复位执行程序不成功要可以查看反汇编文件看链接地址是否成功在这里插入图片描述
上电执行,打印信息:

Bootloader: 1.0
Jul  6 2022 09:51:12
App Start

?
x-h@$@App runing
App runing
myputstr(buf); 输出的是乱码,buf是放在数据段中

LR_IROM1 0x0800B000 0x00075000 {    ; load region size_region   75000
  ER_IROM1 0x0800B000 0x00075000  {  ; load address = execution address  75000
   *.o (RESET, +First)
;   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }
}
buf是属于RW段,链接地址是在0x20000000,这是是RAM开始地址,myputstr(buf)调用使用全局变量buf使用的是绝对地址,也就是链接地址,由于没有重定位,buf的链接地址处是乱码,所以输出这里输出了乱码

2、修改bootloader

将APP程序的入口函数名字改回main,修改bootloader程序为:

#include "usart.h"
#include "stdio.h"

#define BOOTLOADER_VERSION    "1.1" 

#define APP_ADDR   0x800B001

typedef void (*app_func)(void);

void uart_init(void);

int main(void)
{
    app_func app;
    
    unsigned int *p = (unsigned int *)0x800B000;
    unsigned int addr = *(p+1);
    
    uart_init();
    
    myputstr("Bootloader: ");  
    myputstr(BOOTLOADER_VERSION);
    printf("\r\n");
    myputstr(__DATE__);
    
    printf(" %s\r\n", __TIME__);
    printf("Jump addr %x\r\n", addr);
    
    app = (app_func)(addr);
       
    app();    /* 跳转执行app */
}
  • unsigned int *p = (unsigned int *)0x800B000; 0x800B000地址存放的是栈顶地址
  • unsigned int addr = *(p+1); 指针+1,此时指针值为0x800B004,该地址存放的是Reset_Handler 的地址,指针接引用就获得了Reset_Handler 地址,
  • app = (app_func)(addr); 强制将整数值转换为函数指针
  • app(); 跳转执行, 由于跳转地址直接是APP程序的Reset_Handler ,跳过了MDK编译给程序添加的__main程序,所以APP程序成功跳转执行

bootloader使用汇编进行跳转

使用汇编跳转要大概知道ARM编程的知识,主要是ARM汇编以及ARM架构过程调用标准AAPCS

ARM架构过程调用标准AAPCS 规定了函数传参返回值等规则。

简单的说就是C程序调用汇编函数是通过分别通过R0-R3来传递第1到第4个形参,超过4个的部分采用栈传递参数。

汇编跳转实现:
main.c:

#include "usart.h"
#include "stdio.h"

#define BOOTLOADER_VERSION    "1.2" 

#define APP_ADDR   0x800B000   /* APP起始地址 */

void boot_app(unsigned int sp, unsigned int pc);

int main(void)
{
    unsigned int *s_addr = (unsigned int *)APP_ADDR;
    unsigned int sp = *s_addr;
    unsigned int pc = *(s_addr+1);
    
    uart_init();
    
    myputstr("Bootloader: ");  
    myputstr(BOOTLOADER_VERSION);
    printf("\r\n");
    myputstr(__DATE__);
    printf(" %s\r\n", __TIME__);

    boot_app(sp, pc);
}

start.s:

				PRESERVE8    ; instruct is aligned by 8 bytes 指令集8字节对齐
                THUMB        ; use Thumb instruction set    使用thumb指令集
					
				AREA    RESET, DATA, READONLY  ;DATA定义数据段,READONLY只读
				EXPORT  __Vectors

__Vectors       DCD     0                          ; CPU自动将该处的值设置给sp,Top of Stack
                DCD     Reset_Handler              ; Reset Handler, 指令地址,CPU首先执行此句
				
				AREA    |.text|, CODE, READONLY    ; CODE表示定义代码段,READONLY只读
; Reset handler
Reset_Handler    PROC                 ; 子程序开始标志
				 EXPORT  Reset_Handler             [WEAK]   ; 导出Reset_Handler全局可见以及弱定义
				 IMPORT  main               ; 导入main,类似C语言的extern main,声明main由外部定义
		
                 LDR     sp, = (0x20000000+0x10000)  ; 手动设置栈,只有设置了栈才能跳到C语言的世界执行
                 BL     main                         ; 跳到C语言世界执行
         
                 ENDP				; 子程序结束标志
					 
boot_app    	PROC       ; 汇编标号即函数名或者说函数地址
				 EXPORT  boot_app    
		
                ; LDR     sp, = R0  ;  
				 mov sp, R0
                 BLX     R1    ; 跳去执行APP
         
                 ENDP		
				
				 END     ;汇编文件结束
  • 采用BL R1 ; 跳去执行APP,编译会报错。需要使用BLX或BX
  • BL: 带链接的跳转。 首先将当前指令的下一条指令地址保存在LR寄存器,然后跳转的label。通常用于调用子程序,可通过在子程序的尾部添加mov pc, lr 返回.
  • BX: 带状态切换的跳转。最低位为1时,切换到Thumb指令执行,为0时,解释为ARM指令执行
  • BLX: 带链接和状态切换的跳转。结合了BX与BL功能

重定位中断向量表

在这里插入图片描述
修改boot_app实现重定位Vector :

boot_app    	PROC       ; 汇编标号即函数名或者说函数地址
				 EXPORT  boot_app    
					
				 STR R0, [R1]  ; 向中断向量寄存器写入程序链接地址0x800B000
				 
				 LDR sp, [R0]  ;0x800B000地址处的值写入sp即设置栈
				 
				 LDR R2, [R0, #4]  ;  0x800B000 + 4 = 0x800B004,取出0x800B004地址处的值赋值给R2
				 
                 BX     R2    ; 跳去执行APP
         
                 ENDP		

main.c:

#include "usart.h"
#include "stdio.h"

#define BOOTLOADER_VERSION    "1.3" 

#define APP_ADDR   0x800B000   /* APP起始地址 */
#define VECTOR_REG_ADDR    0xE000ED08

void boot_app(unsigned int start_addr, unsigned int vector_reg);

int main(void)
{
    unsigned int s_addr = APP_ADDR;
    unsigned int vector_addr = VECTOR_REG_ADDR;
    ...
    ...
    boot_app(s_addr, vector_addr);
}
// 寄存器间接寻址。把r0中的数写入到r1中的数为地址的内存中去
str r0, [r1] 

//将r1的值赋给r0,ARM是RISC结构,数据从内存到CPU之间的移动只能通过LDR/STR指令来完成。
// 但想把数据从内存中某处读取到寄存器,只能使用ldr ,
//mov只能在寄存器之间移动数据,或者把立即数移动到寄存器中,如 MOV r0,#0是将立即数0放到r0中
ldr r0,[r1] 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从0写bootloader — 最简单的bootloader和App 的相关文章

  • STM32CubeMX学习六 之ADC配置

    文章目录 前言 一 本地环境 二 开始 1 定时器配置 2 引脚配置 在这里插入图片描述 https img blog csdnimg cn e5b6f155a1b8468cb15046a0a9d031cd png 3 内部时钟配置 4 A
  • Flash存储芯片:NOR Flash、NAND Flash、UFS和eMMC的比较与解析

    前言 在数字化时代的今天 数据的存储和管理变得越来越重要 各种各样的存储技术应运而生 以满足不同的使用场景和需求 其中 Flash存储芯片以其非易失性 可擦写性和可编程性等优势 占据了重要地位 本博客将详细介绍Flash存储芯片中的NOR
  • Keil MDK误将Project窗口关了的解决办法,窗口视图重置

    在使用MDK时 误将Project窗口或者其他窗口关了 点击view 选择对应的窗口即可 或者点击Window窗口 选择Reset View to Defaults 再点击Reset即可实现窗口的重置
  • 使用Arduino开发ESP32(17):固件更新演示

    文章目录 目的 基础说明 使用演示 通过SD卡更新固件 通过网页更新固件 总结 目的 很多时候我们会有因为bug修复 功能增加等情况需要对已投产使用的设备更新固件 这种情况下再使用工具通过串口烧录固件就不是那么方便了 比较常用的是通过网络或
  • Keil的软件仿真和硬件仿真

    一 软件仿真 Keil有很强大的软件仿真功能 通过软件仿真可以发现很多将要出现的问题 Keil的仿真可以查看很多硬件相关的寄存器 通过观察这些寄存器值的变化可以知道代码有没有正常运行 这样可以避免频繁下载程序 延长单片机Flash寿命 开始
  • MCU 常用的文件系统

    片外FLASH SPIFFS FATFS LittleFs 片上FLASH FlashDB EasyFlash
  • 【ESP32】反复重启

    ESP32开发 反复重启 串口输出如下所示 rst 0xc SW CPU RESET boot 0x13 SPI FAST FLASH BOOT configsip 188777542 SPIWP 0xee clk drv 0x00 q d
  • 优化第七代英特尔酷睿视频 RAM 中的递增 ASCII 十进制计数器

    我正在尝试针对特定的 Kaby Lake CPU i5 7300HQ 优化以下子例程 理想情况下使代码比其原始形式至少快 10 倍 该代码在 16 位实模式下作为软盘式引导加载程序运行 它在屏幕上显示一个十位十进制计数器 计数 0 9999
  • STM32 F072上的软件如何跳转到bootloader(DFU模式)?

    STM32应用笔记2606对此进行了讨论 但没有简单的代码示例 该答案已使用 IAR EWARM 在 STM32F072 Nucleo 板上进行了测试 这个答案使用 STM32标准外设库 仅此而已 请注意 验证您是否成功进入引导加载程序模式
  • Atmel SAM3X8E 双组切换用于启动不同的行为

    我目前正在使用 Arduino Due 板 该板嵌入了 Atmel SAM3X8E 处理器 我使用 Atmel Studio 版本 7 0 1645 和提供的 Atmel 软件框架 版本 3 28 1 对其进行编程 在 SAM 上运行的程序
  • 使用 GAS AT&T 指令计算引导扇区的填充长度?

    所以我想在引导扇区添加填充 比方说 目前只有一个无限循环 jmp 该扇区的长度需要为 512 字节 还有 神奇的数字0xaa55需要在最后添加 jmp skip 508 0 word 0xaa55 但是 如果我想打印一些内容 但不想计算所有
  • NASM 模块将十六进制转换为字符串并打印出来。已组装但未按预期工作

    我正在尝试编写一个简单的汇编代码以将十六进制值输出到屏幕上 有两个文件print screen asm它正在与其他模块一起工作 我认为问题出在我尝试将十六进制转换为字符串时的逻辑中 我的代码是 org 0x7c00 xor dx dx xo
  • 与 CMPSB 指令混淆

    我一直在看这段代码 我对代表 cmpsb line LOOP push cx mov cx 0x000B eleven character name mov si ImageName image name to find push di r
  • 如何在启动操作系统之前进行一些安全验证?

    我有一个可启动闪存盘 其中包含定制的 Ubunto 我想将闪存盘传递给未知的人 但它存在一些安全问题 我想确保未知的人无法更改闪存盘内容 因此 我想计算闪存内容的哈希值并在每次启动时验证它 并在验证失败或哈希不匹配时防止启动操作系统 为此
  • 引导扇区编程中的无限跳转有什么用

    我正在读一本关于如何构建操作系统的书 我浏览了这段代码 所有代码所做的就是打印 hello 但我想了解更多关于无限跳转的信息 mov ah 0x0e mov al H int 0x10 mov al e int 0x10 mov al l
  • 是否有 FAT FS 驱动程序希望引导扇区的字节 508 和 509 为零?

    在实施的同时我自己的引导扇区加载程序从 2012 年开始 https hg ulukai org ecm ldosboot rev 17884e6352e6 l1 255我确保将偏移量 508 和 509 处的字节清零 这些是标准 512
  • 如何获取 RAM 大小、引导加载程序

    我想问如何在引导加载程序中获取总 RAM 大小和可用 RAM 大小 截至目前 我知道如何获得较低的内存 但由于某种原因我无法将其打印到屏幕上 因为它保存在斧头寄存器中 这是我到目前为止所拥有的 BITS 16 BootLoader alwa
  • 使用BIOS int 13h访问不同磁头的扇区

    我的磁盘每磁道有 63 个扇区 根据我的观察 我假设 我想使用 int 13h 读取 16 位引导加载程序上的扇区 例如 如果我想读取扇区号 63 我将执行以下操作 mov dl 0x80 Drive number mov dh 0 Thi
  • 如何将以下 NASM 代码转换为 AT&T 语法,以修复“无法处理 jmp 中的非绝对段”错误

    我是 AT T 语法新手 我想将以下 NASM 语法代码转换为 AT T 语法只是为了便于理解 我尝试将其转换为 AT T 语法 lgdt gdtpointer jmp gdtcode start gdt quad 0x0000000000
  • Arduino 引导加载程序

    有人可以解释一下如何Arduino引导加载程序 http code google com p arduino source browse tags 0019 hardware arduino bootloaders atmega ATmeg

随机推荐

  • Java中多线程打印abc

    public class PrintABC private int state private int count private static final int MAX COUNT 50 public synchronized void
  • 无处不在的Attention

    概述 之前的博客中 笔者都曾提到attention机制 这种考虑全局 关注重点的机制在深度学习中很常见 尤其是self attention将自然语言处理带到一个新高度 attention增加了深度学习的可解释性 并且应用广泛 在自然语言处理
  • Linux 查看端口占用情况

    Linux 查看端口占用情况可以使用 lsof ss和 netstat 命令 一 lsof lsof命令详解 lsof list open files 是一个列出当前系统打开文件的工具 lsof 查看端口占用语法格式 lsof i 端口号
  • 37黑马QT笔记之QFileInfo提供文件相关信息

    37黑马QT笔记之QFileInfo提供文件相关信息 1 QFileInfo 这个类提供了许多函数给我们查找文件的信息 例如文件名 文件大小等等 对我们进行某些处理相当有用 例如你要对某个目录操作 需要判断它是否为目录 当你需要传输文件内容
  • 华为OD机试 - 最大数字

    题目描述 给定一个由纯数字组成以字符串表示的数值 现要求字符串中的每个数字最多只能出现2次 超过的需要进行删除 删除某个重复的数字后 其它数字相对位置保持不变 如 34533 数字3重复超过2次 需要删除其中一个3 删除第一个3后获得最大数
  • Mac下输入法总是默认中文,怎么设置成英文的?

    最近一同事在DreamWeaver里 写CSS样式的时候 默认总是中文 切到别的窗口 再切回来 就变成中文了 总要按一下切换键 时间长了特别烦人 在网上找了一些方法 最后找到一个有效的 总结一下就是 先把系统语言设置成英文 然后重启 再重新
  • java web从入门到资深进阶路线图

    要头发伤钱 要钱伤头发 如果说你已经下定了决心 为了钱不惜一切代价 请往后看 从我讲课以来 有很多朋友问过我 java web应该如何学 的确 java后端技术知识体系非常庞大 很多初学者往往一头雾水 不知道怎么学 学些什么东西 先学什么
  • uniapp安装npm依赖,导入,使用

    1 本文以uniapp安装并使用动画组件animate为案例书写 其余组件可使用相同方法套用 2 第一步现在先打开命令行 检查npm是否已经正确安装 输入npm v 返回版本号则证明npm安装正常 如有问题 自行百度npm安装 3 npm环
  • Linux - quota的举例说明

    实作 Quota 流程 1 文件系统支持 root www df h home Filesystem Size Used Avail Use Mounted on dev hda3 4 8G 740M 3 8G 17 home lt 鸟哥主
  • 【STM32】工程配置,存储空间分别情况,常用操作

    STM32的程序和数据存储分布 存储域 意义 存储介质 Code 代码域 ROM RO data 只读数据域 ROM RW data 可读可写数据域 指初始化为非0值的可读写数据 不运行的时候ROM 运行的时候RAM ZI data 可读可
  • informatica简易教程

    一 环境配置1 informatica的元数据 账号 密码 信息保存在Oracle上面2 Linux下在infa bin里有informatica的启动脚本startup infa sh3 修改windows主机的hosts 地址C Win
  • Android Studio远程主机强迫关闭了一个现有的连接

    只要android studio一编译项目 就报 远程主机强迫关闭了一个现有的连接 An Existing Connection was Forcibly Closed by the Remote Host 原因 打开了 win10 的热点
  • 【JVM】内存快照分析工具Jprofiler

    OOM了 怎么办 最好能够快速找到代码第几行出错 内存快照分析工具 MAT Jprofiler MAT Jprofiler 作用 分析Dump内存文件 快速定位内存泄漏 获得堆中的数据 获得大的对象 Jprofiler安装 IDEA插件中搜
  • 高性能RTL仿真器ESEENT专题2:firrtl编译和安装

    由于essent使用的是firrtl工具生成的中间表达式文件 fir 所以要使用和测试essent首先要编译和安装firrtl 因此本节将介绍firrtl的编译和安装 前期准备 安装varilator 参考 verilator安装 安装yo
  • django向前端填充HTML标签,防止django自动转义为字符串

    在外面嵌套一个autoescape即可 autoescape on 填充的内容 endautoescape 官方文档请参考 https docs djangoproject com zh hans 4 0 ref templates bui
  • USB过压保护芯片,高输入电压充电器(OVP)

    PW2606B是一种前端过电压和过电流保护装置 它实现了广泛的输入电压范围从2 5VDC到40VDC 过电压阈值可在外部或外部编程设置为内部默认设置 集成功率路径nFET开关的低电阻确保了更好的性能电池充电系统应用的性能 它可以提供高达1A
  • 02链路预测

    什么是链路预测 链路预测是一个利用图网络做预测的经典任务 所谓链路 Link 指节点与节点之间的连接 也就是图论中的边 如上图所示 所谓链路预测就是预测原本不相连的两个节点之间是否有边存在 若是在有权图中 那就顺便还预测下相邻边的权重 如果
  • 微信小程序——image图片组件宽高自适应方法(可详细了!)

    前言 第一次做小程序的时候使用了image图片组件 src引用一张图片后 给图片的样式设置了width 100 发现图片变形了 我想要的效果是 图片在父级中的宽是占据父级元素的100 高度自动适应 按照html中的图片在浏览器中默认显示逻辑
  • 如何解决Linux删除文件但是磁盘空间大小并没有释放的问题?

    一句话解释是因为删除的文件正在使用 关键词 文件已删除 但是df没有正确显示磁盘剩余容量 lsof grep deleted 磁盘剩余空间明显不符合预期 删除一个大文件 但还是提示磁盘空间已满 No space left on device
  • 从0写bootloader — 最简单的bootloader和App

    地址空间划分 对于空间划分是人为定义的 bootloader编写 启动文件start s PRESERVE8 instruct is aligned by 8 bytes 指令集8字节对齐 THUMB use Thumb instructi