Uboot启动分析笔记-----Stage1(start.S与lowlevel_init.S详解)

2024-01-25 17:08

本文主要是介绍Uboot启动分析笔记-----Stage1(start.S与lowlevel_init.S详解),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Uboot启动分析笔记-----Stage1(start.S与lowlevel_init.S详解)

 

1  u-boot.lds

    首先了解uboot的链接脚本board/my2410/u-boot.lds,它定义了目标程序各部分的链接顺序。

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")  /*指定输出可执行文件为ELF格式,32为,ARM小端*/

OUTPUT_ARCH(arm)/*指定输出可执行文件为ARM平台*/

ENTRY(_start)/*起始代码段为 _start*/

SECTIONS

{

         /* 指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置*、

         . = 0x00000000;从 0x0位置开始

         . = ALIGN(4); 4字节对齐

         .text :

         {

                   cpu/arm920t/start.o    (.text)

                   board/my2440/lowlevel_init.o      (.text)

                   *(.text)

         }

         . = ALIGN(4);

         .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

         . = ALIGN(4);

         .data : { *(.data) }  /*  只读数据段 ,所有的只读数据段都放在这个位置*/

         . = ALIGN(4);

         .got : { *(.got) }  /*指定got段, got段式是uboot自定义的一个段, 非标准段*/

         . = .;

         __u_boot_cmd_start = .; /*把__u_boot_cmd_start赋值为当前位置, 即起始位置*/

         .u_boot_cmd : { *(.u_boot_cmd) }  /*  u_boot_cmd段,所有的u-boot命令相关的定义都放在这个位置,因为每个命令定义等长,

                       所以只要以__u_boot_cmd_start为起始地址 进行查找就可以很快查找到某一个命令的定义,并依据定义的命令指针调用相应的函数进行处理用户的任务*/

         __u_boot_cmd_end = .;   /* u_boot_cmd段结束位置,由此可以看出,这段空间的长度并没有严格限制,

                                                 用户可以添加一些u-boot的命令,最终都会在连接是存放在这个位置。*/

         . = ALIGN(4);

         __bss_start = .; /*把__bss_start赋值为当前位置,即bss段的开始位置*/

         .bss (NOLOAD) : { *(.bss) . = ALIGN(4); } /*指定bss段,这里NOLOAD的意思是这段不需装载,仅在执行域中才会有这段*/

         _end = .; /*把_end赋值为当前位置,即bss段的结束位置*/

}

第一个链接的是cpu/board/start.o,也即Uboot的入口指令在start中,下面详细分析程序的跳转和函数调用关系。

 

2 Stage1 : cpu/arm920t/start.S

    这个汇编程序时UBoot的入口程序,以复位向量开头。

                    reset

                        ↓

                cpu_init_crit

                        ↓

                   relocate

                        ↓

                stack_setup

                        ↓

               start_armboot()

                        ↓

             init_sequence[]

                        ↓

                  getenv()

                        ↓

                 main_loop()

 其中前面4步为Stage1下面来详细分析一下  cpu/arm920t/start.S

这里以ARM9 2410为例,2440移植时需要修改一些配置,具体的再后面的移植中介绍.

/* 这段代码的主要作用:

进入SVC模式

关闭看门狗

屏蔽所有IRG掩码

设置时钟频率 FCLK HCLK PCLK

清楚I/D Cache

禁止MMU和CACHE

配置memory control

重定位:如果代码不在指定的地址上需要把uboot从当前位置copy到RAM指定位置上

建立堆栈,为进入C函数做准备

清0 .bss段

跳入start_armboot函数进入stage2(lib_arm/board.c)*/

/****************************************************/

.globl _start
_start: /* 系统复位位置, 各个异常向量对应的跳转代码 */
b reset /* 复位向量 */
ldr pc, _undefined_instruction /* 未定义的指令异常向量 */
ldr pc, _software_interrupt /* 软件中断异常向量 */
ldr pc, _prefetch_abort /* 预取指令操作异常向量 */
ldr pc, _data_abort /* 数据操作异常向量 */
ldr pc, _not_used /* 未使用 */
ldr pc, _irq /* 慢速中断异常向量 */
ldr pc, _fiq /* 快速中断异常向量 */
_undefined_instruction:
.word undefined_instruction
_software_interrupt:
.word software_interrupt
_prefetch_abort:
.word prefetch_abort
_data_abort:
.word data_abort
_not_used:
.word not_used
_irq:
.word irq
_fiq:
.word fiq
.balignl 16,0xdeadbeef


/**ARM9支持7种异常。下面是异常的响应过程

*第一个复位异常,它放在0x0的位置,一上电就执行它,而且我们的程序总是从复位异常处理程序

*    开始执行的,因此复位异常处理程序不需要返回。

*其他异常处理的如下:

*当一个异常出现以后,ARM会自动执行以下几个步骤:

*(1) 把下一条指令的地址放到连接寄存器LR(通常是R14),这样就能够在处理异常返回时从正确的位置继续执行。

*(2) 将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中。从异常退出的时候,就可以由SPSR来恢复CPSR。

*(3) 根据异常类型,强制设置CPSR的运行模式位。

*(4) PC(程序计数器)被强制成相关异常向量处理函数地址,从而跳转到相应的异常处理程序中。

* 当异常处理完毕后,ARM会执行以下几步操作从异常返回:

*(1)将连接寄存器LR的值减去相应的偏移量后送到PC中

*(2) 将SPSR复制回CPSR中

*(3) 若在进入异常处理时设置了中断禁止位,要在此清除

*

* ARM规定了异常向量的地址:
* b reset ; 复位 0x0

* ldr pc, _undefined_instruction ; 未定义的指令异常 0x4

* ldr pc, _software_interrupt ; 软件中断异常 0x8

* ldr pc, _prefetch_abort ; 预取指令 0xc

* ldr pc, _data_abort ; 数据 0x10

* ldr pc, _not_used ; 未使用 0x14

* ldr pc, _irq ; 慢速中断异常 0x18

* ldr pc, _fiq ; 快速中断异常 0x1c
* 当处理器碰到异常时,PC会被强制设置为对应的异常向量,从而跳转到

* 相应的处理程序,然后再返回到主程序继续执行。

* .balignl 16,0xdeadbeef, 将地址对齐到16的倍数,如果地址寄存器的值(PC)跳过4个字节才是16的倍数,

* 则使用0xdeadbeef填充这4个字节,如果它跳过1、2、3个字节,则填充值不确定。如果地址寄存器的值(PC)是16的倍数,则无需移动。********************/

 

/*************************************************************************
* Startup Code (reset vector) ………………….

*************************************************************************/
/* 保存变量的数据区 */
_TEXT_BASE:
.word TEXT_BASE   ;定义一个字并分配空间 4bytes

.globl _armboot_start
_armboot_start:
.word _start  ;声明一个全局的,并用 _start 初始化它, 在u-boot.lds中定义

/* These are defined in the board-specific linker script.*/
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif
/* the actual reset code*/
/* 系统的复位代码。系统一上电,就跳到这里运行 */

 

reset:
    mrs r0,cpsr          /* 取得当前程序状态寄存器cpsr到r0 */
    bic r0,r0,#0x1f    /* 这里使用位清除指令,把中断全部清除,只置位模式控制位为中断提供服务通常是 OS设备驱动程序的责任,

                                 因此在 Boot Loader 的执行全过程中可以不必响应任何中断*/
    orr r0,r0,#0xd3   /* 计算为超级保护模式,并disable IRQ和FIQ */
    msr cpsr,r0 /* 设置cpsr为超级保护模式 */
/*****************

*CPSR 的底8位为I     F        T        M4       M3       M2       M1       M0

          IRQdisable FIQdisable StateBit

SVC[M4~M0] = 10011

StateBit = set:THUMB state, others:ARM state

* 设置cpu运行在SVC32模式。ARM9共有7种模式:
* 用户模式(usr): arm处理器正常的程序执行状态
* 快速中断模式(fiq): 用于高速数据传输或通道处理
* 外部中断模式(irq): 用于通用的中断处理
* 超级保护模式(svc): 操作系统使用的保护模式
* 数据访问终止模式(abt): 当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护
* 系统模式(sys): 运行具有特权的操作系统任务
* 未定义指令中止模式(und): 当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真
* 通过设置ARM的CPSR寄存器,让CPU运行在操作系统保护模式,为后面进行其它操作作好准备了。

*********************************************************/

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)

     /* turn off the watchdog */

//这段是关watchdog

#if defined(CONFIG_S3C2400)

#  define pWTCON   0x15300000

#  define INTMSK   0x14400008    /* Interupt-Controller base addresses */

#  define CLKDIVN  0x14800014    /* clock divisor register */

#else

#  define pWTCON   0x53000000

#  define INTMSK   0x4A000008    /* Interupt-Controller base addresses */

#  define INTSUBMSK    0x4A00001C

#  define CLKDIVN  0x4C000014    /* clock divisor register */

# endif

     ldr  r0, =pWTCON   //具体可以查看手册

     mov  r1, #0x0

     str  r1, [r0]

     /*

      * mask all IRQs by setting all bits in the INTMR - default

      */

     mov  r1, #0xffffffff //禁止所有中断

     ldr  r0, =INTMSK

     str  r1, [r0]

# if defined(CONFIG_S3C2410)

     ldr  r1, =0x3ff

     ldr  r0, =INTSUBMSK

     str  r1, [r0]

# endif

     /* FCLK:HCLK:PCLK = 1:2:4 */

     /* default FCLK is 120 MHz ! */

     ldr  r0, =CLKDIVN

     mov  r1, #3

     str  r1, [r0]

#endif  /* defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)*/

/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
       bl cpu_init_crit  ;cpu初始化,其中会调用lowlevel_init.S


/******************************************************************************
* BL为相对寻址,以程序计数器PC 的当前值为基地址,指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址
* ARM 指令集中的4条跳转指令可以完成从当前指令向前或向后的32MB 的地址空间的跳转,
* 用的是相对寻址,它们是:B、BL、BLX、BX
*******************************************************************************/

#endif


#ifndef CONFIG_SKIP_RELOCATE_UBOOT
/* 重定位Boot代码到RAM内存,将Boot代码从FLASH移到RAM中 。因为flash中执行速度很慢,而且系统每次复位了都会在0x00000000处执行*/


relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */

/**************************************************************************
* 把_start的相对地址移到r0, 相对寻址以程序计数器PC 的当前值为基地址,
* 指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址。
* 它是与位置无关的,主要看Boot在哪里运行,也就是PC指针在哪里 (假设_start偏移量为0),
* 例如这段代码在 0x05000000 (FLASH起始地址)运行,即此时PC=0x05000000,

那么 adr r0, _start 得到 r0 = 0x05000000;
* 如果在地址 0x33008000(Boot在RAM中加载地址)运行,即此时PC=0x33008000,那么r0就是 0x33008000 了。

*通过adr指令得到当前代码的地址信息:如果U-boot是从RAM开始运行,则从adr,r0,_start得到的地址信息为*r0=_start=_TEXT_BASE=TEXT_BASE=0x3ff80000;如果U-boot从Flash开始运行,即从处理器对应的地址运 行,则*r0=0x0000,这时将会执行copy_loop标识的那段代码了
**************************************************************************/
     ldr r1, _TEXT_BASE/* test if we run from flash or RAM */

/* 把_TEXT_BASE地址处的值TEXT_BASE,也就是BOOT在RAM中运行地址移到r1 */
   cmp r0, r1 /* don't reloc during debug */

/* 比较两个地址是否相同,如果相同,就已经在RAM运行,否则就是FLASH中运行 */

beq stack_setup
/* 如果是在FLASH中运行, 则把FLASH中的Boot代码移到RAM中,然后再运行 */
ldr r2, _armboot_start /* 把_armboot_start地址处的值也就是_start绝对地址(也即在内存中的地址,这个绝对
* 地址是在 link 的时候确定的,如0x81008000)移到r2 */
        ldr r3, _bss_start /* 把_bss_start地址处的值也就是__bss_start绝对地址(也即在内存中的地址,这个绝对
* 地址是在 link 的时候确定的)移到r3 */
        sub r2, r3, r2 /* r2 <- size of armboot */ /* 计算引导代码大小并存到r2 */
        add r2, r0, r2 /* r2 <- source end address */ /* 计算引导代码最后相对地址并存入r2 */
copy_loop:
        ldmia r0!, {r3-r10} /* copy from source address [r0] */ /* 从源地址[r0]读取32个字节到寄存器,并更新r0 */
        stmia r1!, {r3-r10} /* copy to target address [r1] */ /* 拷贝寄存器r3-r10的32个字节值保存到 [r1]指明的地址,并更新r1的值 */
        cmp r0, r2 /* until source end addreee [r2] */ /* 循环拷贝,直到把所有引导代码都移到内存 */
        ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */

 

/* Set up the stack */
/* 在内存中建立起堆栈 */

 

stack_setup:
        ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
        sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
        sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
        sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
        sub sp, r0, #12 /* leave 3 words for abort-stack */


/* 初始化内存中bss段中数据为0 */
clear_bss:
        ldr r0, _bss_start /* find start of bss segment*/

 /* 把_bss_start地址处存储的绝对地址移到r0 */
ldr r1, _bss_end /* stop here */ /* 把_bss_end地址处存储的绝对地址移到r1 */
mov r2, #0x00000000 /* clear */
clbss_l:
str r2, [r0] /* clear loop... STR 指令用于从源寄存器中r2将一个32 位的字数据传送到存储器中[r0]*/
add r0, r0, #4
cmp r0, r1
ble clbss_l /* 小于或等于跳转 */
ldr pc, _start_armboot


/***********************************************************
* 已经准备好了堆栈,就可跳到C写的代码里了,也就是
* 跳到内存中的/u-boot-1.1.6/board.c --> start_armboot中运行了
* 把_start_armboot地址处的值也就是start_armboot绝对地址值移到pc
* 神啊!终于跳到C代码了。
***********************************************************/
_start_armboot:
.word start_armboot
/*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************/
/**************************************************************************
* 1、关闭 MMU和CPU 内部指令/数据 (I/D)cache。
* 2、设置 CPU 的速度和时钟频率。
* 3 、RAM 初始化。
****************************************************************************/
cpu_init_crit:
/* flush v4 I/D caches*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
/******************************************************************************************************
* MCR 指令用于将ARM 处理器寄存器中的数据传送到协处理器寄存器中,格式为:
* MCR 协处理器编码,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2,协处理器操作码2。
* 其中协处理器操作码1 和协处理器操作码2 为协处理器将要执行的操作,
* 源寄存器为ARM 处理器的寄存器,目的寄存器1 和目的寄存器2 均为协处理器的寄存器。
******************************************************************************************************/
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/ * disable MMU stuff and caches*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 /* clear bits 13, 9:8 (--V- --RS) */
bic r0, r0, #0x00000087 /* clear bits 7, 2:0 (B--- -CAM) */
orr r0, r0, #0x00000002 /* set bit 2 (A) Align */
orr r0, r0, #0x00001000 /* set bit 12 (I) I-Cache */
mcr p15, 0, r0, c1, c0, 0
/ * Go setup Memory and board specific bits prior to relocation.*/
mov ip, lr /* perserve link reg across call */
bl lowlevel_init /* go setup pll,mux,memory */ /* 位于u-boot-1.1.6/board/xxx(开发板目录名称)/lowlevel_init.S */
mov lr, ip /* restore link */
mov pc, lr /* back to my caller */ /* 从cpu_init_crit子函数返回 */
/*************************************************************************
*
* Interrupt handling
*
*************************************************************************/

/*下面是中断相关的东西,这里略去*/

 

 


3 ./board/my2410/lowlevel_init.S

 

.globl lowlevel_init //读取下面标号为SMRDATA处的地址到R0中
    ldr r0, =SMRDATA
     /*读取上面标号为_TEXT_BASE处的地址内容到R1中
     *也就是取得TEXT_BASE的值到R1中*/
    ldr    r1, _TEXT_BASE
     /*计算SMRDATA的相对地址保存到R0中
     *SMRDATA为虚拟地址,而TEXT_BASE为虚拟地址的起始地址
     *而现在Uboot的起始地址并不为虚拟地址
     *TEXT_BASE为0x33F8 0000,SMRDATA为0x33F8 06C8
     *而现在程序运行在起始地址为0x0000 0000的地方
     *所以需要计算以0x0000 0000为标准的相对地址*/
    sub    r0, r0, r1
     //取得带宽与等待状态控制寄存器地址到R1中
    ldr    r1, =BWSCON    /* Bus Width Status Controller */
     //一共需要设置13个寄存器,每个寄存器4字节,详见芯片手册
    add r2, r0, #13*4
0:
     //读取R0所指的项的值到R3中后R0自加4字节
    ldr r3, [r0], #4
     //将R3中的值保存到R1所指的地址中后R1自加4字节
    str r3, [r1], #4
     //比较R0和R2是否相等,相等则说明13个寄存器全部设置完毕
    cmp r2, r0
     //不等则跳转到上面标号为0处的地址继续执行
    bne 0b
     //跳回到返回地址中继续执行
    mov    pc, lr
    .ltorg
/* the literal pools origin */
SMRDATA:
    .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))

//设置每个BWSCON,注意BANK0由硬件连线决定了
    .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
    .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
    .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
    .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
    .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
    .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
   //设置BANKCON0~BANKCON5    

    .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
    .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))

//设置BANKCON6~BANKCON7
    .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)

//设置REFRESH,在S3C2440中11~17位是保留的,也即(Tchr<<16)无意义
    .word 0x32

//设置BANKSIZE,对于容量可以设置大写,多出来的空内存会被自动检测出来
    .word 0x30

//设置MRSRB6
    .word 0x30

//设置MRSRB7

这篇关于Uboot启动分析笔记-----Stage1(start.S与lowlevel_init.S详解)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/643980

相关文章

Python函数作用域示例详解

《Python函数作用域示例详解》本文介绍了Python中的LEGB作用域规则,详细解析了变量查找的四个层级,通过具体代码示例,展示了各层级的变量访问规则和特性,对python函数作用域相关知识感兴趣... 目录一、LEGB 规则二、作用域实例2.1 局部作用域(Local)2.2 闭包作用域(Enclos

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

Python中注释使用方法举例详解

《Python中注释使用方法举例详解》在Python编程语言中注释是必不可少的一部分,它有助于提高代码的可读性和维护性,:本文主要介绍Python中注释使用方法的相关资料,需要的朋友可以参考下... 目录一、前言二、什么是注释?示例:三、单行注释语法:以 China编程# 开头,后面的内容为注释内容示例:示例:四

mysql表操作与查询功能详解

《mysql表操作与查询功能详解》本文系统讲解MySQL表操作与查询,涵盖创建、修改、复制表语法,基本查询结构及WHERE、GROUPBY等子句,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随... 目录01.表的操作1.1表操作概览1.2创建表1.3修改表1.4复制表02.基本查询操作2.1 SE

MySQL中的锁机制详解之全局锁,表级锁,行级锁

《MySQL中的锁机制详解之全局锁,表级锁,行级锁》MySQL锁机制通过全局、表级、行级锁控制并发,保障数据一致性与隔离性,全局锁适用于全库备份,表级锁适合读多写少场景,行级锁(InnoDB)实现高并... 目录一、锁机制基础:从并发问题到锁分类1.1 并发访问的三大问题1.2 锁的核心作用1.3 锁粒度分

MySQL数据库中ENUM的用法是什么详解

《MySQL数据库中ENUM的用法是什么详解》ENUM是一个字符串对象,用于指定一组预定义的值,并可在创建表时使用,下面:本文主要介绍MySQL数据库中ENUM的用法是什么的相关资料,文中通过代码... 目录mysql 中 ENUM 的用法一、ENUM 的定义与语法二、ENUM 的特点三、ENUM 的用法1

MySQL count()聚合函数详解

《MySQLcount()聚合函数详解》MySQL中的COUNT()函数,它是SQL中最常用的聚合函数之一,用于计算表中符合特定条件的行数,本文给大家介绍MySQLcount()聚合函数,感兴趣的朋... 目录核心功能语法形式重要特性与行为如何选择使用哪种形式?总结深入剖析一下 mysql 中的 COUNT

一文详解Git中分支本地和远程删除的方法

《一文详解Git中分支本地和远程删除的方法》在使用Git进行版本控制的过程中,我们会创建多个分支来进行不同功能的开发,这就容易涉及到如何正确地删除本地分支和远程分支,下面我们就来看看相关的实现方法吧... 目录技术背景实现步骤删除本地分支删除远程www.chinasem.cn分支同步删除信息到其他机器示例步骤